VAO Binding Confusion

Started by
3 comments, last by Green_Baron 4 years ago

I'm learning to use opengl and things have been going well for the last week. But I ran into an issue when trying to render both 2d and 3d objects using batch rendering. What i've read about vao's is that you can use them but you don't have to. Opengl will create a default one, unless you are running the core profile which you will have to create one. But you can just ignore them if you want. But by using them you are supposed to be able to just bind the vertex array that is associated with the VBO and IBO you want to draw. Is this the only use of them? Just being able to bind the VBO and IBO in one function call rather than 2?

Either way, my code doesn't seem to want to run without the VAO. and it doesn't want to work unless i bind the vao, vbo, and ibo. I'm very confused. from what i've read this is wrong. I should be able to just bind the vao.

in my code I use the concept of layers to create different projections to render to. Each layer has it's own vertex buffer and index buffer, and the vbo, ibo, and vao objects for the opengl bindings. Below is the code I use to create my layers.

void create_2d_layer(_render_layer* layer, u32 num_objects) {
   layer->vpo = 4; // 4 vertices per object
   layer->ipo = 6; // 6 indices per object
   layer->num_vertices = num_objects * layer->vpo;
   layer->num_indicies = num_objects * layer->ipo;

   layer->vertex_buffer = (f32*)malloc(layer->num_vertices * sizeof(_vertex));
   layer->index_buffer = (u32*)malloc(layer->num_indicies * sizeof(u32));

   setup_buffers(layer);
}

void create_3d_layer(_render_layer* layer, u32 num_objects) {
   layer->vpo = 8; // 8 vertices per object
   layer->ipo = 36; // 36 indices per object
   layer->num_vertices = num_objects * layer->vpo;
   layer->num_indicies = num_objects * layer->ipo;

   layer->vertex_buffer = (f32*)malloc(layer->num_vertices * sizeof(_vertex));
   layer->index_buffer = (u32*)malloc(layer->num_indicies * sizeof(u32));

   setup_buffers(layer);
}

//GLCALL is a macro to do some debug error handling.

static void setup_buffers(_render_layer* layer) {
    GLCALL(glGenVertexArrays(1, &layer->vao));
    GLCALL(glBindVertexArray(layer->vao));

    GLCALL(glGenBuffers(1, &layer->vbo));
    GLCALL(glBindBuffer(GL_ARRAY_BUFFER, layer->vbo));
    GLCALL(glBufferData(GL_ARRAY_BUFFER, layer->num_vertices * sizeof(_vertex), 0, GL_DYNAMIC_DRAW));

    GLCALL(glEnableVertexAttribArray(0));
    GLCALL(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(_vertex), (const void*)offsetof(_vertex, position)));
    GLCALL(glEnableVertexAttribArray(1));
    GLCALL(glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(_vertex), (const void*)offsetof(_vertex, colour)));
    GLCALL(glEnableVertexAttribArray(2));
    GLCALL(glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(_vertex), (const void*)offsetof(_vertex, texture_coord)));
    GLCALL(glEnableVertexAttribArray(3));
    GLCALL(glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, sizeof(_vertex), (const void*)offsetof(_vertex, texture_id)));

    GLCALL(glGenBuffers(1, &layer->ibo));
    GLCALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, layer->ibo));
    GLCALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, layer->num_indicies * sizeof(u32), 0, GL_DYNAMIC_DRAW));

    GLCALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
    GLCALL(glBindBuffer(GL_ARRAY_BUFFER, 0));
    GLCALL(glBindVertexArray(0));
}

After I do all my updating I update the buffers with the new vertex data:

void update_buffers(_render_layer* layer) {
   GLCALL(glBindBuffer(GL_ARRAY_BUFFER, layer->vbo));
   GLCALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, layer->ibo));

   GLCALL(glBufferSubData(GL_ARRAY_BUFFER, 0, layer->num_vertices * sizeof(_vertex), layer->vertex_buffer));
   GLCALL(glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, layer->num_indicies * sizeof(u32), layer->index_buffer));

   GLCALL(glBindBuffer(GL_ARRAY_BUFFER, 0));
   GLCALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
}

and to draw each layer:

void draw_layer(_render_layer* layer) {
   GLCALL(glBindVertexArray(layer->vao));
   GLCALL(glBindBuffer(GL_ARRAY_BUFFER, layer->vbo));
   GLCALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, layer->ibo));
   // layer->index is the index of the next object to add to the layer
   // in this case we can use it as the number of objects in the layer 
   GLCALL(glDrawElements(GL_TRIANGLES, layer->index * layer->ipo, GL_UNSIGNED_INT, 0));
   GLCALL(glBindBuffer(GL_ARRAY_BUFFER, 0));
   GLCALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
   GLCALL(glBindVertexArray(0));
}

If I comment out the vao binding code then nothing renders. but if I just bind the vao my program crashes when calling glDrawElements. From what I've read I should be able to do either. but I need to bind all 3 to render properly. Hopefully someone can help clear up this confusion.

Advertisement

