Two VBOs at once?

I’ve been doing OpenGL for a while, but have just recently jumped into VBOs. Right now I’m just trying to render a “soup” of unconnected triangles, where each vertex has an xyz position and an xyz normal. I’ve put this into two separate VBOs, which may be a bad idea.

Here’s my current code. It’s in JRuby, but I think it’s still pretty easy to read.


# put the stored data into the their respective VBOs
gl.glBindBuffer(GL.GL_ARRAY_BUFFER, @vertexVBO[0])
gl.glBufferData(GL.GL_ARRAY_BUFFER, 9*numTriangles*BufferUtil::SIZEOF_FLOAT, vertexData, GL.GL_DYNAMIC_DRAW)
gl.glBindBuffer(GL.GL_ARRAY_BUFFER, @normalVBO[0])
gl.glBufferData(GL.GL_ARRAY_BUFFER, 9*numTriangles*BufferUtil::SIZEOF_FLOAT, normalData, GL.GL_DYNAMIC_DRAW)

The vertexData is packed in per vertex like (x1,y1,z1), (x2,y2,z2), etc. and not interleaved. The normals are stored the same way.

And I draw it like this


gl.glBindBuffer(GL::GL_ARRAY_BUFFER, @vertexVBO[0])
vertexLocation = @dummyShader.attribLocation(gl, "vertex")
gl.glVertexAttribPointer(vertexLocation, 3, GL::GL_FLOAT, false, 0, 0)
gl.glEnableVertexAttribArray(vertexLocation)
gl.glBindBuffer(GL::GL_ARRAY_BUFFER, @normalVBO[0])
normalLocation = @dummyShader.attribLocation(gl, "normal")
gl.glVertexAttribPointer(normalLocation, 3, GL::GL_FLOAT, false, 0, 0)
gl.glEnableVertexAttribArray(normalLocation)

gl.glDrawArrays(GL.GL_TRIANGLES, 0, 3*numTriangles)

gl.glDisableVertexAttribArray(vertexLocation)
gl.glDisableVertexAttribArray(normalLocation)

It made sense to me and I was getting the correct vertices on screen, so I assumed it was working, but then I realized my normals weren’t being sent in to my shader. Then I saw I’m calling glBindBuffer twice before calling glDrawArrays, which might be totally incorrect.

Can I use two buffers to draw from or do I need to find a way to interleave the data. If so, how should it be interleaved? Right now I’m not using indices, which I thought would work fine, but I’m not sure if that will work with multiple attributes. Will it? Or is the bind issue not related at all?

Can I use two buffers to draw from

Yes, but you should interleave your data when possible anyway. For performance reasons.

I don’t believe that’s entirely correct. You can use two or more buffers to draw from, but only if they have different targets.

In strattonbrazil’s code, he’s bound the same target, GL_ARRAY_BUFFER, two times. Only the most recent one remains in effect.

Here’s a quote from page 41 (the second paragraph) of the OpenGL core 4.0 spec.:

BindBuffer may also be used to bind an existing buffer object. If the bind is successful no change is made to the state of the newly bound buffer object, and any previous binding to target is broken.

In the case of indexed data, then the indices are bound to target GL_ELEMENT_ARRAY_BUFFER while the vertices (and their attributes) are bound to GL_ARRAY_BUFFER; two different targets and so two different VBOs are okay.

I believe you need to combine all your vertex and attribute (i.e., normals) data into a single VBO.

As for how to do it: I personally find the whole VBO model and related parts of the OpenGL API to be the most confusing aspect of OpenGL (that I have yet encountered). I’m not really sure of the best way to structure you data. My guess, though, is that it’s best to have all your data serially in the VBO. That is, to have for a single vertex, your X then Y then Z then W then Xnorm then Ynorm then Znorm then Utexture then Vtexture then Red then Green then Blue and so on just for that single vertex, and then repeat the whole sequence for the next vertex. The reason I suspect that’s best is that that should allow accessing GPU memory most efficiently and minimize cache thrashing, since you will access memory in a longish sequence of consequtive words rather than accessing individual words scattered apart from each other and all over the GPU’s memory.

I too agree it’s an overly complicated part of OpenGL.
I have a doom model renderer which binds 4 buffer objects simultaneously, 1 for vertex, normals, texcoords and tangents. I have read nVidia papers in the past which have said most of the work is in the pointer operation and not the bind. In my case I have set up a generic vertex stream per bind / pointer so I guess Gl is happy with that. This may not be optimal and I’d be interested to know why it is not?

In strattonbrazil’s code, he’s bound the same target, GL_ARRAY_BUFFER, two times. Only the most recent one remains in effect.

That’s not really how it works.

When you call any glVertexPointer, it takes the buffer object currently bound to GL_ARRAY_BUFFER, and associates it with that particular vertex attribute. Unless you make another call to glVertexPointer for that attribute (or change VAOs), this association will remain in effect.

Correct. That’s why in addition to GL_ARRAY_BUFFER_BINDING, there is also GL_VERTEX_ARRAY_BUFFER_BINDING, GL_NORMAL_ARRAY_BUFFER_BINDING, …, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING…

I’m not sure about this. I think, VertexPointer and so on functions are here to help so that there’s no “jump slowdowns”. Maybe I’m wrong, but for me there’s no “raw” issues with such kind of memories (DRAM and so on).

I guess it depends on how many vertex attributes you have.

If you have position+uv0+uv1+uv2+normal+tangent+weights interleaved in your VBO, and only want to do a z-only pass (and only need position+weight attribute), wouldn’t it be less optimal than having separate VBOs per attribute type? Someone told me that the hardware can cache multiple VBOs just as efficiently as a single interleaved VBO. Iirc this was a side-benefit of the focus on parallelism in modern hardware architecture.

Personally I find that separating attributes leads to a cleaner design (code, file format/parsers, exporters etc) and is more flexible.
For example, in our game engine/toolkit, a user may desire to not export normals or tangents (e.g. unlit skydome model), pick an arbitrary number of texcoord channels, vertex colors, 4-bone per vertex vs 2-bone per vertex weights etc to suit his needs. If you’re going to interleave all of that, you need to include meta data with the geometry, which complicates parsing and processing the data.

But as I said, it depends on what you want to do, and how important performance vs flexibility is to you.