Vertex Array Object

From OpenGL Wiki
Revision as of 14:26, 2 September 2012 by Alfonse (talk | contribs) (Legacy)
Jump to: navigation, search
Vertex Array Object
Core in version 4.6
Core since version 3.0
Core ARB extension ARB_Vertex_Array_Object

Vertex Array Objects (VAO) are OpenGL Objects that store the set of bindings between Vertex Attributes and the user's source vertex data.

VAO State

Vertex Array Objects are subject to a degree of misunderstanding, primarily due to the way that Buffer Objects are associated with vertex attributes.

VAOs are a collection of state, like all OpenGL Objects. Unlike texture objects or Buffer Objects, VAOs are pure state objects; they do not contain any large blocks of data or anything.

If you were to define the VAO state in terms of a C structs, it would look like the following:

struct VertexAttribute
    bool bIsEnabled = GL_FALSE;
    int iSize = 4; //This is the number of elements in this attribute, 1-4.
    unsigned int iStride = 0;
    VertexAttribType eType = GL_FLOAT;
    bool bIsNormalized = GL_FALSE;
    bool bIsIntegral = GL_FALSE;
    void * pBufferObjectOffset = 0;
    BufferObject * pBufferObj = 0;

struct VertexArrayObject
    BufferObject *pElementArrayBufferObject = NULL;
    VertexAttribute attributes[GL_MAX_VERTEX_ATTRIB];

The values given in the struct, the ones to the right of the "=", represent the default state of a newly-created VAO.

Legacy Note: In contexts where the fixed-function pipeline is still available, VAOs can still be used. They simply store more attributes, namely the fixed-function attributes set up by commands like glVertexPointer, glNormalPointer and so forth. So the VAO struct in the above pseudo-code example would have additional VertexAttribute objects stored in it. Similarly, compatibility mode allows pBufferObjectOffset to be a client pointer; whether it is a client pointer or a buffer object offset depends on whether a buffer object is in pBufferObj.


So what does all of this state mean?

Let us say that you are issuing a glDrawElements or similar command while a particular VAO is bound. In our pseudo-code, let us say that the VAO binding is called pVAO. Here is what will happen, relative to our pseudo-code definition.

The pVAO->pElementArrayBufferObject is the buffer object from which vertex indices will be pulled. If this is NULL (ie: bound to zero), an error is raised: you can use glDrawArrays, but not glDrawElements without an element buffer.

If attribute index zero is not enabled (pVAO->attributes[0].bIsEnabled == GL_FALSE), an error is raised; attribute index 0 must always be enabled in a VAO you render with.

For each attribute i for which pVAO->attributes[i].bIsEnabled == GL_TRUE, the buffer object (pBufferObj) will be read starting at the offset value (pBufferObjectOffset), with the given stride (iStride) for each element and of the given number of fields and type components (iSize and eType). One value is read from all of the enabled attributes for each index from the element array buffer. The assemblage of attributes for a single index from the element array buffer constitutes a single vertex, and will be passed along for vertex processing.

A more detailed look at how rendering from VAO state works is in the article on Vertex Specification.

VAO Functions

As with all standard OpenGL Objects, when a VAO is bound to the context, all functions that modify these state fields will modify them for this object.

The VertexArrayObject::pElementArrayBufferObject is controlled by the binding of GL_ELEMENT_ARRAY_BUFFER. Calling void glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferObj) will set this field of the VAO.

The VertexAttribute::bIsEnabled field is set by glEnableVertexAttribArray(attribIndex) and glDisableVertexAttribArray(attribIndex), for the given attribute index.

The other vertex attribute state is governed by these functions:

 void glVertexAttribPointer( GLuint index, GLint size, GLenum type,
   GLboolean normalized, GLsizei stride, const void *offset);
 void glVertexAttribIPointer( GLuint index, GLint size, GLenum type,
   GLsizei stride, const void *offset );

You may notice that these functions do not take a buffer object name. This is because they attach the buffer object implicitly. Rather than having these functions take a buffer object directly, they simply use whatever buffer object is currently bound to GL_ARRAY_BUFFER. Thus, before calling either of these functions, the user should call glBindBuffer(GL_ARRAY_BUFFER, bufferObj) with the buffer of interest for this attribute.

Note: Changing the GL_ARRAY_BUFFER binding does not affect VAO state. This is different from the GL_ELEMENT_ARRAY_BUFFER binding, which is directly part of VAO state. It is only when calling one of these two functions that the GL_ARRAY_BUFFER binding matters to the VAO.

The same buffer object can be used by multiple attributes. This is perfectly legitimate and can result in faster performance. Depending on the vertex data, of course.

These functions will cause the index attribute to be modified. In terms of our pseudo-code example, the parameters are equivalent to the state as follows:

 iSize <= size
 iStride <= stride
 eType <= type
 bIsNormalized <= normalized
 pBufferObjectOffset <= offset
 pBufferObj <= current GL_ARRAY_BUFFER binding

The offset parameter is technically a pointer in glVertexAttribPointer calls. It is however interpreted as a byte-offset into the buffer object that is currently bound to GL_ARRAY_BUFFER. To call this function, you have to perform a cast operation:

glVertexAttribPointer(..., (void*)(byte_offset));

Where byte_offset is an integer.

The "IPointer" version of the function is used to create integral attributes. Thus, when you use this function, VertexAttribute::bIsIntegral is automatically set to GL_TRUE. Similarly, when you use the other function, it is set to GL_FALSE. Since integral attributes cannot be normalized (as attribute normalization makes no sense when applied to integral attributes), this function does not have a normalized field.

It is perfectly legal to call these functions before calling the enable/disable functions.

These are the only functions that affect VAO state.

Double-precision attributes

OpenGL 4.1, and implementations that support the GL_ARB_vertex_attrib_64bit extension, allow the use of double-precision vertex attributes.

All vertex attribute functions previously discussed set the bIsDouble field to GL_FALSE. The only way to specify a double-precision attribute is with this new function:

 void glVertexAttribLPointer( GLuint index, GLint size, GLenum type,
   GLsizei stride, const void *offset );

The type field must be GL_DOUBLE. The rest work as expected. Using this function sets bIsDouble to GL_TRUE. If this function is used, the user must be using a `double` or `dvec` attribute type in the vertex shader.


If you are not using shaders (or using a form of shader that doesn't accept GLSL's generic attributes), there are a number of alternate functions for setting the non-generic vertex attributes.

VAOs encapsulate the non-generic attribute data as well. glEnableClientState and glDisableClientState function like glEnableVertexAttrib and glDisableVertexAttrib, except for non-generic attributes.

Texture coordinates are access a bit differently. There is only one texture coordinate setup function, glTexCoordPointer, and it only works for one texture coordinate. You can change which coordinate this function modifies by calling glClientActiveTexture with the texture index you wish to use. This function also controls which texture gets enabled/disable with glEnableClientState and glDisableClientState.

Overall, the gl*Pointer calls take the same parameters as glVertexAttribPointer, but there are some differences. None of the non-generic vertex arrays can feed an integral attribute, so there is no equivalent to glVertexAttribIPointer. And some of these functions are missing some fields compared to glVertexAttribPointer, because these fields are assumed to be a certain value. They also usually lack an explicit "normalize" field; the values are normalized or not intrinsically.

Table 2.5 in the OpenGL 3.2 compatibility specification details what the restrictions are on these functions.

See Also