Example #1
0
// This Draw method is a wrapper to gl.DrawElements.
func (drawer DrawElement) Draw() {
	gl.DrawElements(drawer.mode, drawer.count, drawer.typ, drawer.indices)
	if err := CheckGlError(); err != nil {
		err.Description = "gl.DrawElements"
		panic(err)
	}
}
Example #2
0
func (m *Map) Draw() {

	// gl.Enable(gl.PRIMITIVE_RESTART)
	// gl.PrimitiveRestartIndex(PRIMITIVE_RESTART)
	gl.EnableClientState(gl.VERTEX_ARRAY)
	gl.Translatef(float32(m.gridSize/2), float32(m.gridSize/2), 0)

	if m.renderSmooth {
		gl.Enable(gl.BLEND)
		gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
		gl.Enable(gl.POLYGON_SMOOTH)
		gl.Hint(gl.POLYGON_SMOOTH_HINT, gl.NICEST)
	}

	if m.renderMode == 1 {
		gl.LineWidth(1)
		gl.VertexPointer(2, gl.FLOAT, 0, m.gridLines)
		gl.Color3f(0.2, 0.2, 0.2)
		gl.DrawArrays(gl.LINES, 0, len(m.gridLines)/2)
		gl.PolygonMode(gl.FRONT_AND_BACK, gl.LINE)
	}

	for _, vl := range m.vl {
		if len(vl.vertices) > 0 {
			gl.VertexPointer(2, gl.FLOAT, 0, vl.vertices)
			gl.Color3f(vl.colors[0], vl.colors[1], vl.colors[2])
			gl.DrawElements(gl.TRIANGLES, len(vl.indices), gl.UNSIGNED_INT, vl.indices)
		}
	}

}
Example #3
0
func fill(canv *Canvas, alphaTex *glh.Texture, paint *Paint) {
	gGl.GlColorConfig.SetColor(paint.fillColor)
	defer gGl.GlColorConfig.Reset()
	gGl.Activate(gGl.FillDrawer)
	gl.ColorMask(true, true, true, true)
	gl.StencilMask(0x3)
	gl.StencilFunc(gl.LESS, 0, 0xff)
	w, h := canv.W, canv.H
	p := canv.toGLPoints([]Point{
		iPt(0, 0),
		iPt(w, 0),
		iPt(w, h),
		iPt(0, h),
	})
	vertices := []float32{
		p[0].X, p[0].Y, 0, 1,
		p[1].X, p[1].Y, 1, 1,
		p[2].X, p[2].Y, 1, 0,
		p[3].X, p[3].Y, 0, 0,
	}
	gl.BufferData(gl.ARRAY_BUFFER, len(vertices)*4, vertices, gl.STATIC_DRAW)

	elements := []uint32{
		0, 1, 2,
		2, 3, 0,
	}
	gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(elements)*4, elements, gl.STATIC_DRAW)

	glh.With(alphaTex, func() {
		gl.DrawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, nil)
	})
}
Example #4
0
func (w World) Draw() {
	w.program.Use()
	w.vao.Bind()
	w.vbo.Bind(gl.ARRAY_BUFFER)
	w.ibo.Bind(gl.ELEMENT_ARRAY_BUFFER)
	posLoc.EnableArray()
	colorLoc.EnableArray()

	gl.DrawElements(gl.LINES, int(len(w.indices)), gl.UNSIGNED_INT,
		uintptr(0))

	colorLoc.DisableArray()
	posLoc.DisableArray()
	w.ibo.Unbind(gl.ELEMENT_ARRAY_BUFFER)
	w.vbo.Unbind(gl.ARRAY_BUFFER)
}
Example #5
0
// Bind all the OpenGL buffers and such and draw the elements.
func (m Model) Draw() {
	m.program.Use()
	m.vao.Bind()
	m.vbo.Bind(gl.ARRAY_BUFFER)
	m.ibo.Bind(gl.ELEMENT_ARRAY_BUFFER)
	posLoc.EnableArray()
	colorLoc.EnableArray()

	gl.DrawElements(gl.TRIANGLES, int(len(m.indices)), gl.UNSIGNED_INT,
		uintptr(0))

	colorLoc.DisableArray()
	posLoc.DisableArray()
	m.ibo.Unbind(gl.ELEMENT_ARRAY_BUFFER)
	m.vbo.Unbind(gl.ARRAY_BUFFER)
}
func (self *OpenGLRenderer) renderOne(operation render.RenderOperation, renderState RenderState) {
	mesh := operation.Mesh
	material := operation.Material
	transform := operation.Transform

	// No attributes? no loaded or empty? Better way to handle this than spamming
	// the console?
	if mesh.VertexArrayObj == nil {
		log.Println("WARNING: Trying to render an invalid mesh", mesh)
		return
	}

	vertexArrayObj := mesh.VertexArrayObj.(gl.VertexArray)
	vertexArrayObj.Bind()

	material.Shader.Program.Use()
	material.Shader.Program.SetUniformMatrix(
		"modelViewProjection",
		renderState.ViewProjection.Times(transform),
	)

	if material.Texture != nil {
		glTexture := material.Texture.Id.(gl.Texture)
		gl.ActiveTexture(gl.TEXTURE0)

		if !material.IsCubeMap {
			glTexture.Bind(gl.TEXTURE_2D)
			defer glTexture.Unbind(gl.TEXTURE_2D)
			material.Shader.Program.SetUniformUnit("textureSampler", 0)
		} else {
			gl.Disable(gl.DEPTH_TEST)
			defer gl.Enable(gl.DEPTH_TEST)

			glTexture.Bind(gl.TEXTURE_CUBE_MAP)
			defer glTexture.Unbind(gl.TEXTURE_CUBE_MAP)
			material.Shader.Program.SetUniformUnit("cubeMap", 0)
		}
	}

	if len(mesh.IndexList) == 0 {
		gl.DrawArrays(gl.TRIANGLES, 0, len(mesh.VertexList)*3)
	} else {
		gl.DrawElements(gl.TRIANGLES, len(mesh.IndexList), gl.UNSIGNED_INT, nil)
	}
}
Example #7
0
// Arrays mode uses vertex arrays which involves gl*Pointer calls and
// directly passing in the vertex data on every render pass. This is slower
// than using VBO's, because the data has to be uploaded to the GPU on every
// render pass, but it is useful for older systems where glBufferData is
// not available.
func (mb *MeshBuffer) renderArrays(mode gl.GLenum, m Mesh, pa, ca, na, ta, ia *Attr) {
	ps, pc := m[mbPositionKey][0], m[mbPositionKey][1]
	is, ic := m[mbIndexKey][0], m[mbIndexKey][1]
	cc := m[mbColorKey][1]
	nc := m[mbNormalKey][1]
	tc := m[mbTexCoordKey][1]

	gl.PushClientAttrib(gl.CLIENT_VERTEX_ARRAY_BIT)
	defer gl.PopClientAttrib()

	if pc > 0 {
		gl.EnableClientState(gl.VERTEX_ARRAY)
		defer gl.DisableClientState(gl.VERTEX_ARRAY)
		gl.VertexPointer(pa.size, pa.typ, 0, pa.ptr(0))
	}

	if cc > 0 {
		gl.EnableClientState(gl.COLOR_ARRAY)
		defer gl.DisableClientState(gl.COLOR_ARRAY)
		gl.ColorPointer(ca.size, ca.typ, 0, ca.ptr(0))
	}

	if nc > 0 {
		gl.EnableClientState(gl.NORMAL_ARRAY)
		defer gl.DisableClientState(gl.NORMAL_ARRAY)
		gl.NormalPointer(na.typ, 0, na.ptr(0))
	}

	if tc > 0 {
		gl.EnableClientState(gl.TEXTURE_COORD_ARRAY)
		defer gl.DisableClientState(gl.TEXTURE_COORD_ARRAY)
		gl.TexCoordPointer(ta.size, ta.typ, 0, ta.ptr(0))
	}

	if ic > 0 {
		gl.EnableClientState(gl.INDEX_ARRAY)
		defer gl.DisableClientState(gl.INDEX_ARRAY)
		gl.IndexPointer(ia.typ, 0, ia.ptr(0))
		gl.DrawElements(mode, ic, ia.typ, ia.ptr(is*ia.size))
	} else {
		gl.DrawArrays(mode, ps, pc)
	}
}
Example #8
0
// renderBuffered uses VBO's. This is the preferred mode for systems
// where shader support is not present or deemed necessary.
func (mb *MeshBuffer) renderBuffered(mode gl.GLenum, m Mesh, program gl.Program) {
	is, ic := m[mbIndexKey][0], m[mbIndexKey][1]

	program.Use()
	var attribLocation gl.AttribLocation
	for _, value := range mb.attr {
		if value.name == "index" {
			continue
		}
		value.bind()
		if value.Invalid() {
			value.buffer()
		}

		attribLocation = program.GetAttribLocation(value.name)
		attribLocation.EnableArray()
		attribLocation.AttribPointer(value.size, value.typ, false, 0, uintptr(0))
		value.unbind()
	}

	ia := mb.find(mbIndexKey)
	ia.bind()
	if ia.Invalid() {
		ia.buffer()
	}

	gl.DrawElements(mode, ic, ia.typ, uintptr(is*ia.stride))
	ia.unbind()

	for _, value := range mb.attr {
		if value.name == "index" {
			continue
		}
		attribLocation := program.GetAttribLocation(value.name)
		attribLocation.DisableArray()
	}

}
Example #9
0
// renderBuffered uses VBO's. This is the preferred mode for systems
// where shader support is not present or deemed necessary.
func (mb *MeshBuffer) renderBuffered(mode gl.GLenum, m Mesh, pa, ca, na, ta, ia *Attr) {
	ps, pc := m[mbPositionKey][0], m[mbPositionKey][1]
	is, ic := m[mbIndexKey][0], m[mbIndexKey][1]
	cc := m[mbColorKey][1]
	nc := m[mbNormalKey][1]
	tc := m[mbTexCoordKey][1]

	if pc > 0 {
		gl.EnableClientState(gl.VERTEX_ARRAY)
		defer gl.DisableClientState(gl.VERTEX_ARRAY)

		pa.bind()
		if pa.Invalid() {
			pa.buffer()
		}
		gl.VertexPointer(pa.size, pa.typ, 0, uintptr(0))
		pa.unbind()
	}

	if cc > 0 {
		gl.EnableClientState(gl.COLOR_ARRAY)
		defer gl.DisableClientState(gl.COLOR_ARRAY)

		ca.bind()
		if ca.Invalid() {
			ca.buffer()
		}
		gl.ColorPointer(ca.size, ca.typ, 0, uintptr(0))
		ca.unbind()
	}

	if nc > 0 {
		gl.EnableClientState(gl.NORMAL_ARRAY)
		defer gl.DisableClientState(gl.NORMAL_ARRAY)

		na.bind()
		if na.Invalid() {
			na.buffer()
		}
		gl.NormalPointer(na.typ, 0, uintptr(0))
		na.unbind()
	}

	if tc > 0 {
		gl.EnableClientState(gl.TEXTURE_COORD_ARRAY)
		defer gl.DisableClientState(gl.TEXTURE_COORD_ARRAY)

		ta.bind()
		if ta.Invalid() {
			ta.buffer()
		}
		gl.TexCoordPointer(ta.size, ta.typ, 0, uintptr(0))
		ta.unbind()
	}

	if ic > 0 {
		ia.bind()

		if ia.Invalid() {
			ia.buffer()
		}

		gl.PushClientAttrib(gl.CLIENT_VERTEX_ARRAY_BIT)
		gl.DrawElements(mode, ic, ia.typ, uintptr(is*ia.stride))
		gl.PopClientAttrib()
		ia.unbind()
	} else {
		pa.bind()
		gl.PushClientAttrib(gl.CLIENT_VERTEX_ARRAY_BIT)
		gl.DrawArrays(mode, ps, pc)
		gl.PopClientAttrib()
		pa.unbind()
	}
}
Example #10
0
func handleElement(camera *PerspectiveCamera, element SceneObject) {
	// Material
	material := element.Material()
	program := material.Program()
	if program == nil {
		program = createProgram(element)
		material.SetProgram(program)
	}
	program.Use()
	defer program.Unuse()

	view := camera.Transform.modelMatrix().Inv()
	model := element.Transform().modelMatrix()
	projection := camera.projectionMatrix
	MVP := projection.Mul4(view).Mul4(model)

	// Set model view projection matrix
	program.uniforms["MVP"].apply(MVP)
	program.uniforms["M"].apply(model)
	program.uniforms["V"].apply(view)

	// Light position
	lightPos := mgl32.Vec3{4., 4., 4.}
	program.uniforms["LightPosition_worldspace"].apply(lightPos)

	if c, ok := material.(Colored); ok {
		if c.Color() != nil {
			program.uniforms["diffuse"].apply(c.Color())
		}
	}

	if t, ok := material.(Textured); ok {
		texture := t.Texture()
		if texture != nil {
			gl.ActiveTexture(gl.TEXTURE0)
			texture.Bind()
			defer texture.Unbind()
			program.uniforms["texture"].apply(texture)
			program.uniforms["repeat"].apply(texture.Repeat)
		}
	}

	for _, attribute := range program.attributes {
		attribute.enable()
		defer attribute.disable()
		attribute.bindBuffer()
		defer attribute.unbindBuffer()
		attribute.pointer()
		attribute.bindBuffer()
	}

	vertexAttrib := gl.AttribLocation(0)
	vertexAttrib.EnableArray()
	defer vertexAttrib.DisableArray()
	element.VertexBuffer().Bind(gl.ARRAY_BUFFER)
	defer element.VertexBuffer().Unbind(gl.ARRAY_BUFFER)
	vertexAttrib.AttribPointer(3, gl.FLOAT, false, 0, nil)

	if t, ok := material.(Wireframed); ok {
		if t.Wireframe() {
			gl.PolygonMode(gl.FRONT_AND_BACK, gl.LINE)
		} else {
			gl.PolygonMode(gl.FRONT_AND_BACK, gl.FILL)
		}
	}

	// If index available
	index := element.Index()
	if index != nil && index.count > 0 {
		index.enable()
		defer index.disable()

		gl.DrawElements(gl.TRIANGLES, index.count, gl.UNSIGNED_SHORT, nil)
	} else {
		gl.DrawArrays(element.Mode(), 0, element.Geometry().ArrayCount())
	}
}
Example #11
0
// Draw makes a glDrawElements call using the previously set uniforms and
// geometry.
func (s *Shader) Draw() {
	gl.DrawElements(gl.TRIANGLES, s.indexCount, s.indexType, uintptr(s.indexOffset))
}
Example #12
0
func main() {
	if err := glfw.Init(); err != nil {
		fmt.Fprintf(os.Stderr, "%s\n", err.Error())
		return
	}

	defer glfw.Terminate()

	glfw.OpenWindowHint(glfw.FsaaSamples, 4)
	glfw.OpenWindowHint(glfw.OpenGLVersionMajor, 3)
	glfw.OpenWindowHint(glfw.OpenGLVersionMinor, 3)
	glfw.OpenWindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)

	if err := glfw.OpenWindow(1024, 768, 0, 0, 0, 0, 32, 0, glfw.Windowed); err != nil {
		fmt.Fprintf(os.Stderr, "%s\n", err.Error())
		return
	}

	glfw.SetSwapInterval(0)

	//gl.GlewExperimental(true)
	gl.Init()     // Can't find gl.GLEW_OK or any variation, not sure how to check if this worked
	gl.GetError() // ignore error, since we're telling it to use CoreProfile above, we get "invalid enumerant" (GLError 1280) which freaks the OpenGLSentinel out

	glfw.SetWindowTitle("Tutorial 09")

	glfw.Enable(glfw.StickyKeys)
	glfw.Disable(glfw.MouseCursor) // Not in the original tutorial, but IMO it SHOULD be there
	glfw.SetMousePos(1024.0/2.0, 768.0/2.0)

	gl.ClearColor(0., 0., 0.4, 0.)

	gl.Enable(gl.DEPTH_TEST)
	gl.DepthFunc(gl.LESS)

	gl.Enable(gl.CULL_FACE)

	camera := input.NewCamera()

	vertexArray := gl.GenVertexArray()
	defer vertexArray.Delete()
	vertexArray.Bind()

	prog := helper.MakeProgram("StandardShading.vertexshader", "StandardShading.fragmentshader")
	defer prog.Delete()

	matrixID := prog.GetUniformLocation("MVP")
	viewMatrixID := prog.GetUniformLocation("V")
	modelMatrixID := prog.GetUniformLocation("M")

	texture := helper.MakeTextureFromTGA("uvmap.tga") // Had to convert to tga, go-gl is missing the texture method for DDS right now
	defer texture.Delete()
	texSampler := prog.GetUniformLocation("myTextureSampler")

	meshObj := objloader.LoadObject("suzanne.obj")

	indices, indexedVertices, indexedUVs, indexedNormals := indexer.IndexVBO(meshObj.Vertices, meshObj.UVs, meshObj.Normals)

	vertexBuffer := gl.GenBuffer()
	defer vertexBuffer.Delete()
	vertexBuffer.Bind(gl.ARRAY_BUFFER)
	gl.BufferData(gl.ARRAY_BUFFER, len(indexedVertices)*3*4, indexedVertices, gl.STATIC_DRAW)

	uvBuffer := gl.GenBuffer()
	defer uvBuffer.Delete()
	uvBuffer.Bind(gl.ARRAY_BUFFER)
	// And yet, the weird length stuff doesn't seem to matter for UV or normal
	gl.BufferData(gl.ARRAY_BUFFER, len(indexedUVs)*2*4, indexedUVs, gl.STATIC_DRAW)

	normBuffer := gl.GenBuffer()
	defer normBuffer.Delete()
	normBuffer.Bind(gl.ARRAY_BUFFER)
	gl.BufferData(gl.ARRAY_BUFFER, len(indexedNormals)*3*4, indexedNormals, gl.STATIC_DRAW)

	elementBuffer := gl.GenBuffer()
	defer elementBuffer.Delete()
	elementBuffer.Bind(gl.ELEMENT_ARRAY_BUFFER)
	gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(indices)*2, indices, gl.STATIC_DRAW) // NOTE: a GL_UNSIGNED_SHORT is 16-bits

	lightID := prog.GetUniformLocation("LightPosition_worldspace")
	lastTime := glfw.Time()
	nbFrames := 0
	// Equivalent to a do... while
	for ok := true; ok; ok = (glfw.Key(glfw.KeyEsc) != glfw.KeyPress && glfw.WindowParam(glfw.Opened) == gl.TRUE && glfw.Key('Q') != glfw.KeyPress) {

		currTime := glfw.Time()
		nbFrames++
		if currTime-lastTime >= 1.0 {
			fmt.Printf("%f ms/frame\n", 1000.0/float64(nbFrames))
			nbFrames = 0
			lastTime += 1.0
		}

		func() {
			gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

			prog.Use()
			defer gl.ProgramUnuse()

			view, proj := camera.ComputeViewPerspective()
			model := mathgl.Ident4f()

			MVP := proj.Mul4(view).Mul4(model)
			//mvpArray := mvp.AsCMOArray(mathgl.FLOAT32).([16]float32)
			//vArray := view.AsCMOArray(mathgl.FLOAT32).([16]float32)
			//mArray := model.AsCMOArray(mathgl.FLOAT32).([16]float32)

			matrixID.UniformMatrix4fv(false, MVP)
			viewMatrixID.UniformMatrix4fv(false, view)
			modelMatrixID.UniformMatrix4fv(false, model)

			lightID.Uniform3f(4., 4., 4.)

			gl.ActiveTexture(gl.TEXTURE0)
			texture.Bind(gl.TEXTURE_2D)
			defer texture.Unbind(gl.TEXTURE_2D)
			texSampler.Uniform1i(0)

			vertexAttrib := gl.AttribLocation(0)
			vertexAttrib.EnableArray()
			defer vertexAttrib.DisableArray()
			vertexBuffer.Bind(gl.ARRAY_BUFFER)
			defer vertexBuffer.Unbind(gl.ARRAY_BUFFER)
			vertexAttrib.AttribPointer(3, gl.FLOAT, false, 0, nil)

			uvAttrib := gl.AttribLocation(1)
			uvAttrib.EnableArray()
			defer uvAttrib.DisableArray()
			uvBuffer.Bind(gl.ARRAY_BUFFER)
			defer uvBuffer.Unbind(gl.ARRAY_BUFFER)
			uvAttrib.AttribPointer(2, gl.FLOAT, false, 0, nil)

			normAttrib := gl.AttribLocation(2)
			normAttrib.EnableArray()
			defer normAttrib.DisableArray()
			normBuffer.Bind(gl.ARRAY_BUFFER)
			defer normBuffer.Unbind(gl.ARRAY_BUFFER)
			normAttrib.AttribPointer(3, gl.FLOAT, false, 0, nil)

			elementBuffer.Bind(gl.ELEMENT_ARRAY_BUFFER)
			defer elementBuffer.Unbind(gl.ELEMENT_ARRAY_BUFFER)

			gl.DrawElements(gl.TRIANGLES, len(indices), gl.UNSIGNED_SHORT, nil)

			glfw.SwapBuffers()
		}() // Defers unbinds and disables to here, end of the loop
	}

}
Example #13
0
func main() {
	runtime.LockOSThread()

	if !glfw.Init() {
		fmt.Fprintf(os.Stderr, "Can't open GLFW")
		return
	}
	defer glfw.Terminate()

	glfw.WindowHint(glfw.Samples, 4)
	glfw.WindowHint(glfw.ContextVersionMajor, 3)
	glfw.WindowHint(glfw.ContextVersionMinor, 3)
	glfw.WindowHint(glfw.OpenglProfile, glfw.OpenglCoreProfile)
	glfw.WindowHint(glfw.OpenglForwardCompatible, glfw.True) // needed for macs

	window, err := glfw.CreateWindow(1024, 768, "Tutorial 8", nil, nil)
	if err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		return
	}

	window.MakeContextCurrent()

	gl.Init()
	gl.GetError() // Ignore error
	window.SetInputMode(glfw.StickyKeys, 1)
	window.SetCursorPosition(1024/2, 768/2)
	window.SetInputMode(glfw.Cursor, glfw.CursorHidden)

	gl.ClearColor(0., 0., 0.4, 0.)

	gl.Enable(gl.DEPTH_TEST)
	gl.DepthFunc(gl.LESS)

	gl.Enable(gl.CULL_FACE)

	camera := input.NewCamera(window)

	vertexArray := gl.GenVertexArray()
	defer vertexArray.Delete()
	vertexArray.Bind()

	prog := helper.MakeProgram("StandardShading.vertexshader", "StandardShading.fragmentshader")
	defer prog.Delete()

	matrixID := prog.GetUniformLocation("MVP")
	viewMatrixID := prog.GetUniformLocation("V")
	modelMatrixID := prog.GetUniformLocation("M")

	texture, err := helper.TextureFromDDS("uvmap.DDS")
	if err != nil {
		fmt.Printf("Could not load texture: %v\n", err)
	}
	defer texture.Delete()
	texSampler := prog.GetUniformLocation("myTextureSampler")

	meshObj := objloader.LoadObject("suzanne.obj", true)

	indices, indexedVertices, indexedUVs, indexedNormals := indexer.IndexVBOSlow(meshObj.Vertices, meshObj.UVs, meshObj.Normals)

	vertexBuffer := gl.GenBuffer()
	defer vertexBuffer.Delete()
	vertexBuffer.Bind(gl.ARRAY_BUFFER)
	gl.BufferData(gl.ARRAY_BUFFER, len(indexedVertices)*3*4, indexedVertices, gl.STATIC_DRAW)

	uvBuffer := gl.GenBuffer()
	defer uvBuffer.Delete()
	uvBuffer.Bind(gl.ARRAY_BUFFER)
	// And yet, the weird length stuff doesn't seem to matter for UV or normal
	gl.BufferData(gl.ARRAY_BUFFER, len(indexedUVs)*2*4, indexedUVs, gl.STATIC_DRAW)

	normBuffer := gl.GenBuffer()
	defer normBuffer.Delete()
	normBuffer.Bind(gl.ARRAY_BUFFER)
	gl.BufferData(gl.ARRAY_BUFFER, len(indexedNormals)*3*4, indexedNormals, gl.STATIC_DRAW)

	elementBuffer := gl.GenBuffer()
	defer elementBuffer.Delete()
	elementBuffer.Bind(gl.ELEMENT_ARRAY_BUFFER)
	gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(indices)*2, indices, gl.STATIC_DRAW) // NOTE: a GL_UNSIGNED_SHORT is 16-bits

	lightID := prog.GetUniformLocation("LightPosition_worldspace")
	lastTime := glfw.GetTime()
	nbFrames := 0
	// Equivalent to a do... while
	for ok := true; ok; ok = (window.GetKey(glfw.KeyEscape) != glfw.Press && !window.ShouldClose()) {

		currTime := glfw.GetTime()
		nbFrames++
		if currTime-lastTime >= 1.0 {
			fmt.Printf("%f ms/frame\n", 1000.0/float64(nbFrames))
			nbFrames = 0
			lastTime += 1.0
		}

		func() {
			gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

			prog.Use()
			defer gl.ProgramUnuse()

			view, proj := camera.ComputeViewPerspective()
			model := mgl32.Ident4()

			MVP := proj.Mul4(view).Mul4(model)
			//mvpArray := mvp.AsCMOArray(mathgl.FLOAT32).([16]float32)
			//vArray := view.AsCMOArray(mathgl.FLOAT32).([16]float32)
			//mArray := model.AsCMOArray(mathgl.FLOAT32).([16]float32)

			matrixID.UniformMatrix4fv(false, MVP)
			viewMatrixID.UniformMatrix4fv(false, view)
			modelMatrixID.UniformMatrix4fv(false, model)

			lightID.Uniform3f(4., 4., 4.)

			gl.ActiveTexture(gl.TEXTURE0)
			texture.Bind(gl.TEXTURE_2D)
			defer texture.Unbind(gl.TEXTURE_2D)
			texSampler.Uniform1i(0)

			vertexAttrib := gl.AttribLocation(0)
			vertexAttrib.EnableArray()
			defer vertexAttrib.DisableArray()
			vertexBuffer.Bind(gl.ARRAY_BUFFER)
			defer vertexBuffer.Unbind(gl.ARRAY_BUFFER)
			vertexAttrib.AttribPointer(3, gl.FLOAT, false, 0, nil)

			uvAttrib := gl.AttribLocation(1)
			uvAttrib.EnableArray()
			defer uvAttrib.DisableArray()
			uvBuffer.Bind(gl.ARRAY_BUFFER)
			defer uvBuffer.Unbind(gl.ARRAY_BUFFER)
			uvAttrib.AttribPointer(2, gl.FLOAT, false, 0, nil)

			normAttrib := gl.AttribLocation(2)
			normAttrib.EnableArray()
			defer normAttrib.DisableArray()
			normBuffer.Bind(gl.ARRAY_BUFFER)
			defer normBuffer.Unbind(gl.ARRAY_BUFFER)
			normAttrib.AttribPointer(3, gl.FLOAT, false, 0, nil)

			elementBuffer.Bind(gl.ELEMENT_ARRAY_BUFFER)
			defer elementBuffer.Unbind(gl.ELEMENT_ARRAY_BUFFER)

			gl.DrawElements(gl.TRIANGLES, len(indices), gl.UNSIGNED_SHORT, nil)

			window.SwapBuffers()
			glfw.PollEvents()
		}() // Defers unbinds and disables to here, end of the loop
	}

}