OpenGL How Many VAOs
Asked Answered
M

1

22

I am writing an OpenGL3+ application and have some confusion about the use of VAOs. Right now I just have one VAO, a normalised quad set around the origin. This single VAO contains 3 VBOs; one for positions, one for surface normals, and one GL_ELEMENT_ARRAY_BUFFER for indexing (so I can store just 4 vertices, rather than 6).

I have set up some helper methods to draw objects to the scene, such as drawCube() which takes position and rotation values and follows the procedure;

  • Bind the quad VAO.
  • Per cube face:
    • Create a model matrix that represents this face.
    • Upload the model matrix to the uniform mat4 model vertex shader variable.
    • Call glDrawElements() to draw the quad into the position for this face.

I have just set about the task of adding per-cube colors and realised that I can't add my color VBO to the single VAO as it will change with each cube, and this doesn't feel right.

I have just read the question; OpenGL VAO best practices, which tells me that my approach is wrong, and that I should use more VAOs to save the work of setting the whole scene up every time.

How many VAOs should be used? Clearly my approach of having 1 is not optimal, should there be a VAO for every static surface in the scene? What about ones that move?

I am writing to a uniform variable for each vertex, is that correct? I read that uniform shader variables should not change mid-frame, if I am able to write different values to my uniform variable, how do uniforms differ from simple in variables in a vertex shader?

Mujik answered 28/8, 2013 at 10:28 Comment(6)
A VAO doesn't contain any VBO. That's a common misconception about how VAOs work and what their state vector actually contains. You can completely initialize a VAO without a VBO ever being bound. It won't let you push a single vertex down the pipeline but it's possible. That being said, the terminology "VBO" is ambiguous in your case. Normally you refer to buffer object bound to GL_ARRAY_BUFFER as a VBO. Such buffer object have different semantics than a buffer object bound to GL_ELEMENT_ARRAY_BUFFER. The latter binding actually is VAO state. However, they're both still just buffer objects.Garrow
"I am writing to a uniform variable for each vertex, is that correct?" That is not merely incorrect, it is not possible. Either you are confusing your terms or you're doing something you don't realize you're doing.Isocline
@NicolBolas I am interleaving calls to glDrawElements with calls to glUniformMatrix4 to draw the same VAO to different positions by updating the model matrix, declared as uniform mat4 model. I can only assume this is working as my cubes are appearing in the expected positions and no other use of those positions is made.Mujik
@Garrow thanks, I felt that the ELEMENT buffer was different but did not realise it was incorrect to refer to is as a VBO. Should I be using a VAO for each polygon in my world? Or one VAO for all surfaces that have the same texture, etc... I'm trying to figure out what granularity VAOs should be used at.Mujik
@lynks: What Nicol Bolas is saying is that you cannot change the value of a uniform variable for each vertex submitted by a single draw call. You can change uniforms only in between draw calls but never for each vertex, unless you issue a draw call for each vertex which would be stupid in general and would not even work for any primitive type other than POINTS.Garrow
@lynks: A uniform is literally uniform, you set it once per-program and it is uniform across every vertex/primitive/fragment a particular shader stage processes.Oni
G
22

Clearly my approach of having 1 is not optimal, should there be a VAO for every static surface in the scene?

Absolutely not. Switching VAOs is costly. If you allocate one VAO per object in your scene, you need to switch the VAO before rendering such objects. Scale that up to a few hundred or thousand objects currently visible and you get just as much VAO changes. The questions is, if you have multiple objects which share a common memory layout, i.e. sizes/types/normalization/strides of elements are the same, why would you want to define multiple VAOs that all store the same information? You control the offset where you want to start pulling vertex attributes from directly with a corresponding draw call.

For non-indexed geometry this is trivial, since you provide a first (or an array of offsets in the multi-draw case) argument to gl[Multi]DrawArrays*() which defines the offset into the associated ARRAY_BUFFER's data store.

For indexed geometry, and if you store indices for multiple objects in a single ELEMENT_ARRAY_BUFFER, you can use gl[Multi]DrawElementsBaseVertex to provide a constant offset for indices or manually offset your indices by adding a constant offset before uploading them to the buffer object.

Being able to provide offsets into a buffer store also implies that you can store multiple distinct objects in a single ARRAY_BUFFER and corresponding indices in a single ELEMENT_ARRAY_BUFFER. However, how large buffer objects should be depends on your hardware and vendors differ in their recommendations.

