VAO and element array buffer state
Asked Answered
S

5

24

I was recently writing some OpenGL 3.3 code with Vertex Array Objects (VAO) and tested it later on Intel graphics adapter where I found, to my disappointment, that element array buffer binding is evidently not part of VAO state, as calling:

glBindVertexArray(my_vao);
glDrawElements(GL_TRIANGLE_STRIP, count, GL_UNSIGNED_INTEGER, 0);

had no effect, while:

glBindVertexArray(my_vao);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, my_index_buffer); // ?
glDrawElements(GL_TRIANGLE_STRIP, count, GL_UNSIGNED_INTEGER, 0);

rendered the geometry. I thought it was a mere bug in Intel implementation of OpenGL (because it is clearly stated in GL_ARB_vertex_array_object (and even in GL_OES_vertex_array_object) that element array is part of the saved state), but then it occured on mobile NVIDIA Quadro 4200. That's no fun.

Is it a driver bug, a specs bug, or a bug somewhere in my code? The code works flawlessly on GeForce 260 and 480.

Anyone had similar experience?

What is also strange is that GL_EXT_direct_state_access does not have a function to bind an element array buffer to VAO (but it does have functions to specify vertex attrib arrays, and hence array buffers). Are the GPU manufacturers screwing the specs and cheating on us, or what?

EDIT:

I originally didn't intend to show any source code because I believed it was not necessary here. But as requested, here is the minimal test case that reproduces the problem:

static GLuint n_vertex_buffer_object, p_index_buffer_object_list[3];
static GLuint p_vao[2];

bool InitGLObjects()
{
    const float p_quad_verts_colors[] = {
        1, 0, 0, -1, 1, 0,
        1, 0, 0, 1, 1, 0,
        1, 0, 0, 1, -1, 0,
        1, 0, 0, -1, -1, 0, // red quad
        0, 0, 1, -1, 1, 0,
        0, 0, 1, 1, 1, 0,
        0, 0, 1, 1, -1, 0,
        0, 0, 1, -1, -1, 0, // blue quad
        0, 0, 0, -1, 1, 0,
        0, 0, 0, 1, 1, 0,
        0, 0, 0, 1, -1, 0,
        0, 0, 0, -1, -1, 0 // black quad
    };
    const unsigned int p_quad_indices[][6] = {
        {0, 1, 2, 0, 2, 3},
        {4, 5, 6, 4, 6, 7},
        {8, 9, 10, 8, 10, 11}
    };
    glGenBuffers(1, &n_vertex_buffer_object);
    glBindBuffer(GL_ARRAY_BUFFER, n_vertex_buffer_object);
    glBufferData(GL_ARRAY_BUFFER, sizeof(p_quad_verts_colors), p_quad_verts_colors, GL_STATIC_DRAW);
    glGenBuffers(3, p_index_buffer_object_list);
    for(int n = 0; n < 3; ++ n) {
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, p_index_buffer_object_list[n]);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(p_quad_indices[n]), p_quad_indices[n], GL_STATIC_DRAW);
    }

    glGenVertexArrays(2, p_vao);
    glBindVertexArray(p_vao[0]);
    {
        glBindBuffer(GL_ARRAY_BUFFER, n_vertex_buffer_object);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), p_OffsetInVBO(0));
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), p_OffsetInVBO(3 * sizeof(float)));
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, p_index_buffer_object_list[0]); // red
    }
    glBindVertexArray(0);

    glBindVertexArray(p_vao[1]);
    {
        glBindBuffer(GL_ARRAY_BUFFER, n_vertex_buffer_object);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), p_OffsetInVBO(0));
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), p_OffsetInVBO(3 * sizeof(float)));
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, p_index_buffer_object_list[1]); // blue
    }
    glBindVertexArray(0);

#ifdef BIND_BLACK_QUAD_ELEMENT_ARRAY_BUFFER
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, p_index_buffer_object_list[2]);
    // bind the buffer with the black quad (not inside VAO, should NOT be seen)
#endif // BIND_BLACK_QUAD_ELEMENT_ARRAY_BUFFER

    // [compile shaders here]

    return true; // success
}

The above code creates a vertex buffer containing three quads, red one, blue one and black one. Then it creates three index buffers that point to the individual quads. Then two VAOs are created and set up, one should contain red quad indices and the other should contain blue quad indices. The black quad should not be rendered at all (assume BIND_BLACK_QUAD_ELEMENT_ARRAY_BUFFER is defined).