First, you must have a VAO bound before rendering. So you can create and bind a vao during startup and forget about it… This won’t be a bad idea, I saw a presentation from Nvidia a while back and I remember a takeout was that VAOs aren't efficient (I think on AMD it's more efficient).


And binding buffers after binding them to the VAO is not necessary (that’s why you’re using them in the first place). For example, during rendering (draw_layer) your vao was already bound during creation (create_3d_layer). This is a normal usage of vaos.

Your problem is unbinding the element buffer (3rd line before the end of create_3d_layer). Only array buffers should be unbound before unbinding the vao.

What you’re probably trying to do is to create 2 separate vaos and render them separately. If you want to use a single vao, you’ll have to pack both layers into a unified vbo and ibo /ebo. Then, you’ll have to specify the base vertex when rendering.

It’s more book-keeping but it’s the more efficient way to go since in a tight rendering loop you want to minimize state changes. This will be the beginning of your geometry streaming system.

iradicator said:
Only array buffers should be unbound before unbinding the vao.

I'd go further and say that nothing should be unbound.

Unbinding is an OpenGL anti-pattern which may have unintended side-effects, and which may also reconfigure the pipeline in unexpected ways, particularly for inexperienced programmers. The effect of this may be that the program continues to work but seems to exhibit problems in different parts of the code to that which actually contains the bad code.

Unbinding buffer objects actually reconfigures OpenGL to source vertex attrib and element data from system memory rather than from buffers. This only affects compatibility profiles, and OpenGL purists may argue that you should be using buffers for everything anyway, but if you didn't know about this behaviour then it might very well catch you by surprise.

Unbinding texture objects doesn't do what you think it does at all - because texture object 0 is actually a valid texture object in OpenGL, so when you bind texture object 0 you are actually reverting OpenGL to version 1.0 behaviour where texture objects did not exist. And you can glTexImage this object, you can glTexSubImage it, you can draw from it, you can do everything with it as if it were a non-zero texture object. Again, if you don't know about this behaviour you might be in for a nasty surprise.

Likewise VAOs - in compatibility profiles VAO 0 is also a valid VAO.

We frequently see unbinding in OpenGL tutorials and beginning programmers pick up this anti-pattern from these tutorials, which leads to a thinking that unbinding is necessary, and then we get problems such as in the OP. Unbinding is not, and never was, necessary in OpenGL.

The purpose of unbinding in tutorials is instead as a workaround for OpenGL's long-standing bind-to-create/bind-to-edit/bind-to-use issues, where state changes made for creating or editing objects can affect state required to use them. There are almost always better solutions to unbinding, and it would indeed be preferable if tutorials taught these instead.

One such solution is to - instead of changing state back when finishing up operations - you should only set all state you need when beginning operations. That is a more robust pattern (and is actually the pattern required by newer APIs such as Vulkan) and helps to ensure that there is absolutely no state leakage between operations.

Another is to use the newer Direct State Access (DSA) APIs in more recent OpenGL versions.

In the specific case of VAO and VBO interaction, the current GL_ARRAY_BUFFER binding only has effect at one point in the setup - when you call glVertexAttribPointer. Otherwise it is irrelevant, and binding, or unbinding, will have no effect. The current GL_ELEMENT_ARRAY_BUFFER binding is retained as part of the VAO state and is used during calls to glDrawElements - so changing that binding while a VAO is bound does have effect.

Where it therefore might make sense to bind VAO 0 is before any operations that update buffer objects (strictly speaking only required for the GL_ELEMENT_ARRAY_BUFFER binding), so as to keep state used for updating separate from state used for drawing.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.

The buffer object simply isn't bound to the vertex array. The array doesn't know where to take the data from.

And a thing i don't understand:

glDrawElements(GL_TRIANGLES, layer->index * layer->ipo, GL_UNSIGNED_INT, 0)

How does the second argument relate to the number od elements ? Ipo is the buffer handle, right ? And may be different on different runs, depending on what was created before.

Once again as allways: those macros are tinkering, get a debug context, it'll tell you that data isn't avaibale when drawing. It will, though, not tell you that indices are not available or out of bounds,which causes the crash when calling drawelements. And grab a modern book on OpenGL (Programming Guide 9th (preferrably) or Superbible 7th) and follow the introduction. It is steep at the beginning, but nothing compared to Vulkan and the like.

OP, you are right to assume that only the vao must be bound, but the buffer with the data and the indices must be know to the vao. (bind the buffer to the vao, in 4.5 this goes with glvertexarrayvertexbuffer call). The index buffer can (and at least initially must) be bound separately via glvertexarrayelementbuffer (OpenGL 4.5). With these apis you avoid related state changes from binding and unbinding during drawing of different objects because you prepare them only once.

What's true is that less state changes (binding and unbinding) are faster than many. But that depends on the application and what you're up to. If you have one or more buffers, element or vertex data, static, dynamic or stream, with a single or seperate entry points, offsets, attributes etc. or any other state change is up to you.

Grab the documentation and go ahead :-)

Edit: maybe i should one day write a short tutorial on OpenGL 4.5 buffer handling and drawing arrays and elements ? But it costs so much time :-/

This topic is closed to new replies.

Advertisement