Advertisement

Rule of thumb for VAO & VBO usage?

Started by October 15, 2017 05:49 AM
4 comments, last by Hodgman 7 years, 3 months ago

I've had a Google around for this but haven't yet found some solid advice. There is a lot of "it depends", but I'm not sure on what.

My question is what's a good rule of thumb to follow when it comes to creating/using VBOs & VAOs? As in, when should I use multiple or when should I not? My understanding so far is that if I need a new VBO, then I need a new VAO. So when it comes to rendering multiple objects I can either:

* make lots of VAO/VBO pairs and flip through them to render different objects, or

* make one big VBO and jump around its memory to render different objects. 

I also understand that if I need to render objects with different vertex attributes, then a new VAO is necessary in this case.

If that "it depends" really is quite variable, what's best for a beginner with OpenGL, assuming that better approaches can be learnt later with better understanding?

 

I've seen comments about storing all UI vertex data in one big VBO. This is unnecessary since you can just store 4 vertices that form a 1 by 1 rectangle, and then translate and scale it to the correct position and dimensions. You can also use this VBO with only 4 vertices for billboards, and particle effects (assuming you don't use a point sprite instead). This will save space on the GPU, but sending data from the CPU (after translating and scaling it) to the shader on the GPU will slow the game down. So, it may be more efficient to store all billboards, and particle effects combined together in one separate VAO & VBO.

As far as I know one huge buffer is slower to work with than multiple smaller buffers that equal the same size as the huge one, you probably won't have to worry about this if each model (not just a rectangle, but a complex model) is stored in its own VAO & VBO.

A short summary: It depends on how much data you store in the buffer, and what you're going to do with it, and the game's performance. Also any like models can use the same VAO & VBO, just translate it to the correct position. For example: boxes or a few chests scattered around a room or an area. Also, if your environment is not too huge you can combine static environment models into one VBO. I would only do this for small areas not an open world.

Advertisement
10 hours ago, Yxjmir said:

I've seen comments about storing all UI vertex data in one big VBO. This is unnecessary since you can just store 4 vertices that form a 1 by 1 rectangle, and then translate and scale it to the correct position and dimensions.

Streaming data into a VBO from the CPU is very cheap - each map/unmap/draw has a coat, but writing the actual data is very cheap (100k verts a frame shouldn't break a sweat - but 100k draw calls of one quad each is impossibly slow). 

If you translate and scale it using a matrix, then the translation/scale data will be bigger than the extra vertices that you saved. If you tightly pack the translation/scale data in a uniform it will be smaller, but setting a uniform value is extremely expensive compared to writing a few extra vertices to a mapped buffer. Not to mention that sending that data via uniforms would mean that you're performing one draw call per quad instead of one draw call for the entire UI. 

The modern way to avoid those extra vertices is to fill a VBO with the per-quad data (position, size, uv area) - one entry per-quad instead of 4 vertices. Then bind the VBO to your shader as a TBO instead of using the VAO to bind it. In the vertex shader you can then read from the buffer manually using offset 'gl_VertexID / 4' (aka id>>2) to get the per-quad data, and then use 'gl_VertexID % 4' (aka id&3) to determine which corner is being processed / whether to offset positively or negatively by half the size. 

3 hours ago, Hodgman said:

Streaming data into a VBO from the CPU is very cheap - each map/unmap/draw has a coat, but writing the actual data is very cheap (100k verts a frame shouldn't break a sweat - but 100k draw calls of one quad each is impossibly slow). 

If you translate and scale it using a matrix, then the translation/scale data will be bigger than the extra vertices that you saved. If you tightly pack the translation/scale data in a uniform it will be smaller, but setting a uniform value is extremely expensive compared to writing a few extra vertices to a mapped buffer. Not to mention that sending that data via uniforms would mean that you're performing one draw call per quad instead of one draw call for the entire UI. 

The modern way to avoid those extra vertices is to fill a VBO with the per-quad data (position, size, uv area) - one entry per-quad instead of 4 vertices. Then bind the VBO to your shader as a TBO instead of using the VAO to bind it. In the vertex shader you can then read from the buffer manually using offset 'gl_VertexID / 4' (aka id>>2) to get the per-quad data, and then use 'gl_VertexID % 4' (aka id&3) to determine which corner is being processed / whether to offset positively or negatively by half the size. 

So, lets say we had a scene with several boxes, and chests, and maybe other static objects, was I right that it would be better to store all of that (environment included) in one VBO? Also, is there an max size that a VBO can be, or is it just the maximum number of vertices your card can support?

51 minutes ago, Yxjmir said:

So, lets say we had a scene with several boxes, and chests, and maybe other static objects, was I right that it would be better to store all of that (environment included) in one VBO? Also, is there an max size that a VBO can be, or is it just the maximum number of vertices your card can support?

Probably -- putting it in the same VBO means you can (potentially) draw it all in a single draw-call, which means there's less CPU overhead (less calls to GL functions) and the GPU will be happy (lots of parallel work to do in one big batch). To achieve this you've also got to merge all your materials into a single UBO/etc, bind all the textures at once, etc... Texture arrays can help for this, and you can put material data into a TBO instead of a UBO, or just arrays inside an UBO and index them.

There is a max size and a way to query it in GL (I forget how exactly :o ). On ancient cards the limit might be a few megabytes, and on modern cards the limit will be a few gigabytes.

If you do need multiple draw-calls, you can still use one VBO though -- draw functions take an offset parameter which specifies where to start reading from the VBO. You can put one model at the start of the VBO and another in the middle. Then you can draw them both without having to change VBO/VAO bindings in between.

This topic is closed to new replies.

Advertisement