5.3 - A Primer on Buffer Objects

A buffer object is a contiguous block of memory in the GPU that can be accessed very quickly by shader programs executing on the GPU. Buffer objects store the data that shader programs need for rendering. For WebGL 1.0, the contents of a buffer object is always an 1-dimensional array of floats.

Buffer objects provide the data for attribute variables in vertex shader programs. WebGL restricts the data types of attribute variables to be of type float, vec2, vec3, vec4, mat2, mat3, and mat4. Note that all of these data types contain floating point values.

JavaScript is not a strongly typed language and it does not distinguish between different types of numbers. Most programming languages have shorts, ints, floats, and doubles. JavaScript has only one data type for numeric values: number. JavaScript was modified to deal with binary data values by adding “typed array” objects. For WebGL, the data for all of your buffer objects will be defined in Float32Array arrays.

// Floating point arrays.
var f32 = new Float32Array(size); // Fractional values with 7 digits of accuracy

There are two ways to put data into a “typed array”:

For example:

// Create an array containing 6 floats. Notice the brackets around the array data.
var my_array = new Float32Array( [1.0, 2.0, 3.0, -1.0, -2.0, -3.0] );

// Create an array to hold 4 floating point numbers.
var an_array = new Float32Array(4);
an_array[0] = 12.0;
an_array[1] =  5.0;
an_array[2] = 37.0;
an_array[3] = 18.3;

Creating and Initializing Buffer Objects

Note that buffer objects reside in the GPU, but they are created, managed, and deleted using the WebGL API from JavaScript code. Here is a typical sequence of commands to create a buffer object and fill it with data.

//-----------------------------------------------------------------------
function createAndFillBufferObject(gl, data) {
  var buffer_id;

  // Create a buffer object
  buffer_id = gl.createBuffer();
  if (!buffer_id) {
    out.displayError('Failed to create the buffer object for ' + model_name);
    return null;
  }

  // Make the buffer object the active buffer.
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer_id);

  // Upload the data for this buffer object to the GPU.
  gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);

  return buffer_id;
}

Please note the following about this code:

  • Creating an object buffer does nothing more than reserve a new ID for a new buffer.
  • You will typically have many object buffers and only one of them is the “active buffer”. When you issue commands on buffer objects you are always manipulating the “active buffer”. The bindBuffer function simply changes the “active buffer” to the specific buffer using the buffer_id.
  • The bufferData function copies data from your JavaScript program into the GPU’s buffer object. If there was already data in the buffer object then its current contents is deleted and the new data is added.
  • The main error you will receive when copying data to the GPU is OUT_OF_MEMORY. The code above should check for gl errors by calling gl.getError(), but we will worry about catching errors later.

Shaders, Buffers, and the Graphics Pipeline

To help you understand the relationship between your shader programs and the graphics pipeline, let’s write some pseudocode that describes how the graphics pipeline performs rendering. This functionality is built into the graphics pipeline and hidden from your control.

Each time your JavaScript program calls gl.drawArrays(mode, start, count), ‘count’ number of vertices are sent through the graphics pipeline. Your vertex shader program is called once for each vertex in an array of vertices that is stored in a buffer object. Inside the graphics pipeline, hidden from you, is a algorithm that is doing this:

//////////// pseudocode ////////////
pipeline_mode = mode;
for (j = start; j < count; j += 1) {
  call vertex_shader(vertex_buffer[j]);
}

Please note:

The pseudocode above is misleading because GPU’s are multiprocessors and perform many operations in parallel. Vertices are not necessarily processed in the sequence they are defined and many vertices are typically being processed simultaneously.

Vertex and fragment shaders need more than just location data to create complex graphic images. Such information includes color, normal vector, texture coordinates, etc.. Because the graphics pipeline is optimized for speed, the other data has to be organized in arrays in the same order as the vertex data. If each vertex has additional attributes, the above pseudocode becomes something like this:

//////////// pseudocode ////////////
pipeline_mode = mode;
for (j = start; j < count; j += 1) {
  call vertex_shader(vertex_buffer[j], color_buffer[j], normal_vector_buffer[j], ...);
}

This is an important basic principle of WebGL rendering. All data must be organized on a “per vertex” basis because of the way the pipeline works. This means that in some cases your data must be duplicated in arrays multiple times to “match up” with the vertex data. This can be very inefficient for memory usage, but it makes rendering very fast. To illustrate this principle, suppose you want to render two triangles, one red (1,0,0) and one green (0,1,0). You must create an array that stores the color of each individual vertex, which means duplicating the color values multiple times. The code belows shows an array of 18 floats that represent 6 vertices. If the color of the vertices is coming from an array in a buffer object, the color has to be stored three times for each triangle.

var triangle_vertices = [0,0,0, 1,6,2, 3,4,1, 3,4,1, 1,6,2, 2,5,1];
var triangle_color    = [1,0,0, 1,0,0, 1,0,0, 0,1,0, 0,1,0, 0,1,0];

Basic data organization

Rendering data is always organized on a per-vertex basis.

Glossary

buffer object
a contiguous block of memory in the GPU that stores rendering data for a model. For WebGL 1.0, a buffer object is always a 1D array of floats.
vertex object buffer
a buffer object that contains vertices data. It is sometimes abbreviated as VOB.
Float32Array
a JavaScript data type that creates an array of floating point values.

Self-Assessments

    Q-100: A WebGL buffer object is … (Select all that apply.)
  • a one-dimensional array of floats.
  • Correct.
  • a contiguous block of memory in the GPU that stores data values.
  • Correct.
  • always stored in the GPU's memory.
  • Correct.
  • a variable in a shader program.
  • Incorrect.
    Q-101: A Float32Array is …
  • a JavaScript one-dimensional array of floats.
  • Correct.
  • a GLSL one-dimensional array of floats.
  • Incorrect. GLSL does not define Float32Array.
  • an array of integers.
  • Incorrect.
  • an array of characters (a string).
  • Incorrect.
    Q-102: Suppose you want to render a model defined by 200 triangles and you want to color each triangle with a unique color. The triangles are defined by 3 vertices per triangle, with each vertex defined by 3 floats. The color values are stored as RGB values in a separate object buffer. What is the required size for the color object buffer?
  • 1800 floats.
  • Correct. There are 600 vertices and 3 floats per RGB color.
  • 200 floats.
  • Incorrect.
  • 900 floats.
  • Incorrect.
  • 600 floats.
  • Incorrect.
Next Section - 5.4 - A Simple Model