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. The contents of a buffer object is always an 1-dimensional array. For our purposes we will consider a buffer object to always be homogeneous - that is, every value will have the same data type. (You can mix data types with some tricky JavaScript code, but let’s keep things simple.)
Buffer objects provide the data for attribute
variables in vertex
shader programs. Remember that WebGL is a restricted subset of OpenGL,
and WebGL only allows attribute
variables to be of type float
,
vec2
, vec3
, vec4
, mat2
, mat3
, and
mat4
. These are all floating point values. Therefore, all
buffer objects that you create will be arrays of 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’s 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, all of your buffer objects will contain
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”:
- Include a normal JavaScript array of numbers as a parameter to the constructor.
- Create the array of a certain size and then set individual elements to specific values.
These two options are demonstrated in the following code:
// 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¶
Shader programs are sometimes difficult to “wrap your brain around” because the relationship between the graphics pipeline and a shader program is not obvious and sometimes never explained. Let’s try to write some pseudocode that describes how the graphics pipeline performs rendering.
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:
for (j = start; j < count; j += 1) {
call vertex_shader(vertex_buffer[j]);
}
Vertex and fragment shaders need more than just location data if they are going 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:
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 really fast. To illustrate this principle, suppose you want to render a triangle using a particular color. You must create an array that stores the color of each individual vertex, even though all three vertices have the same color. The code belows shows an array of 9 values that represents 3 vertices. If the color of the vertices is coming from an array in a buffer object, the color has to be stored in three times to “match up” with the vertex data. In the example below, the color ‘red’ is stored three times.
var triangle_vertices = [0,0,0, 1,6,2, 3,4,1];
var triangle_color = [1,0,0, 1,0,0, 1,0,0]
Glossary¶
- buffer object
- a contiguous block of memory in the GPU that stores rendering data for a model. For WebGL, a buffer object is always a 1D array of floats.
- vertex object buffer
- a buffer object that contains vertices. It is sometimes abbreviated as VOB.
Float32Array
- a JavaScript data type that creates an array of floating point values.