GL ARB vertex buffer object

From OpenGL Wiki
Jump to navigation Jump to search

The GL_ARB_vertex_buffer_object extension provides a mechanism for storing vertex array data in "fast" memory (video or AGP), thereby allowing for significant increases in vertex throughput between the application and the GPU. Similar functionality has since long been exposed by the GL_NV_vertex_array_range and GL_ATI_vertex_array_object extensions. GL_ARB_vertex_buffer_object supersedes these extensions with a new vendor-independent mechanism.

Inner Workings

Chunks of vertex array data are encapsulated in a vertex buffer objects (VBO). A VBO is an opaque handle. Much like a texture object or display list, you can associate data with a VBO but the actual storage location of the data is hidden from you. The API for creating a VBO is very similar to the texture object APIs. A VBO is created using glGenBuffers() and destroyed using glDeleteBuffers(). Before rendering from a VBO, it has to be made active using glBindBuffer().

Data can be written to a VBO in two ways. The first way is through the GL, using the glBufferData() or glBufferSubData() functions. These functions are conceptually similar to glTexImage*() and glTexSubImage*(). The second option is to obtain a direct pointer to the VBO data and to write the data to this memory area yourself. This technique is called "mapping a buffer". The application calls glMapBuffer() to obtain a pointer to the VBO, then writes data to it, and finishes by calling glUnmapBuffer(). When calling glMapBuffer(), you have to specify the desired access mode that you would like for the data. This can be GL_READ_ONLY, GL_WRITE_ONLY or GL_READ_WRITE. The meanings of these are self-explanatory. Obviously you should not attempt to read from a write-only mapping or vice versa.

Restrictions

It's important to note that a VBO cannot be rendered from while it is mapped. This restriction exists so that drivers may be free to move the data around as they see fit in order to provide the best performance. This also means that mapping and unmapping the same buffer twice will not necessarily give you back the same pointer. You should never store the pointer to a buffer after you've unmapped that buffer, and you should never pass it to the GL (e.g. to glVertexPointer() or related functions).

Also note that it's not possible to map a buffer until memory has been allocated for it. To do so, you have to use glBufferData() at least once. If you don't have any data available at buffer creation time, you can pass a null pointer to allocate uninitialized memory. You can then fill this memory later by mapping the buffer or by using glBufferSubData().

Allocating VBOs

When allocating VBOs, you have to specify your intended usage for them. The first thing you have to specify is what kind of data you will store in the buffer -- this can be either vertex data or element data (i.e. indices). You do this by setting the target parameter of glBufferData() to GL_ARRAY_BUFFER or GL_ELEMENT_ARRAY_BUFFER, respectively. The same target also has to be specified when binding or mapping the buffer. The second thing you have to specify is the access pattern you will use for your buffer.

Accessing VBOs

The access pattern is specified as a combination of two settings. The first is how often you intend to modify the data. The three possible settings are:

  • STATIC: You will specify the data only once, then use it many times without modifying it.
  • STREAM: You will modify the data once, then use it once, and repeat this process many times.
  • DYNAMIC: You will specify or modify the data repeatedly, and use it repeatedly after each time you do this.

The second setting indicates what the source and destination of the data will be. There are again three possibilities:

  • DRAW: The data is generated by the application and passed to the GL for rendering.
  • COPY: The data is generated by the GL, and copied into the VBO to be used for rendering.
  • READ: The data is generated by the GL, and read back by the application. It is not used by the GL.

The usage parameter of glBufferData() is a combination of these two settings, as shown in this table:

DRAW COPY READ
STATIC GL_STATIC_DRAW GL_STATIC_COPY GL_STATIC_READ
STREAM GL_STREAM_DRAW GL_STREAM_COPY GL_STREAM_READ
DYNAMIC GL_DYNAMIC_DRAW GL_DYNAMIC_COPY GL_DYNAMIC_READ

