Using Vertex Array Objects

Amy Pond <3

With my recent post about what iOS devices supported what extensions, I noticed a lot of people got to my website trying to work out how to use Vertex Array Objects (VAO) with the GL_OES_vertex_array_object extension.

They’re easy to use so I thought I would write up a quick how-to use them. There is a limitation with the VAO however, it cannot be shared between contexts and can be problematic for multithreading.

If you plan to use one, either generate and create for each context you will be using it on. Or generate and create on the context you will be mostly using it on. It might be a mistake, since it says they didn’t want to create the first non-shared named object in OpenGL ES, however at the same time they say no to sharing.

Like with any OpenGL object, first a name must be generated for the object, make sure you always initialise your name variables to 0, since this refers to a non-object.

GLuint gVAO = 0;
 
//
//
int main()
{
	//
	GenerateVAO();
	CreateVAO();
 
	//
	do
	{
		BindVAO();
		DrawSomething();
		UnbindVAO();
	}
	while( 1 );
 
	//
	DestroyVAO();
}
 
//
//
void GenerateVAO()
{
	// Generate Name for VAO
	glGenVertexArraysOES( 1, &gVAO );
}
 
//
//
void DestroyVAO()
{
	if( gVAO )
	{
		glDeleteVertexArraysOES( 1, &gVAO );
		gVAO = 0;
	}
}

I’ve also put the syntax in there for deleting the VAO when you do not need it anymore.

Now for filling in the VAO. The VAO is basically a block of memory for storing vertex data schematic. For fixed function, the schematic includes information about data type, count, stride, buffer offset and whether it is enabled. This is for every pointer, glVertexPointer, glNormalPointer, glColorPointer, glTexCoordPointer, etc.

And for the shader program pipeline, it contains the schematic for each glVertexAttribPointer.

//
//
void CreateVAO()
{
	// Bind VAO
	glBindVertexArrayOES( gVAO );
 
	// Bind Vertex Buffer
	glBindBuffer( GL_ARRAY_BUFFER, gSomeVBO );
 
	// Fill in the VAO with our vertex schematic
	if( hasShaders )
	{
		glEnableVertexAttribArray( 0 );
 
		glVertexAttribPointer( 0,			// Attribute bind location
				       3,			// Data type count
				       GL_FLOAT,		// Data type
				       GL_FALSE,		// Normalise this data types?
				       sizeof(float) * 3,	// Stride to the next vertex
				       0 );			// Vertex Buffer starting offset
	}
	else
	{
		glEnableClientState( GL_VERTEX_ARRAY );
 
		glVertexPointer( 3,			// Data type count
				 GL_FLOAT,		// Data type
				 sizeof(float) * 3,	// Stride to the next vertex
				 0 );			// Vertex Buffer starting offset
	}
 
	// Unbind VAO
	glBindVertexArrayOES( 0 );
}

 

You’ll notice that when I am finished with with the VAO, I unbind it. This is because any changes to the vertex pointers will override any we have set within this function, so to protect the VAO we have just filled in, we unbind it.

When using a VAO, you will not need to bind the GL_ARRAY_BUFFER again the next time you use the VAO, since whenever you make a call to gl*Pointer() or glVertexAttribPointer(), the vertex buffer is stored with the vertex schematic. Because of this, you can have multiple streams of data from different vertex buffers.

//
//
void CreateVAO()
{
	// Bind VAO
	glBindVertexArrayOES( gVAO );
 
	// Vertex Buffer 1
	{
		// Bind Vertex Buffer
		glBindBuffer( GL_ARRAY_BUFFER, gSomeVBO1 );
 
		// Fill in the VAO with our vertex schematic
		if( hasShaders )
		{
			//
			glEnableVertexAttribArray( 0 );
 
			glVertexAttribPointer( 0,			// Attribute bind location
					       3,			// Data type count
					       GL_FLOAT,		// Data type
					       GL_FALSE,		// Normalise this data types?
					       sizeof(float) * 5,	// Stride to the next vertex
					       0 );			// Vertex Buffer starting offset
 
			//
			glEnableVertexAttribArray( 1 );
 
			glVertexAttribPointer( 1,			// Attribute bind location
					       2,			// Data type count
					       GL_FLOAT,		// Data type
					       GL_FALSE,		// Normalise this data types?
					       sizeof(float) * 5,	// Stride to the next vertex
					       sizeof(float) * 3 );	// Vertex Buffer starting offset
		}
		else
		{
			//
			glEnableClientState( GL_VERTEX_ARRAY );
 
			glVertexPointer( 3,			// Data type count
					 GL_FLOAT,		// Data type
					 sizeof(float) * 5,	// Stride to the next vertex
					 0 );			// Vertex Buffer starting offset
 
			//
			glClientActiveTexture( GL_TEXTURE0 );
			glEnableClientState( GL_TEXTURE_COORD_ARRAY );
 
			glTexCoordPointer( 2,						// Data type count
					   GL_FLOAT,					// Data type
					   sizeof(float) * 5,				// Stride to the next vertex
					   (const GLvoid *)(sizeof(float) * 3) );	// Vertex Buffer starting offset
		}
	}
 
	// Vertex Buffer 2
	{
		// Bind Vertex Buffer
		glBindBuffer( GL_ARRAY_BUFFER, gSomeVBO2 );
 
		// Fill in the VAO with our vertex schematic
		if( hasShaders )
		{
			glEnableVertexAttribArray( 2 );
 
			glVertexAttribPointer( 2,			// Attribute bind location
					       2,			// Data type count
					       GL_FLOAT,		// Data type
					       GL_FALSE,		// Normalise this data types?
					       sizeof(float) * 2,	// Stride to the next vertex
					       0 );			// Vertex Buffer starting offset
		}
		else
		{
			glClientActiveTexture( GL_TEXTURE1 );
			glEnableClientState( GL_TEXTURE_COORD_ARRAY );
 
			glTexCoordPointer( 2,			// Data type count
					   GL_FLOAT,		// Data type
					   sizeof(float) * 2,	// Stride to the next vertex
					   0 );			// Vertex Buffer starting offset
		}
	}
 
	// Unbind VAO
	glBindVertexArrayOES( 0 );
}

 

In the code above, the first texture coordinate set is interlaced with the position data (offset by the sizeof three floats), and is bound using the first vertex buffer (gSomeVBO1). But the second texture coordinate is contained within a second vertex buffer (gSomeVBO2).

Finally, using the VAO is easy.

//
//
void BindVAO()
{
	glBindVertexArrayOES( gVAO );
}
 
//
//
void UnbindVAO()
{
	glBindVertexArrayOES( 0 );
}

The call to glBindVertexArrayOES() with the VAO object name is just like calling everything we did in the create function. The only limitation is that one VAO is needed per vertex buffer, even if you have the same vertex schematic.

However, this is a great place for optimisation by sharing the same vertex buffer for vertex data with the same schematic. Then whenever you make a call to glDrawArrays() you can specify which vertex to start from. glDrawElements() is a little more complicated, you will need to increase all your elements in your index buffer so they point to the correct position within the shared buffer.

Or alternatively for glDrawElements(), you could share the same index buffer, and just specify the index buffer offset.