void onDraw()
{
    glClearColor(.5f, .5f, .5f, 0);
    glClear(GL_COLOR_BUFFER_BIT);
    glDisable(GL_DEPTH_TEST);

    glUseProgram(n_program_object);

    static int n_last_color = -1;
    int n_color = (clock() / 2000) % 2;
    if(n_last_color != n_color) {
        printf("now drawing %s quad\n", (n_color)? "blue" : "red");
        n_last_color = n_color;
    }

    glBindVertexArray(p_vao[n_color]);
#ifdef VAO_DOESNT_STORE_ELEMENT_ARRAY_BUFFER
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, p_index_buffer_object_list[n_color]); // fixes the problem
#endif // VAO_DOESNT_STORE_ELEMENT_ARRAY_BUFFER
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
    glBindVertexArray(0);
}

This clears the viewport to gray and renders either blue or red quad in repeating manner (it also prints which one). While this works on desktop GPU, it doesn't work on notebook GPU (black quad is rendered unless the VAO_DOESNT_STORE_ELEMENT_ARRAY_BUFFER macro is defined. Undefining the BIND_BLACK_QUAD_ELEMENT_ARRAY_BUFFER macro makes the quad blue, as the blue index buffer is bound last. But it doesn't render the red quad no matter what.

So the way I see it, it's either a fatal misconception in my understanding of how should VAO work, a bug in my code, or a driver bug.

Full source
Binaries (windows, 32 bit)

Spandrel answered 23/1, 2012 at 15:4 Comment(3)
It's more likely that your code isn't putting the element buffer in the VAO initially. Why don't you show us your VAO initialization code.Pincers
Oh come on, not that stupid. Plus I said it worked on GeForce 260 / 480. Read the posts before writing comments. I'm fully capable of debugging my OpenGL code. This question is about differences between OpenGL implementations and compatibility.Spandrel
Just because code works doesn't mean it is correct. Whether through fortuitous circumstance or whatever, code can manage to work. The fact that it both fails and succeeds on NVIDIA drivers suggests user error. If it worked on NVIDIA and failed on ATI, or vice-versa, it would be more likely to be a driver bug. But NVIDIA especially is pretty self-similar. So if it sometimes works on some NVIDIA hardware and sometimes doesn't, that sounds like user error.Pincers
S
26

After some time, I found out this was actually kind of my bad. The laptop with the mobile NVIDIA Quadro 4200 graphics card was set so that all the apps would run on the Intel graphics by default, even when the laptop was in the performance mode. I don't understand why would someone want to do that, as then there was no way for any application to use the more powerful GPU from OpenGL (it was still possible to use it for OpenCL as there is explicit device selection, also maybe for DirectX - that would explain why some games ran smoothly).

Nevertheless, the described errorneous behavior is just a bug in Intel drivers, that's all there's to it. Intel drivers do not save ELEMENT_ARRAY_BUFFER_BINDING. There.

I'm sincerely sorry for asking the question, as there was no way to give a good answer without knowing the above.

Spandrel answered 29/6, 2012 at 12:37 Comment(3)
Did you file a bug with Intel? Any follow up on this story? Knowing such subtle issues exist in the world deeply trouble me as a novice graphics programmer.Anomalistic
@Philip I have not considered that, I barely consider Intel a usable GPU so for me it was easy to say that I will not support Intel GPUs. But maybe that is a good idea. If you do it, please post a link to the bug tracker in a comment. Thanks.Spandrel
Even if it wasn't helpful to you, having a driver bug documented on SO was helpful to me.Kossuth
P
20

I actually believe that ARB VAO is missing the element array buffer binding (or any other buffer binding) state.

Belief is not required; the spec tells the facts.

From the ARB_vertex_array_object specification:

The command

void GenVertexArrays(sizei n, uint *arrays);

returns previous unused vertex array object names in . These names are marked as used, for the purposes of GenVertexArrays only, and are initialized with the state listed in tables 6.6 (except for the CLIENT_ACTIVE_TEXTURE selector state), 6.7 and 6.8 (except for the ARRAY_BUFFER_BINDING state).

So there we have it: the entire state encompassed by VAOs are the contents of those three tables, with the noted exceptions.