Rendering from a VBO is like rendering from a normal vertex array, and can be done using any of the relevant OpenGL functions such as glDrawArrays(), glDrawElements() or glDrawRangeElements(). There are however two important steps to take. The first is that you have to make your VBO active by calling glBindBuffer(), taking care to bind the buffer to the correct target. The second is that you have to convert your vertex array pointers (glVertexPointer() and related) from direct pointers (which you don't have in the case of a VBO) to pointers that are relative to the start of your VBO. In other words, a pointer to the first byte in the VBO would now become a null pointer.

Usage Rationale

As mentioned in the introduction, GL_ARB_vertex_buffer_object offers a vendor-independent alternative to GL_NV_vertex_array_range and GL_ATI_vertex_array_object. These extensions exist because the standard OpenGL vertex array functionality requires vertex array data to reside in system memory. This makes it hard to obtain good vertex throughput from the application to the GPU. By enabling the application to store data directly in graphics memory (video or AGP), the GPU can get much faster access to it. Display lists can serve the same purpose, but the compilation step may take too much time for applications that deal with dynamic data, and the memory overhead is unknown to the developer.

Sample Code

I suggest that that you make a VBO for your vertices (vertex, normals, texcoords, perhaps other things) and another VBO for indices (IBO). I suggest that you interleave your vertex attributes for best performance. The order of of attributes should not matter for performance because it's just a pointer to a memory location for the GPU.

Make a structure for your vertex attributes in your C++ code :

struct MyVertex
{
float x, y, z;        //Vertex
float nx, ny, nz;     //Normal
float s0, t0;         //Texcoord0
float s1, t1;         //Texcoord1
float s2, t2;         //Texcoord2
float padding[4];
};

The structure's size is 64 bytes. ATI suggests that you align your vertex size for 32 bytes, so I added 4 floats to make it 2 * 32 bytes. For nVidia, I think it doesn't matter

Generate a VBO for vertices. Make it STATIC, meaning we will never change it. It will be stored in VRAM:

glGenBuffers(1, VertexVBOID);

GL_ARRAY_BUFFER is for vertices:

glBindBuffer(GL_ARRAY_BUFFER, VertexVBOID);

The parameter SizeInBytes is how big you want your VBO to be, don't make it too small and don't make it too large. For good performance, allocate something like 1MB or 2MB or 4MB or 8MB and put many objects to be rendered into it. Notice that the pointer is NULL, only memory will be reserved. You can upload your vertices right now if you wish.

glBufferData(GL_ARRAY_BUFFER, SizeInBytes, NULL, GL_STATIC_DRAW);

Let's assume you used NULL; You can use to upload all your vertices to the VBO or just upload some vertices. This is nice if you want to update only some of the vertices, but right not, we keep the example simple with lots of comments.

glBufferSubData(GL_ARRAY_BUFFER, offsetInByte, SizeInBytes, &pvertices);

Now it's time to make a VBO for the indices. The same API for vertices is used but instead of calling it a VBO, we can call it a index buffer obejct - IBO.

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IndexVBOID);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, SizeInBytes, NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offsetInByte, SizeInBytes, &pindices);

For best performance, they suggest that you used 16 bit unsigned integers (short) 32 bit unsigned integer is the other option. Don't use anything ridiculous like unsigned byte.

Now it's time to render. I have not included the part about setting up texture combiners, or shader, or binding shaders.

Make the following definition in your C++ code:

define BUFFER_OFFSET(i) ((char *)NULL + (i))

Bind your VBO. P.S., Our VBO is already bound actually.

glBindBuffer(GL_ARRAY_BUFFER, VertexVBOID);

Call your gl***Pointer functions to make a INTERLACED ARRAY. The pointers are relative to the zero point of the VBO.

glVertexPointer(3, GL_FLOAT, 64, BUFFER_OFFSET(0));
glNormalPointer(GL_FLOAT, 64, BUFFER_OFFSET(12));
glTexCoordPointer(2, GL_FLOAT, 64, BUFFER_OFFSET(24));
glClientActiveTexture(GL_TEXTURE1);
glTexCoordPointer(2, GL_FLOAT, 64, BUFFER_OFFSET(32));
glClientActiveTexture(GL_TEXTURE2);
glTexCoordPointer(2, GL_FLOAT, 64, BUFFER_OFFSET(40));

Bind your IBO. P.S., Our IBO is already bound actually.

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IndexVBOID);

Draw and pass the pointer relative to the zero point of the IBO.

glDrawRangeElements(GL_TRIANGLES, x, y, z, GL_UNSIGNED_SHORT, BUFFER_OFFSET(0));

In summary:

  • Make you vertex structure.
  • Make it multiple of 32 bytes in size.
  • Make a VBO.
  • Make an IBO.
  • Use 16 bit integer.
  • Try not to make many redundent calls to GL so that your performance stays the best.
  • Don't use glInterleaved array because almost nobody uses it and it's limited.

I haven't talked about glMapBuffer and glUnmapBuffer, but it's easy to use

Tips and tricks

  • Mapping a buffer may cause your data to be lost.
  • Array pointers are only treated as offsets if a buffer object is bound. Binding 0 returns to normal behaviour (i.e. direct pointers).
  • Multiple buffers can be mapped simultaneously.
  • All vertex attributes do not necessarily need to be in the same buffer object.

References