OpenGL VAO best practices
Asked Answered
D

4

85

Im facing an issue which I believe to be VAO-dependant, but Im not sure..

I am not sure about the correct usage of a VAO, what I used to do during GL initialization was a simple

glGenVertexArrays(1,&vao)

followed by a

glBindVertexArray(vao)

and later, in my drawing pipeline, I just called glBindBuffer(), glVertexAttribPointer(), glEnableVertexAttribArray() and so on.. without caring about the initally bound VAO

is this a correct practice?

Devoice answered 19/1, 2012 at 8:42 Comment(2)
I am not sure why this question got a -1.. it doesnt show any effort and.or is unclear? I spent 3 days struggling with the specs, wikis and forums before posting this and didnt get any real reason I should use VAO.. meh..Devoice
Because for the cool bro's it's all obvious as soon as they have their cut-and-pastes handy, why bother with understanding stuff??Undershirt
X
106

VAOs act similarly to VBOs and textures with regard to how they are bound. Having a single VAO bound for the entire length of your program will yield no performance benefits because you might as well just be rendering without VAOs at all. In fact it may be slower depending on how the implementation intercepts vertex attribute settings as they're being drawn.

The point of a VAO is to run all the methods necessary to draw an object once during initialization and cut out all the extra method call overhead during the main loop. The point is to have multiple VAOs and switch between them when drawing.

In terms of best practice, here's how you should organize your code:

initialization:
    for each batch
        generate, store, and bind a VAO
        bind all the buffers needed for a draw call
        unbind the VAO

main loop/whenever you render:
    for each batch
        bind VAO
        glDrawArrays(...); or glDrawElements(...); etc.
    unbind VAO

This avoids the mess of binding/unbinding buffers and passing all the settings for each vertex attribute and replaces it with just a single method call, binding a VAO.

Xylol answered 19/1, 2012 at 8:53 Comment(8)
ah yes, infact I didnt use VAO at all before.. until GL specs began complaining an error might (and will be) thrown if no VAO is bound.. Thank youDevoice
is it really necessary to unbind the VAO? Ive found different practices aroundDevoice
It's not necessary, but I usually do it in case some other object decides to render without VAOs (debug drawing/font rendering library/etc.), as that would (as far as I know) replace the attribute settings for the currently bound VAO.Xylol
Why should the object be drawn once during initialization? Is that drawing another glDrawArrays() call?Phosphatase
As with another answer I wrote around the same time, I was under the impression that a draw call was required, when in fact all that is necessary is binding all the buffers. Fixed the answer.Xylol
Why is the unbind occurring only once and outside the loop? Shouldn't the unbind happen on a per-batch basis?Untie
Binding a new VAO replaces the old VAO. Unbinding inside the loop would just be extra work.Xylol
@RobertRouhani Is there any benefit to that last unbind VAO, then? (Does not unbinding affect anything?)Williamwilliams
R
30

No, that's not how you use VAO. You should use VAO in same way how you are using VBO or textures, or shaders. First set it up. And during rendering only Bind them, without modifying it.

So with VAO you do following:

void Setup() {
    glGenVertexArrays(..);
    glBindVertexArray(..);
    // now setup all your VertexAttribPointers that will be bound to this VAO
   glBindBuffer(..);
   glVertexAttribPointer(..);
   glEnableVertexAttribArray(..);
}

void Render() {
    glBindVertexArray(vao);
    // that's it, now call one of glDraw... functions
    // no need to set up vertex attrib pointers and buffers!
    glDrawXYZ(..)
}

See also these links:

Reasoned answered 19/1, 2012 at 8:53 Comment(2)
glEnableVertexAttribArray(...) should be called before glVertexAttribPointer(...). Some drivers (including mine) really doesn't like it the other way round.Cacie
Upvoted because most of the examples aren't clear that you HAVE to call glEnableVertexAttribArray during the VAO binding.Inger
N
11

is this a correct practice?

Yes, this is perfectly legal and valid. Is it good? Well...

There has been some informal performance testing on this sort of thing. And it seems, at least on NVIDIA hardware where this was tested, the "proper" use of VAOs (ie: what everyone else advocated) actually is slower in many cases. This is especially true if changing VAOs does not change which buffers are bound.

No similar performance testing has taken place on AMD hardware, to my knowledge. In general, unless something changes with them, this is an acceptable use of VAOs.

Nabonidus answered 19/1, 2012 at 14:39 Comment(2)
Surely once each VAO has enough various state that it tracks, that switching the buffers and attrib ptrs manually during the render loop would require many calls, that we would start to see improved performance from the use of the VAO?Iowa
Unfortunately, the link is dead.Aparejo
C
3

Robert's answer above worked for me when I tried it. For what it's worth here is the code, in Go, of using multiple Vertex Attribute Objects:

// VAO 1

vao1 := gl.GenVertexArray()
vao1.Bind()

vbo1 := gl.GenBuffer()
vbo1.Bind(gl.ARRAY_BUFFER)

verticies1 := []float32{0, 0, 0, 0, 1, 0, 1, 1, 0}
gl.BufferData(gl.ARRAY_BUFFER, len(verticies1)*4, verticies1, gl.STATIC_DRAW)

pa1 := program.GetAttribLocation("position")
pa1.AttribPointer(3, gl.FLOAT, false, 0, nil)
pa1.EnableArray()
defer pa1.DisableArray()

vao1.Unbind()

// VAO 2

vao2 := gl.GenVertexArray()
vao2.Bind()

vbo2 := gl.GenBuffer()
vbo2.Bind(gl.ARRAY_BUFFER)

verticies2 := []float32{-1, -1, 0, -1, 0, 0, 0, 0, 0}
gl.BufferData(gl.ARRAY_BUFFER, len(verticies2)*4, verticies2, gl.STATIC_DRAW)

pa2 := program.GetAttribLocation("position")
pa2.AttribPointer(3, gl.FLOAT, false, 0, nil)
pa2.EnableArray()
defer pa2.DisableArray()

vao2.Unbind()

Then in your main loop you can use them as such:

for !window.ShouldClose() {
    gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

    vao1.Bind()
    gl.DrawArrays(gl.TRIANGLES, 0, 3)
    vao1.Unbind()

    vao2.Bind()
    gl.DrawArrays(gl.TRIANGLES, 0, 3)
    vao2.Unbind()

    window.SwapBuffers()
    glfw.PollEvents()

    if window.GetKey(glfw.KeyEscape) == glfw.Press {
        window.SetShouldClose(true)
    }
}

If you want to see the full source, it is available as a Gist and derived from the examples in go-gl:

https://gist.github.com/mdmarek/0f73890ae2547cdba3a7

Thanks everyone for the original answers, I had the same question as ECrownofFire.

Cuneate answered 24/10, 2014 at 12:28 Comment(1)
You don't need to unbind vao1 before binding vao2, simply binding vao2 will suffice.Overgrowth

© 2022 - 2024 — McMap. All rights reserved.