Element Buffer Objects (EBOs) (also known as Index Buffers) in OpenGL allow you to reuse vertices in your draw commands. So instead of passing the same vertex three times when drawing a cube (because a cube corner is shared by three sides), you can just pass the vertex once, and then reference it by index three times.

In go-gl, your vertex array will usually be a slice of float32 values bound to your Vertex Array Object (VAO) as gl.ARRAY_BUFFER. Then you can create a slice of uint32 values that reference indices in your vertex array, specifying the order they will be traversed when you call DrawElements().

The biggest benefit of using an EBO is performance: transferring data from main memory to the graphics card is a common bottleneck in OpenGL applications. If you don’t need to transfer as many vertices, then that’s less data to move, and less bandwidth required.

Like the setup for your vertex array buffer, a name for your EBO can be generated, bound to the current vertex array object (VAO), and you can create an initialize the data storage for it. In go-gl that looks like this:

elements := []uint32{
    0, 1, 2, 2, 1, 3,
}

var ebo uint32
gl.GenBuffers(1, &ebo)
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo)
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(elements)*4, gl.Ptr(elements), gl.STATIC_DRAW)

Of course, like many other things in OpenGL, you have to do this with a VAO bound, since the VAO holds the state you’ll use to draw, and the EBO is part of that state.

Now if you are going to be calling DrawElements with the same element slice – the same list of vertex array indices in the same order – more than once, then this is what you should do. Each time it needs to be drawn, you bind your VAO, and you call gl.DrawElements. In that case, you should pass gl.PtrOffset(0) as the last argument to your gl.DrawElements command, which has the following signature:

func DrawElements(mode uint32, count int32, xtype uint32, indices unsafe.Pointer)

So your code will look something like this:

gl.BindVertexArray(vao)
gl.DrawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, gl.PtrOffset(0))

However, if your elements list changes with each call, you’ll be transmitting it from main memory to the graphics card each time anyway, and gl.DrawElements allows you to reference it without setting it up as part of VAO state in the ELEMENT_ARRAY_BUFFER first. So if you don’t call GenBuffers, BindBuffer, and BindData for an EBO, but just define an elements slice and then call this:

gl.BindVertexArray(vao)
gl.DrawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, gl.Ptr(elements))

it will work and you will get the same scene drawn on screen with a slightly simpler setup.

In my view it’s nice to have that available, especially for “quick” (haha?) OpenGL scripting. But I still expect I’ll be setting up my EBO in most serious applications.