I am writing to a uniform variable for each vertex, is that correct? I read that uniform shader variables should not change mid-frame, if I am able to write different values to my uniform variable, how do uniforms differ from simple in variables in a vertex shader?

First of all, a uniforms and shader input/output variables declared as in/out differ in various instances:

  • input/output variables define an interface between shader stages, i.e. output variables in one shader stage are backed by a corresponding and equally named input variable in the following stage. A uniform is available in all stages if declared with the same name and is constant until changed by the application.

  • input variables inside a vertex shader are filled from an ARRAY_BUFFER. Uniforms inside a uniform block are backed a UNIFORM_BUFFER.

  • input variables can also be written directly using the glVertexAttrib*() family of functions. single uniforms are written using the glUniform*() family of functions.

  • the values of uniforms are program state. the values of input variables are not.

The semantic difference should also be obvious: uniforms, as their name suggests, are usually constant among a set of primitives, whereas input variables usually change per vertex or fragment (due to interpolation).

EDIT: To clarify and to factor in Nicol Bolas' remark: Uniforms cannot be changed by the application for a set of vertices submitted by a single draw call, neither can vertex attributes by calling glVertexAttrib*(). Vertex shader inputs backed by a buffer objects will change either once per vertex or at some specific rate set by glVertexAttribDivisor.

EDIT2: To clarify how a VAO can theoretically store multiple layouts, you can simply define multiple arrays with different indices but equal semantics. For instance,

glVertexAttribPointer(0, 4, ....);

and

glVertexAttribPointer(1, 3, ....);

could define two arrays with indices 0 and 1, component sized 3 and 4 and both refer to position attributes of vertices. However, depending on what you want to render, you can bind a hypothetical vertex shader input

// if you have GL_ARB_explicit_attrib_location or GL3.3 available, use explicit
// locations
/*layout(location = 0)*/ in vec4 Position; 

or

/*layout(location = 1)*/ in vec3 Position;

to either index 0 or 1 explicitly or glBindAttribLocation() and still use the same VAO. AFAIK, the spec says nothing about what happens if an attribute is enabled but not sourced by the current shader but I suspect implementation to simply ignore the attribute in that case.

Whether you source the data for said attributes from the same or a different buffer object is another question but of course possible.

Personally I tend to use one VBO and VAO per layout, i.e. if my data is made up of an equal number of attributes with the same properties, I put them into a single VBO and a single VAO.

In general: You can experiment with this stuff a lot. Do it!

Garrow answered 28/8, 2013 at 11:53 Comment(5)
So a VAO has no particular attachment to one object or another? This might be my confusion, as in reading my code I see that I am writing position values into the VAO by binding VBOs to the in variables by calling glEnableVertexAttribArray() followed by glBindBuffer(). Should these two calls be part of the rendering loop? Swapping out a different 'position' VBO for each surface? Right now they are part of my setup.Mujik
A VAO stores a list of mapping between a vertex attribute index and a corresponding buffer binding. When you call glVertexAttribPointer, the name of the buffer object currently bound to ARRAY_BUFFER is recorded for the specified index and stored in the VAO. That's how a VAO associates indices and buffer object. The corresponding value can be queried with glGetVertexAttribiv() and VERTEX_ATTRIB_ARRAY_BUFFER_BINDING. See also Table 6.5 in section 6.2 (State Tables) of the GL 3.3 core spec.Garrow
Thanks for your explanations. I think I must have a very wrong idea of how things work. If you had two quads of different positions and textures, would you have one big VBO for everything and only one VAO? Or 4 VBOs for position and texture per quad? Then one or two VAOs? I would guess one VAO, which you re-bind to the position and texture VBOs using glBindBuffer() and glVertexAttribPointer() in the rendering cycle?Mujik
I suggest a single VBO/VAO. There is absolutely no reason why you should need to put the quads into separate storage and the memory layout of the attribs obviously is the same so you don't need to put stuff into separate VAOs. Also, please see my second edit for more on this.Garrow
@Garrow if you use only one VBO/VAO for a model how do you reserve the size of the buffer if the number of objects changes? Or if there is a new object you create a new VBO and attach it to the existing VAO?Invalidism

© 2022 - 2024 — McMap. All rights reserved.