io7m | single-page | multi-page | archive (zip, signature)
4. Vertex ArraysA Brief History Of Vertex Specification In OpenGL 6. Modern Vertex Buffer Objects
PreviousUpNext

Vertex Buffer Objects
Whilst vertex arrays removed the large number of function calls that were required per vertex under immediate mode, the problem remained that large amounts of vertex data had to be sent from the program to the OpenGL implementation each frame. If this data consisted entirely of static models (as is the case for many 3D applications), removing this repeated transmission of data would increase efficiency by orders of magnitude. The API already had functions that facilitated one-time bulk uploads of data: The glTexImage family, promoted from the EXT_texture_object extension in OpenGL 1.1. The programmer performs the following steps in order to upload a texture to OpenGL:
  1. Obtain a new texture name/index with glGenTextures().
  2. "Bind" the resulting name with glBindTexture(), making it the current texture.
  3. Supply texture data as an array of bytes (or other formats) with glTexImage().
Later in the program, when actually drawing primitives, the programmer once again calls glBindTexture() to select a texture and then supplies texture coordinates to draw textured polygons. Note that at this point, the actual texture bitmap data could have been discarded by the program and may only exist in the OpenGL implementation's memory (most likely hardware texture memory).
In OpenGL 1.5, Vertex Buffer Objects (VBOs) were added, allowing vertex data to be uploaded to the OpenGL implementation and then possibly discarded from the program's memory space. Somewhat confusingly, the designers reused much of the Vertex Array API but added explicitly bound "buffers" in a similar manner to the texture API. As an example (source):
#define GL_GLEXT_PROTOTYPES 1

#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#include <GL/glut.h>
#include <GL/glext.h>

typedef float vector3[3];

typedef struct
{
  vector3 position;
  vector3 colour;
} vertex;

static GLuint vertices_name;
static GLuint triangles[2];

static void
init(void)
{
  vertex *data = malloc(4 * sizeof(vertex));
  if (data == NULL) abort();

  data[0].position[0] = 0.0;
  data[0].position[1] = 0.0;
  data[0].position[2] = 0.0;
  data[0].colour[0] = 1.0;
  data[0].colour[1] = 0.0;
  data[0].colour[2] = 0.0;

  data[1].position[0] = 100.0;
  data[1].position[1] = 0.0;
  data[1].position[2] = 0.0;
  data[1].colour[0] = 0.0;
  data[1].colour[1] = 1.0;
  data[1].colour[2] = 0.0;

  data[2].position[0] = 100.0;
  data[2].position[1] = 100.0;
  data[2].position[2] = 0.0;
  data[2].colour[0] = 0.0;
  data[2].colour[1] = 0.0;
  data[2].colour[2] = 1.0;

  data[3].position[0] = 0.0;
  data[3].position[1] = 100.0;
  data[3].position[2] = 0.0;
  data[3].colour[0] = 1.0;
  data[3].colour[1] = 1.0;
  data[3].colour[2] = 0.0;

  glGenBuffers(1, &vertices_name);
  glBindBuffer(GL_ARRAY_BUFFER, vertices_name);
  glBufferData(GL_ARRAY_BUFFER, 4 * sizeof(vertex), data, GL_STATIC_DRAW);

  free(data);

  glGenBuffers(2, triangles);

  {
    unsigned char indices[] = { 0, 1, 2 };
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangles[0]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); 
  }

  {
    unsigned char indices[] = { 1, 2, 3 };
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangles[1]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); 
  }

  assert(glGetError() == GL_NO_ERROR);
}

static void
reshape(int width, int height)
{
  glViewport(0, 0, width, height);
 
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(0, width, 0, height, -1.0, 100.0);

  assert(glGetError() == GL_NO_ERROR);
}

static void
display(void)
{
  glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
  glClear(GL_COLOR_BUFFER_BIT);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  glPushMatrix();
  glTranslated(20.0, 20.0, 0.0);

  glEnableClientState(GL_VERTEX_ARRAY);
  glEnableClientState(GL_COLOR_ARRAY);
 
  glBindBuffer(GL_ARRAY_BUFFER, vertices_name);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangles[0]);
  glVertexPointer(3, GL_FLOAT, sizeof(vertex), (void *) 0);
  glColorPointer(3, GL_FLOAT, sizeof(vertex), (void *) offsetof(vertex, colour));
  glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, (void *) 0);
 
  glTranslated(120.0, 120.0, 0.0);

  glBindBuffer(GL_ARRAY_BUFFER, vertices_name);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangles[1]);
  glVertexPointer(3, GL_FLOAT, sizeof(vertex), (void *) 0);
  glColorPointer(3, GL_FLOAT, sizeof(vertex), (void *) offsetof(vertex, colour));
  glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, (void *) 0);

  glDisableClientState(GL_VERTEX_ARRAY);
  glDisableClientState(GL_COLOR_ARRAY);

  glPopMatrix();

  assert(glGetError() == GL_NO_ERROR);

  glutSwapBuffers();
}

int
main(int argc, char **argv)
{
  glutInit(&argc, argv);
  glutCreateWindow("Vertex array triangle");
  init();
  glutReshapeFunc(reshape);
  glutDisplayFunc(display);
  glutIdleFunc(glutPostRedisplay);

  glutMainLoop();
  return 0;
}

      
Note that the code is almost identical to that of the vertex array code, except that vertex and index data is uploaded once and referenced later via explicitly bound buffers. The data exists only in the OpenGL implementation's memory. Also, calls to glVertexPointer(), glColorPointer(), and glDrawElements() are now passed integer byte offsets as opposed to memory addresses. Yes, unfortunately, this is a type error in languages with stronger type systems!
The vertex buffer API also provides functions for modifying and/or replacing already buffered data such as glBufferSubData() (analogous to glTexSubImage() in the texture API) and mapping buffers directly into the application's address space for real-time modification (glMapBuffer(), which is somewhat analogous to the POSIX mmap() function).

PreviousUpNext
4. Vertex ArraysA Brief History Of Vertex Specification In OpenGL 6. Modern Vertex Buffer Objects