You just read about vertex
buffer objects. Each vertex attribute has an offset within a buffer and a
set of other state such as data type and stride. Each one also has an
associated buffer, which can be different for each attribute. Calling glVertexAttribPointer
sets all of this state, including the buffer binding for the attribute.
If you have a fairly complex scene with several objects in it and each
object keeps its vertex data in its own VBO, then that is a reasonable
amount of state per object. If the application is well-written, drawing
one of these object may end up as simple as a single call to a function
like glDrawElements or glDrawArrays.
Even if the layout of data is
the same between objects (it probably will be for many applications)
and the offsets of the data are the same (maybe all data starts at
offset zero, for example), it is still necessary to call glVertexAttribPointer for every vertex attribute. For an object that has, say, eight vertex attributes, this means at least one call to glBindBuffer (possibly up to eight if all the vertex attributes are in separate buffer objects), and eight calls to glVertexAttribPointer. If you’re using indexed vertices, you also need to bind your GL_ELEMENT_ARRAY_BUFFER. All this to prepare for a single call to glDrawElements.
This is a lot of state to set, a lot of error checking that the driver
has to do, and a lot of information that the application has to look
after.
To help organize all
this information, OpenGL provides an object called a vertex array object
(VAO). A VAO is a container that packages together all of the state
that can be set by glVertexAttribPointer and a few other functions. When using a VAO, all state specified through a call to glVertexAttribPointer
is stored in the current VAO. There is no default VAO in OpenGL. This
means that before you can even specify your vertex pointers, you need to
create and bind a VAO. For simple applications, it may be sufficient to
create a single VAO, bind it, and leave it bound for the lifetime of
the application (as we did when we introduced VBOs earlier). However, an
application can create as many VAOs as it needs and use them to manage
all of the array state. When it’s time to draw using a particular set of
vertex attributes, simply bind the VAO containing that set of state and
start drawing. This allows each object in a scene to manage its own
vertex buffers by creating a VAO to maintain its state and binding it
before drawing. That way, the object won’t upset the vertex array state
of any other object in the scene.
To create one or more VAOs, call
void glGenVertexArrays(GLsizei n, GLuint *arrays);
Like most other OpenGL objects, VAOs are referred to by name represented as unsigned integers. The glGenVertexArrays function creates n vertex arrays and places their names in the array arrays. If glGenVertexArrays
fails to allocate a VAO for some reason, it returns zero for its name. A
well-written application should always check for this condition before
trying to use the result. Like buffer objects, the VAO name zero is
reserved by OpenGL to mean “no VAO.” Again, when no VAO is bound, glVertexAttribPointer will not work and will generate an error if you call it. To delete VAOs, call
void glDeleteVertexArrays(GLsizei n, GLuint *arrays);
This function deletes the n VAOs whose names are in arrays. It is important for your application to clean up after itself. If arrays
has an element containing the name zero, that will be ignored. This
means that you can safely pass an array previously written to by glGenVertexArrays to glDeleteVertexArrays without worrying whether some of the names might be zero (due to an error during the execution of glGenVertexArrays, for example). To start using a VAO, call
void glBindVertexArray(GLuint array);
This makes array the
current VAO. When a new VAO is bound for the first time, it contains
all of the default state that would be present in a freshly created
context. From now on, any time you call a function that accesses the
vertex array state, it will access the state contained in the currently
bound VAO. This includes functions that set state, such as glVertexAttribPointer; functions that implicitly use that state, such as glDrawArrays or glDrawElements; and functions that explicitly read vertex array state, such as glGetIntegerv.
Now that we have a VAO, we can set as much state on it as we like. We can call glVertexAttribPointer as many times as we need and the state will be stored in the VAO. If we call glBindBuffer followed by glVertexAttribPointer,
the buffer binding will also be stored in the VAO. Note, though, that
while the buffer binding associated with the vertex attribute is stored
in the VAO, binding a new VAO does not change the current buffer
bindings. That is, the actual state of the currently bound buffers is
not stored in the VAO. To return to the example at the start of this
section—the object with many vertex attributes, each with different
state and buffer bindings—we can improve the performance of this greatly
using VAOs.
Instead of calling glBindBuffer and glVertexAttribPointer
many times right before drawing the object, we can do it at
initialization time. When it is created, the object can generate a VAO,
bind it using glBindVertexArray, and set
all of its vertex array state as if it were about to render itself.
After initialization, return OpenGL to having no VAO bound by calling
Now, when the object is about to be rendered, call glBindVertexArray again with the object’s VAO, and then call the rendering functions such as glDrawArrays.
Thus, rendering a complete object that has many vertex attributes, all
stored in a collection of VBOs with different parameters, can be as
simple as two function calls—glBindVertexArray and glDrawElements,
for example. This is also beneficial for layered libraries, scene graph
managers, and middleware that might want to render without disturbing
the current OpenGL state. If the normal behavior of the environment is
to have no VAO bound, then each object binds its own VAO, renders
itself, and then binds VAO zero, resetting everything.