Vertex Arrays vs Vertex Array Objects vs Buffer Objects

I have downloaded the OpenGL specs for versions 2.0, 2.1, 3.0, and 3.2core. In the version 2.0 and 2.1 specs I see in the table of contents sections for Vertex Arrays and Buffer Objects (sections 2.8 and 2.9 of these specs). In the version 3.0 and 3.2core specs I see in the table of contents sections for Vertex Arrays, Buffer Objects, and Vertex Array Objects (sections 2.8, 2.9, and 2.10).

According to the 3.2core spec appendix, Vertex Array Objects are based on an Apple extension: APPLE_vertex_array_object. The information on the APPLE_vertex_array_object is at This document states the following:

“This extension introduces named vertex array objects which encapsulate vertex array state on the client side. The main purpose of these objects is to keep pointers to static vertex data and provide a name for different sets of static vertex data.”

This statement leads me to believe that VAOs are used to maintain state for Vertex Arrays. Vertex Arrays seem to be very different from Buffer Objects. For one, Vertex Arrays seem to be client side while Buffer Objects are server side. Much of what I have read about VAOs uses them in conjuction with Buffer Objects (e.g. Vertex Buffer Objects). I have not seen VAOs used in the context of Vertex Arrays.

I find the documentation on Vertex Array Objects extremely confusing. I get that they maintain state. However, I am not sure if it is state for Vertex Arrays or for Buffer Objects or for both. Can anyone enlighten me?


Think of it this way: before you can glDrawElements() or some GL draw call, you have to set up the bindings for where the vertex attributes come from and which ones are enabled. Vertex Attribute Objects (VAOs) capture those bindings in a single object. Things like glVertexAttribPointer, etc.

Those bindings can come from vertex arrays (app-side buffers), from vertex buffer objects aka VBOs (driver-side buffers), or wherever. Makes no difference. VAOs capture those bindings. They’re basically just a shortcut, think of it as a script, that when replayed (bound) saves you from having to replay that bind logic yourself explicitly with a number of GL calls (in practice it’s just a block of cached data).