The extension is written against The OpenGL Graphics Specification version 2.1 (PDF). Therefore, any page numbers, section labels, or table numbers are referenced relative to that spec.

I'm not about to copy those three tables here. But if you look on page 273 (by the spec's page count)/page 287 (by the number of physical pages), you will find table 6.8. And on that table is the following:

  • ELEMENT_ARRAY_BUFFER_BINDING

There is no ambiguity here. The information may not be plainly stated. But it is there, unquestionably. The ELEMENT_ARRAY_BUFFER_BINDING is part of VAO state.

Therefore, your problem can come from one of two sources:

  1. Driver bug. As I stated in a comment, a driver bug seems unlikely. Not impossible, just unlikely. NVIDIA's drivers are pretty self-similar for different hardware, and VAOs are hardly mirrored in hardware. Unless you are using different versions of the drivers, there's little reason to expect an error to be due to a driver bug.

  2. User error. I know you claim that your code works, and therefore it's fine. Everyone makes that claim about some code. And there have been plenty of times when I would swear up and down that some code was working just fine. Yet it was broken; it just so happened to get by. It happens. If you post your code, then at least we would be able to discount this possibility. Otherwise, we have nothing more than your word. And considering how often human beings are wrong about that, that isn't worth much.

Pincers answered 6/2, 2012 at 9:6 Comment(2)
Fair enough. I can't post complete code but I'll try to come up with minimal code sufficient to reproduce the error. Also the ARB thing is solid, the state really seems to be there.Spandrel
and here comes the source code (edit in the original question). So is it a driver bug or my fault?Spandrel
O
3

What is also strange is that GL_EXT_direct_state_access does not have a function to bind an element array buffer to VAO

For anyone stumbling upon this in the present and targeting at least OpenGL 4.5, there IS now a function to directly bind an element array buffer to a VAO without binding the VAO: glVertexArrayElementBuffer, and using it will probably solve whatever problem made you dig up this question.

Odontology answered 25/11, 2021 at 21:24 Comment(0)
E
0

A likely cause for this is that your Intel adapter cannot provide an OpenGL 3.3 context, but instead defaults to 2.1 or the like. As others have pointed out, in earlier versions of OpenGL the element array buffer state was not part of a VAO.

Era answered 27/6, 2012 at 9:45 Comment(3)
That is actually not possible, since I'm creating the context as forward-compatible, so it can't default to a lower one. Or if it does, it definitely shouldn't.Spandrel
I thought there was a glGet call you can run to query the context version. To find out.Basketball
@StevenLu yes, there is glGetString(GL_VERSION) but that would only mean that one bug (element array not being cached) was just caused by a much worse bug (creating an old 2.1 context instead of a forward-compatible one).Spandrel
S
-1

I can imagine the ELEMENT buffer not being cached; if you do:

glBindBuffer(GL_ARRAY_BUFFER, n_vertex_buffer_object);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), p_OffsetInVBO(0));

It is like saying:

gBoundBuffer_GL_ARRAY_BUFFER=n_vertex_buffer_object;
currentVAO->enable|=(1<<0);
currentVAO->vertexBuffer=IndexToPointer(gBoundBuffer_GL_ARRAY_BUFFER);

In other words, glBindBuffer() doesn't do anything but set the value of GL_ARRAY_BUFFER. It is at glVertexAttribPointer() where you modify the VAO.

So when you do:

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, p_index_buffer_object_list[0]);
glBindVertexArray(0);

You really do:

gBoundBuffer_GL_ELEMENT_ARRAY_BUFFER=p_index_buffer_object_list[0];
currentVAO=0;

Where it makes sense that the GL_ELEMENT_ARRAY_BUFFER binding is not doing anything. I'm not sure if there is a glVertexAttribPointer()-like variant for elements though (like glElementPointer()), which would actually act on the VAO.

Shagreen answered 23/9, 2013 at 9:55 Comment(1)
Well, the behavior that you describe (element array not cached) is actually against the specification, and it makes little sense to do it that way. When you want to draw some geometry, there are only rare cases when vertices and indices are separable. Mostly, the models are loaded from artist-created files, and indices of one model would not fit vertices from any other model. So it actually makes perfect sense to bind index buffer and vertex attrib pointers at the same time, at least in "graphics application, displaying 3D meshes" scenario.Spandrel

© 2022 - 2024 — McMap. All rights reserved.