Chapter 6. Vertex Attributes, Vertex Arrays, and Buffer Objects

This chapter describes how vertex attributes and data are specified in OpenGL ES 2.0. We discuss what vertex attributes are, how to specify them and their supported data formats, and how to bind vertex attribute indices to the appropriate vertex attribute names used in a vertex shader. After reading this chapter, you should have a good grasp of what vertex attributes are and how to draw primitives with vertex attributes in OpenGL ES 2.0.

Vertex data, also referred to as vertex attributes, specify per-vertex data. This per-vertex data can be specified for each vertex or a constant value can be used for all vertices. For example, if you want to draw a triangle that has a solid color (for the sake of this example say the color is black as shown in Figure 6-1), you would specify a constant value that will be used by all three vertices of the triangle. However, the position of the three vertices that make up the triangle will not be the same and therefore we will need to specify a vertex array that stores three position values.

Triangle with a Constant Color Vertex and Per-Vertex Position Attributes

Figure 6-1. Triangle with a Constant Color Vertex and Per-Vertex Position Attributes

In OpenGL ES 1.1, vertex attributes had predefined names such as position, normal, color, and texture coordinates. This was acceptable because the fixed function pipeline implemented by OpenGL ES 1.1 only required these predefined vertex attributes. With a programmable pipeline, developers need to be able to specify their own vertex attribute names used in vertex shaders. Support for user-defined (i.e., generic) vertex attributes therefore became a requirement for OpenGL ES 2.0. If generic vertex attributes are supported by the API then there is no longer a need to support predefined vertex attribute names because they can be mapped by the application to one of the generic vertex attributes.

Specifying Vertex Attribute Data

As mentioned before, only generic vertex attributes are supported by OpenGL ES 2.0. The attribute data can be specified for each vertex using a vertex array or it can be a constant value that is used for all vertices of a primitive.

All OpenGL ES 2.0 implementations must support a minimum of eight vertex attributes. An application can query the exact number of vertex attributes that are supported by a particular implementation, which might be greater than eight. The following code describes how an application can query the number of vertex attributes an implementation actually supports.

GLint maxVertexAttribs;   // n will be >= 8
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);

Constant Vertex Attribute

A constant vertex attribute is the same for all vertices of a primitive, and therefore only one value needs to be specified for all the vertices of a primitive.

A constant vertex attribute value is specified using any of the following functions:

void glVertexAttrib1f(GLuint index, GLfloat x);
void glVertexAttrib2f(GLuint index, GLfloat x, GLfloat y);
void glVertexAttrib3f(GLuint index, GLfloat x, GLfloat y, GLfloat z);
void glVertexAttrib4f(GLuint index, GLfloat x, GLfloat y, GLfloat z,
                      GLfloat w);
void glVertexAttrib1fv(GLuint index, const GLfloat *values);
void glVertexAttrib2fv(GLuint index, const GLfloat *values);
void glVertexAttrib3fv(GLuint index, const GLfloat *values);
void glVertexAttrib4fv(GLuint index, const GLfloat *values);

The glVertexAttrib* commands are used to load the generic vertex attribute specified by index. glVertexAttrib1f and glVertexAttrib1fv load (x, 0.0, 0.0, 1.0) into the generic vertex attribute. glVertexAttrib2f and glVertexAttrib2fv load (x, y, 0.0, 1.0) into the generic vertex attribute. glVertexAttrib3f and glVertexAttrib3fv load (x, y, z, 1.0) into the generic vertex attribute. glVertexAttrib4f and glVertexAttrib4fv load (x, y, z, w) into the generic vertex attribute.

A question arises: OpenGL version 2.0 and higher supports functions that specify the constant vertex attribute data as byte, unsigned byte, short, unsigned short, int, unsigned int, float, and double. Why does OpenGL ES 2.0 only support the float variant? The reason for this is that constant vertex attributes are not used that frequently. Because their use is infrequent, and because they will most likely be stored as single precision floating-point values internally, ES 2.0 only supports the float variant.

Vertex Arrays

Vertex arrays specify attribute data per vertex and are buffers stored in the application’s address space (what OpenGL ES calls the client space). They provide an efficient and flexible way for specifying vertex attribute data. Vertex arrays are specified using the glVertexAttribPointer function.

void

glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *ptr)

index

specifies the generic vertex attribute index. This value is 0 to max vertex attributes supported – 1

size

number of components specified in the vertex array for the vertex attribute referenced by index. Valid values are 1–4

type

data format. Valid values are:

GL_BYTE GL_UNSIGNED_BYTE GL_SHORT GL_UNSIGNED_SHORT GL_FLOAT GL_FIXED GL_HALF_FLOAT_OES[*]

normalized

is used to indicate whether the non-floating data format type should be normalized or not when converted to floating point

stride

the components of vertex attribute specified by size are stored sequentially for each vertex. stride specifies the delta between data for vertex index I and vertex (I + 1). If stride is 0, attribute data for all vertices are stored sequentially. If stride is > 0, then we use the stride value as the pitch to get vertex data for next index

ptr

Pointer to the buffer holding vertex attribute data

[*] GL_HALF_FLOAT_OES is an optional vertex data format supported by OpenGL ES 2.0. The extension string that implements this vertex data format is named GL_OES_vertex_half_float. To determine whether this feature is supported by an OpenGL ES 2.0 implementation, look for the string name GL_OES_vertex_half_float in the list of extensions returned by glGetString(GL_EXTENSIONS).

We present a few examples that illustrate how to specify vertex attributes with glVertexAttribPointer. The commonly used methods for allocating and storing vertex attribute data are:

  • Store vertex attributes together in a single buffer. This method of storing vertex attributes is called an array of structures. The structure represents all attributes of a vertex and we have an array of these attributes per vertex.

  • Store each vertex attribute in a separate buffer. This method of storing vertex attributes is called structure of arrays.

Let us say that each vertex has four vertex attributes—position, normal, and two texture coordinates—and that these attributes are stored together in one buffer that is allocated for all vertices. The vertex position attribute is specified as a vector of three floats (x, y, z), the vertex normal is also specified as a vector of three floats, and each texture coordinate is specified as a vector of two floats. Figure 6-2 gives the memory layout of this buffer.

Position, Normal, and Two Texture Coordinates Stored As an Array

Figure 6-2. Position, Normal, and Two Texture Coordinates Stored As an Array

Example 6-1 describes how these four vertex attributes are specified with glVertexAttribPointer.

Example 6-1. Array of Structures

#define VERTEX_POS_SIZE           3    // x, y and z
#define VERTEX_NORMAL_SIZE        3    // x, y and z
#define VERTEX_TEXCOORD0_SIZE     2    // s and t
#define VERTEX_TEXCOORD1_SIZE     2    // s and t

#define VERTEX_POS_INDX           0
#define VERTEX_NORMAL_INDX        1
#define VERTEX_TEXCOORD0_INDX     2
#define VERTEX_TEXCOORD1_INDX     3

// the following 4 defines are used to determine location of various
// attributes if vertex data is are stored as an array of structures
#define VERTEX_POS_OFFSET         0
#define VERTEX_NORMAL_OFFSET      3
#define VERTEX_TEXCOORD0_OFFSET   6
#define VERTEX_TEXCOORD1_OFFSET   8

#define VERTEX_ATTRIB_SIZE   VERTEX_POS_SIZE + 
                             VERTEX_NORMAL_SIZE + 
                             VERTEX_TEXCOORD0_SIZE + 
                             VERTEX_TEXCOORD1_SIZE

float *p  = malloc(numVertices * VERTEX_ATTRIB_SIZE
                   * sizeof(float));

// position is vertex attribute 0
glVertexAttribPointer(VERTEX_POS_INDX, VERTEX_POS_SIZE,
                      GL_FLOAT, GL_FALSE,
                      VERTEX_ATTRIB_SIZE * sizeof(float),
                      p);
// normal is vertex attribute 1
glVertexAttribPointer(VERTEX_NORMAL_INDX, VERTEX_NORMAL_SIZE,
                      GL_FLOAT, GL_FALSE,
                      VERTEX_ATTRIB_SIZE * sizeof(float),
                      (p +  VERTEX_NORMAL_OFFSET));

// texture coordinate 0 is vertex attribute 2
glVertexAttribPointer(VERTEX_TEXCOORD0_INDX, VERTEX_TEXCOORD0_SIZE,
                      GL_FLOAT, GL_FALSE,
                      VERTEX_ATTRIB_SIZE * sizeof(float),
                      (p +  VERTEX_TEXCOORD0_OFFSET));

// texture coordinate 1 is vertex attribute 3
glVertexAttribPointer(VERTEX_TEXCOORD1_INDX, VERTEX_TEXCOORD1_SIZE,
                      GL_FLOAT, GL_FALSE,
                      VERTEX_ATTRIB_SIZE * sizeof(float),
                      (p + VERTEX_TEXCOORD1_OFFSET));

In Example 6-2 that follows, position, normal, and texture coordinate 0 and 1 are stored in separate buffers.

Example 6-2. Structure of Arrays

float *position  = malloc(numVertices * VERTEX_POS_SIZE *
                          sizeof(float));
float *normal    = malloc(numVertices * VERTEX_NORMAL_SIZE *
                          sizeof(float));
float *texcoord0 = malloc(numVertices * VERTEX_TEXCOORD0_SIZE *
                          sizeof(float));
float *texcoord1 = malloc(numVertices * VERTEX_TEXCOORD1_SIZE *
                          sizeof(float));

// position is vertex attribute 0
glVertexAttribPointer(VERTEX_POS_INDX, VERTEX_POS_SIZE,
                      GL_FLOAT, GL_FALSE,
                      VERTEX_POS_SIZE * sizeof(float), position);

// normal is vertex attribute 1
glVertexAttribPointer(VERTEX_NORMAL_INDX, VERTEX_NORMAL_SIZE,
                      GL_FLOAT, GL_FALSE,
                      VERTEX_NORMAL_SIZE * sizeof(float), normal);

// texture coordinate 0 is vertex attribute 2
glVertexAttribPointer(VERTEX_TEXCOORD0_INDX, VERTEX_TEXCOORD0_SIZE,
                      GL_FLOAT, GL_FALSE, VERTEX_TEXCOORD0_SIZE *
                      sizeof(float), texcoord0);
// texture coordinate 1 is vertex attribute 3
glVertexAttribPointer(VERTEX_TEXCOORD1_INDX, VERTEX_TEXCOORD1_SIZE,
                      GL_FLOAT, GL_FALSE,
                      VERTEX_TEXCOORD1_SIZE * sizeof(float),
                      texcoord1);

Performance Hints

How to store different attributes of a vertex

We described the two most common ways of storing vertex attributes—array of structures and structure of arrays. The question to ask is which allocation method would be the most efficient for OpenGL ES 2.0 hardware implementations. The answer is array of structures. The reason is that the attribute data for each vertex can be read in sequential fashion and so will most likely result in an efficient memory access pattern. A disadvantage of using array of structures is when an application wants to modify specific attributes. If a subset of vertex attribute data needs to be modified (e.g., texture coordinates), this will result in strided updates to the vertex buffer. When vertex buffer is supplied as a buffer object, the entire vertex attribute buffer will need to be reloaded. One can avoid this inefficiency by storing vertex attributes that are dynamic in nature in a separate buffer.

Which data format to use for vertex attributes

The vertex attribute data format specified by the type argument in glVertexAttribPointer cannot only impact the graphics memory storage requirements for vertex attribute data, but can also impact the overall performance, which is a function of memory bandwidth required to render the frame(s). The smaller the data footprint, the lower the memory bandwidth required. Our recommendation is that applications should use GL_HALF_FLOAT_OES wherever possible. Texture coordinates, normals, binormals, tangent vectors, and so on are good candidates to be stored using GL_HALF_FLOAT_OES for each component. Color could be stored as GL_UNSIGNED_BYTE with four components per vertex color. We also recommend GL_HALF_FLOAT_OES for vertex position, but recognize that this might not be possible for quite a few cases. For such cases, the vertex position could be stored as GL_FLOAT or GL_FIXED.

How the Normalized Flag in glVertexAttribPointer Works

Vertex attributes are internally stored as a single precision floating-point number before they get used in a vertex shader. If the data type indicates that the vertex attribute is not a float, then the vertex attribute will be converted to a single precision floating-point number before it gets used in a vertex shader. The normalized flag controls the conversion of the nonfloat vertex attribute data to a single precision floating-point value. If the normalized flag is false, the vertex data is converted directly to a floating-point value. This would be similar to casting the variable that is not a float type to float. The following code gives an example.

GLfloat   f;
GLbyte    b;
f = (GLfloat)b;  // f represents values in the range [-128.0, 127.0]

If the normalized flag is true, the vertex data is mapped to the [–1.0, 1.0] range if the data type is GL_BYTE, GL_SHORT or GL_FIXED or to the [0.0, 1.0] range if the data type is GL_UNSIGNED_BYTE or GL_UNSIGNED_SHORT.

Table 6-1 describes conversion of non-floating-point data types with the normalized flag set.

Table 6-1. Data Conversions

Vertex Data Format

Conversion to Floating Point

GL_BYTE

(2c + 1) / (28 – 1)

GL_UNSIGNED_BYTE

c / (28 – 1)

GL_SHORT

(2c + 1) / (216 – 1)

GL_UNSIGNED_SHORT

c / (216 – 1)

GL_FIXED

c / 216

GL_FLOAT

c

GL_HALF_FLOAT_OES

c

Selecting Between a Constant Vertex Attribute or a Vertex Array

The application can enable whether it wants OpenGL ES to use the constant data or data from vertex array. Figure 6-3 describes how this works in OpenGL ES 2.0.

Selecting Constant or Vertex Array Vertex Attribute

Figure 6-3. Selecting Constant or Vertex Array Vertex Attribute

The commands glEnableVertexAttribArray and glDisableVertexAttribArray are used to enable and disable a generic vertex attribute array. If the vertex attribute array is disabled for a generic attribute index, the constant vertex attribute data specified for that index will be used.

void

glEnableVertexAttribArray(GLuint index);

void

glDisableVertexAttribArray(GLuint index);

index

specifies the generic vertex attribute index. This value is 0 to max vertex attributes supported – 1

Example 6-3 describes how to draw a triangle where one of the vertex attributes is constant and the other is specified using a vertex array.

Example 6-3. Using Constant and Vertex Array Attributes

GLbyte vertexShaderSrc[] =
      "attribute vec4 a_position;    
"
      "attribute vec4 a_color;       
"
      "varying vec4   v_color;       
"
      "void main()                   
"
      "{                             
"
      "    v_color = a_color;        
"
      "    gl_Position = a_position; 
"
      "}";

GLbyte fragmentShaderSrc[] =
      "varying vec4 v_color;         
"
      "void main()                   
"
      "{                             
"
      "    gl_FragColor = v_color;   
"
      "}";

GLfloat   color[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
GLfloat   vertexPos[3 * 3];  // 3 vertices, with (x,y,z) per-vertex
GLuint    shaderObject[2];
GLuint    programObject;

shaderObject[0] = LoadShader(vertexShaderSrc, GL_VERTEX_SHADER);
shaderObject[1] = LoadShader(fragmentShaderSrc, GL_FRAGMENT_SHADER);

programObject = glCreateProgram();
glAttachShader(programObject, shaderObject[0]);
glAttachShader(programObject, shaderObject[1]);

glVertexAttrib4fv(0, color);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, vertexPos);
glEnableVertexAttribArray(1);

glBindAttribLocation(programObject, 0, "a_color");
glBindAttribLocation(programObject, 1, "a_position");

glLinkProgram(programObject);
glUseProgram(programObject);

glDrawArrays(GL_TRIANGLES, 0, 3);

The vertex attribute color used in the code example is a constant value whereas the vertexPos attribute is specified using a vertex array. The value of color will be the same for all vertices of the triangle(s) drawn whereas the vertexPos attribute could vary for vertices of the triangle(s) drawn.

Declaring Vertex Attribute Variables in a Vertex Shader

We have looked at what a vertex attribute is, and how to specify vertex attributes in OpenGL ES. We now discuss how to declare vertex attribute variables in a vertex shader.

In a vertex shader, a variable is declared as a vertex attribute by using the attribute qualifier. The attribute qualifier can only be used in a vertex shader. If the attribute qualifier is used in a fragment shader, it should result in an error when the fragment shader is compiled.

A few example declarations of vertex attributes are given here.

attribute vec4   a_position;
attribute vec2   a_texcoord;
attribute vec3   a_normal;

The attribute qualifier can be used only with the data types float, vec2, vec3, vec4, mat2, mat3, and mat4. Attribute variables cannot be declared as arrays or structures. The following example declarations of vertex attributes are invalid and should result in a compilation error.

attribute foo_t  a_A;   // foo_t is a structure
attribute vec4   a_B[10];

An OpenGL ES 2.0 implementation supports GL_MAX_VERTEX_ATTRIBS vec4 vertex attributes. A vertex attribute that is declared as a float or vec2 or vec3 will count as one vec4 attribute. Vertex attributes declared as mat2, mat3, or mat4 will count as two, three, or four vec4 attributes, respectively. Unlike uniform and varying variables, which get packed automatically by the compiler, attributes do not get packed. Each component is stored internally by the implementation as a 32-bit single precision floating-point value. Please consider carefully when declaring vertex attributes with sizes less than vec4, as the maximum number of vertex attributes available is a limited resource. It might be better to pack them together into one vec4 attribute instead of declaring them as individual vertex attributes in the vertex shader.

Variables declared as vertex attributes in a vertex shader are read-only variables and cannot be modified. The following code should cause a compilation error.

attribute vec4   a_pos;
uniform   vec4   u_v;

void main()
{
   a_pos = u_v; <--- cannot assign to a_pos as it is read-only
}

An attribute can be declared inside a vertex shader but if it is not used then it is not considered active and does not count against the limit. If the number of attributes used in a vertex shader is greater than GL_MAX_VERTEX_ATTRIBS, the vertex shader will fail to link.

Once a program has been successfully linked, we need to find out the number of active vertex attributes used by the vertex shader attached to this program. The following line of code describes how to get the number of active vertex attributes.

glGetProgramiv(progam, GL_ACTIVE_ATTRIBUTES, &numActiveAttribs);

A detailed description of glGetProgamiv is given in Chapter 4, “Shaders and Programs.”

The list of active vertex attributes used by a program and their data types can be queried using the glGetActiveAttrib command.

void

glGetActiveAttrib(GLuint program, GLuint index, GLsizei bufsize, GLsizei *length, GLint *size, GLenum *type, GLchar *name)

program

name of a program object that was successfully linked previously

index

specifies the vertex attribute to query and will be a value between 0 ... GL_ACTIVE_ATTRIBUTES – 1. The value of GL_ACTIVE_ATTRIBUTES is determined with glGetProgramiv

bufsize

specifies the maximum number of characters that may be written into name, including the null terminator

length

returns the number of characters written into name excluding the null terminator, if length is not NULL

type

returns the type of the attribute. Valid values are:

GL_FLOAT

GL_FLOAT_VEC2

GL_FLOAT_VEC3

GL_FLOAT_VEC4

GL_FLOAT_MAT2

GL_FLOAT_MAT3

GL_FLOAT_MAT4

size

returns the size of the attribute. This is specified in units of the type returned by type. If the variable is not an array, size will always be 1. If the variable is an array, then size returns the size of the array

name

name of the attribute variable as declared in the vertex shader

The glGetActiveAttrib call provides information about the attribute selected by index. As described above, index must be a value between 0 and GL_ACTIVE_ATTRIBUTES – 1. The value of GL_ACTIVE_ATTRIBUTES is queried using glGetProgramiv. An index of 0 selects the first active attributes and an index of GL_ACTIVE_ATTRIBUTES – 1 selects the last vertex attribute.

Binding Vertex Attributes to Attribute Variables in a Vertex Shader

We discussed that in a vertex shader, vertex attribute variables are specified by the attribute qualifier, the number of active attributes can be queried using glGetProgramiv and the list of active attributes in a program can be queried using glGetActiveAttrib. We also described that generic attribute indices that range from 0 to (GL_MAX_VERTEX_ATTRIBS – 1) are used to enable a generic vertex attribute and specify a constant or per-vertex (i.e., vertex array) value using the glVertexAttrib* and glVertexAttribPointer commands. Now we describe how to map this generic attribute index to the appropriate attribute variable declared in the vertex shader. This mapping will allow appropriate vertex data to be read into the correct vertex attribute variable in the vertex shader.

Figure 6-4 describes how generic vertex attributes are specified and bound to attribute names in a vertex shader.

Specifying and Binding Vertex Attributes for Drawing a Primitive(s)

Figure 6-4. Specifying and Binding Vertex Attributes for Drawing a Primitive(s)

There are two approaches that OpenGL ES 2.0 enables to map a generic vertex attribute index to an attribute variable name in the vertex shader. These approaches can be categorized as follows:

  • OpenGL ES 2.0 will bind the generic vertex attribute index to the attribute name.

  • The application can bind the vertex attribute index to an attribute name.

The glBindAttribLocation command can be used to bind a generic vertex attribute index to an attribute variable in a vertex shader. This binding takes effect when the program is linked the next time. It does not change the bindings used by the currently linked program.

void

glBindAttribLocation(GLuint program, GLuint index, const GLchar *name)

program

name of a program object

index

generic vertex attribute index

name

name of the attribute variable

If name was bound previously, its assigned binding is replaced with an index. glBindAttribLocation can be called even before a vertex shader is attached to a program object. This means that this call can be used to bind any attribute name. Attribute names that do not exist or are not active in a vertex shader attached to the program object are ignored.

The other option is to let OpenGL ES 2.0 bind the attribute variable name to a generic vertex attribute index. This binding is performed when the program is linked. In the linking phase, the OpenGL ES 2.0 implementation performs the following operation for each attribute variable:

For each attribute variable, check if a binding has been specified via glBindAttribLocation. If a binding is specified, the appropriate attribute index specified is used. If not, the implementation will assign a generic vertex attribute index.

This assignment is implementation specific and can vary from one OpenGL ES 2.0 implementation to another. An application can query the assigned binding by using the glGetAttribLocation command.

GLint

glGetAttribLocation(GLuint program, const GLchar *name)

program

program object

name

name of attribute variable

glGetAttribLocation returns the generic attribute index bound to attribute variable name when the program object defined by program was last linked. If name is not an active attribute variable, or if program is not a valid program object or was not linked successfully, then –1 is returned, indicating an invalid attribute index.

Vertex Buffer Objects

The vertex data specified using vertex arrays is stored in client memory. This data has to be copied from client memory to graphics memory when a call to glDrawArrays or glDrawElements is made. These two commands are described in detail in Chapter 7, “Primitive Assembly and Rasterization.” It would, however, be much better if we did not have to copy the vertex data on every draw call and instead cache the data in graphics memory. This can significantly improve the rendering performance and additionally reduce the memory bandwidth and power consumption requirements, both of which are quite important for handheld devices. This is where vertex buffer objects can help. Vertex buffer objects allow OpenGL ES 2.0 applications to allocate and cache vertex data in high-performance graphics memory and render from this memory, thus avoiding resending data every time a primitive is drawn. Not only the vertex data, but even the element indices that describe the vertex indices of the primitive and are passed as an argument to glDrawElements can also be cached.

There are two types of buffer objects supported by OpenGL ES: array buffer objects and element array buffer objects. The array buffer objects specified by the GL_ARRAY_BUFFER token are used to create buffer objects that will store vertex data. The element array buffer objects specified by the GL_ELEMENT_ARRAY_BUFFER token are used to create buffer objects that will store indices of a primitive.

Note

To get best performance, we recommend that OpenGL ES 2.0 applications use vertex buffer objects for vertex attribute data and element indices wherever possible.

Before we can render using buffer objects, we need to allocate the buffer objects and upload the vertex data and element indices into appropriate buffer objects. This is demonstrated by the sample code in Example 6-4.

Example 6-4. Creating and Binding Vertex Buffer Objects

void   initVertexBufferObjects(vertex_t *vertexBuffer,
                               GLushort *indices,
                               GLuint numVertices, GLuint numIndices,
                               GLuint *vboIds)
{
    glGenBuffers(2, vboIds);

    glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);
    glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(vertex_t),
                 vertexBuffer, GL_STATIC_DRAW);

    // bind buffer object for element indices
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIds[1]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER,
                 numIndices * sizeof(GLushort),indices,
                 GL_STATIC_DRAW);
}

The code described in Example 6-4 creates two buffer objects: a buffer object to store the actual vertex attribute data, and a buffer object to store the element indices that make up the primitive. In this example, the glGenBuffers command is called to get two unused buffer object names in vboIds. The unused buffer object names returned in vboIds are then used to create an array buffer object and an element array buffer object. The array buffer object is used to store vertex attribute data for vertices of one or more primitives. The element array buffer object stores the indices of a primitive(s). The actual array or element data is specified using glBufferData. Note the GL_STATIC_DRAW that is passed as an argument to glBufferData. This is used to describe how the buffer is accessed by the application and will be described later in this section.

void

glGenBuffers(GLsizei n, GLuint *buffers)

n

number of buffer objects names to return

buffers

pointer to an array of n entries, where allocated buffer objects are returned

glGenBuffers assigns n buffer object names and returns them in buffers. The buffer object names returned by glGenBuffers are unsigned integer numbers other than 0. The value 0 is reserved by OpenGL ES and does not refer to a buffer object. Applications trying to modify or query buffer object state for buffer object 0 will generate an appropriate error.

The glBindBuffer command is used to make a buffer object the current array buffer object or the current element array buffer object. The first time a buffer object name is bound by calling glBindBuffer, the buffer object is allocated with appropriate default state, and if the allocation is successful, this allocated object is bound as the current array buffer object or the current element array buffer object for the rendering context.

void

glBindBuffer(GLenum target, GLuint buffer)

target

can be set to GL_ARRAY_BUFFER or GL_ELEMENT_ARRAY_BUFFER

buffer

buffer object to be assigned as the current object to target

Note that glGenBuffers is not required to assign a buffer object name before it is bound using glBindBuffer. An application can specify an unused buffer object name to glBindBuffer. However, we do recommend that OpenGL ES applications call glGenBuffers and use buffer object names returned by glGenBuffers instead of specifying their own buffer object names.

The state associated with a buffer object can be categorized as follows:

  • GL_BUFFER_SIZE. This refers to the size of the buffer object data that is specified by glBufferData. The initial value when the buffer object is first bound using glBindBuffer is zero.

  • GL_BUFFER_USAGE. This is a hint as to how the application is going to use the data stored in the buffer object. This is described in detail in Table 6-2. The initial value is GL_STATIC_DRAW.

Table 6-2. Buffer Usage

Buffer Usage Enum

Description

GL_STATIC_DRAW

The buffer object data will be specified once by the application and used many times to draw primitives.

GL_DYNAMIC_DRAW

The buffer object data will be specified repeatedly by the application and used many times to draw primitives.

GL_STREAM_DRAW

The buffer object data will be specified once by the application and used a few times to draw primitives.

As mentioned earlier, GL_BUFFER_USAGE is a hint to OpenGL ES and not a guarantee. Therefore, an application could allocate a buffer object data store with usage set to GL_STATIC_DRAW and frequently modify it.

Missing OpenGL ES Buffer Usage Enums Supported by OpenGL

Note

The GL_STATIC_READ, GL_STATIC_COPY, GL_DYNAMIC_READ, GL_DYNAMIC_COPY, GL_STREAM_READ, and GL_STREAM_COPY enums supported by OpenGL are not defined by OpenGL ES. This is because these enums imply that the data store contents will be specified by reading data from the GL. OpenGL allows applications to read the contents of the vertex buffer storage but these API calls are missing from OpenGL ES. As there is no mechanism to read buffer data in OpenGL ES, these enums are no longer valid and are therefore not supported.

The vertex array data or element array data storage is created and initialized using the glBufferData command.

void

glBufferData(GLenum target, GLsizeiptr size, const void *data, GLenum usage)

target

can be set to GL_ARRAY_BUFFER or GL_ELEMENT_ARRAY_BUFFER

size

size of buffer data store in bytes

data

pointer to the buffer data supplied by the application

usage

a hint on how the application is going to use the data stored in the buffer object. Refer to Table 6-2 for details

glBufferData will reserve appropriate data storage based on the value of size. The data argument can be a NULL value indicating that the reserved data store remains uninitialized. If data is a valid pointer, then contents of data are copied to the allocated data store. The contents of the buffer object data store can be initialized or updated using the glBufferSubData command.

void

glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void *data)

target

can be set to GL_ARRAY_BUFFER or GL_ELEMENT_ARRAY_BUFFER

offset size

offset into the buffer data store and number of bytes of the data store that is being modified

data

pointer to the client data that needs to be copied into the buffer object data storage

After the buffer object data store has been initialized or updated using glBufferData or glBufferSubData, the client data store is no longer needed and can be released. For static geometry, applications can free the client data store and reduce the overall system memory consumed by the application. This might not be possible for dynamic geometry.

We now look at drawing primitives with and without buffer objects. Example 6-5 describes drawing primitives with and without vertex buffer objects. Notice that the code to set up vertex attributes is very similar. In this example, we use the same buffer object for all attributes of a vertex. When a GL_ARRAY_BUFFER buffer object is used, the pointer argument in glVertexAttribPointer changes from being a pointer to the actual data to being an offset in bytes into the vertex buffer store allocated using glBufferData. Similarly if a valid GL_ELEMENT_ARRAY_BUFFER object is used, the indices argument in glDrawElements changes from being a pointer to the actual element indices to being an offset in bytes to the element index buffer store allocated using glBufferData.

Example 6-5. Drawing with and without Vertex Buffer Objects

#define VERTEX_POS_SIZE         3    // x, y and z
#define VERTEX_NORMAL_SIZE      3    // x, y and z
#define VERTEX_TEXCOORD0_SIZE   2    // s and t

#define VERTEX_POS_INDX         0
#define VERTEX_NORMAL_INDX      1
#define VERTEX_TEXCOORD0_INDX   2

//
// vertices   - pointer to a buffer that contains vertex attribute
                data
// vtxStride  - stride of attribute data / vertex in bytes
// numIndices - number of indices that make up primitive
//              drawn as triangles
// indices    - pointer to element index buffer.
//
void    drawPrimitiveWithoutVBOs(GLfloat *vertices, GLint vtxStride,
                                 GLint numIndices, GLushort *indices)
{
    GLfloat   *vtxBuf = vertices;

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

    glEnableVertexAttribArray(VERTEX_POS_INDX);
    glEnableVertexAttribArray(VERTEX_NORMAL_INDX);
    glEnableVertexAttribArray{VERTEX_TEXCOORD0_INDX);

    glVertexAttribPointer(VERTEX_POS_INDX, VERTEX_POS_SIZE,
                          GL_FLOAT, GL_FALSE, vtxStride, vtxBuf);
    vtxBuf += VERTEX_POS_SIZE;
    glVertexAttribPointer(VERTEX_NORMAL_INDX, VERTEX_NORMAL_SIZE,
                          GL_FLOAT, GL_FALSE, vtxStride, vtxBuf);
    vtxBuf += VERTEX_NORMAL_SIZE;
    glVertexAttribPointer(VERTEX_TEXCOORD0_INDX,
                          VERTEX_TEXCOORD0_SIZE, GL_FLOAT,
                          GL_FALSE, vtxStride, vtxBuf);

    glBindAttribLocation(program, VERTEX_POS_INDX, "v_position");
    glBindAttribLocation(program, VERTEX_NORMAL_INDX, "v_normal");
    glBindAttribLocation(program, VERTEX_TEXCOORD0_INDX,
                         "v_texcoord");

    glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT,
                   indices);
}

void    drawPrimitiveWithVBOs(GLint numVertices, GLfloat *vtxBuf,
                              GLint vtxStride, GLint numIndices,
                              GLushort *indices)
{
    GLuint   offset = 0;
    GLuint   vboIds[2];

    // vboIds[0] - used to store vertex attribute data
    // vboIds[1] - used to store element indices
    glGenBuffers(2, vboIds);

    glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);
    glBufferData(GL_ARRAY_BUFFER, vtxStride * numVertices,
                 vtxBuf, GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIds[1]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER,
                 sizeof(GLushort) * numIndices,
                 indices, GL_STATIC_DRAW);

    glEnableVertexAttribArray(VERTEX_POS_INDX);
    glEnableVertexAttribArray(VERTEX_NORMAL_INDX);
    glEnableVertexAttribArray{VERTEX_TEXCOORD0_INDX);

    glVertexAttribPointer(VERTEX_POS_INDX, VERTEX_POS_SIZE,
                          GL_FLOAT, GL_FALSE, vtxStride,
                          (const void*)offset);

    offset += VERTEX_POS_SIZE * sizeof(GLfloat);
    glVertexAttribPointer(VERTEX_NORMAL_INDX, VERTEX_NORMAL_SIZE,
                          GL_FLOAT, GL_FALSE, vtxStride,
                          (const void*)offset);

    offset += VERTEX_NORMAL_SIZE * sizeof(GLfloat);
    glVertexAttribPointer(VERTEX_TEXCOORD0_INDX,
                          VERTEX_TEXCOORD0_SIZE,
                          GL_FLOAT, GL_FALSE, vtxStride,
                          (const void*)offset);
    glBindAttribLocation(program, VERTEX_POS_INDX, "v_position");
    glBindAttribLocation(program, VERTEX_NORMAL_INDX, "v_normal");
    glBindAttribLocation(program, VERTEX_TEXCOORD0_INDX,
                         "v_texcoord");

    glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, 0);

    glDeleteBuffers(2, vboIds);
}

In Example 6-5, we used one buffer object to store all the vertex data. This represents the array of structures method of storing vertex attributes described in Example 6-1. It is also possible to have a buffer object for each vertex attribute. This would be the structure of arrays method of storing vertex attributes described in Example 6-2. Example 6-6 describes how drawPrimitiveWithVBOs would look with a separate buffer object for each vertex attribute.

Example 6-6. Drawing with a Buffer Object per Attribute

#define VERTEX_POS_SIZE         3    // x, y and z
#define VERTEX_NORMAL_SIZE      3    // x, y and z
#define VERTEX_TEXCOORD0_SIZE   2    // s and t

#define VERTEX_POS_INDX         0
#define VERTEX_NORMAL_INDX      1
#define VERTEX_TEXCOORD0_INDX   2

//
// numVertices - number of vertices
// vtxBuf - an array of pointers describing attribute data
// vtxStrides - an array of stride values for each attribute
// numIndices - number of element indices of primitive
// indices - actual element index buffer
//
void   drawPrimitiveWithVBOs(GLint numVertices,
                             GLfloat **vtxBuf, GLint *vtxStrides,
                             GLint numIndices, GLushort *indices)
{
    GLuint   vboIds[4];

    // vboIds[0] - used to store vertex position
    // vboIds[1] - used to store vertex normal
    // vboIds[2] - used to store vertex texture coordinate 0
    // vboIds[3] - used to store element indices
    glGenBuffers(4, vboIds);

    glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);
    glBufferData(GL_ARRAY_BUFFER, vtxStrides[0] * numVertices,
                 vtxBuf[0], GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, vboIds[1]);
    glBufferData(GL_ARRAY_BUFFER, vtxStrides[1] * numVertices,
                 vtxBuf[1], GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, vboIds[2]);
    glBufferData(GL_ARRAY_BUFFER, vtxStrides[2] * numVertices,
                 vtxBuf[2], GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIds[3]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER,
                 sizeof(GLushort) * numIndices,
                 indices, GL_STATIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);
    glEnableVertexAttribArray(VERTEX_POS_INDX);
    glBindBuffer(GL_ARRAY_BUFFER, vboIds[1]);
    glEnableVertexAttribArray(VERTEX_NORMAL_INDX);
    glBindBuffer(GL_ARRAY_BUFFER, vboIds[2]);
    glEnableVertexAttribArray{VERTEX_TEXCOORD0_INDX);

    glVertexAttribPointer(VERTEX_POS_INDX, VERTEX_POS_SIZE,
                          GL_FLOAT, GL_FALSE, vtxStrides[0], 0);
    glVertexAttribPointer(VERTEX_NORMAL_INDX, VERTEX_NORMAL_SIZE,
                          GL_FLOAT, GL_FALSE, vtxStrides[1], 0);
    glVertexAttribPointer(VERTEX_TEXCOORD0_INDX,
                          VERTEX_TEXCOORD0_SIZE,
                          GL_FLOAT, GL_FALSE, vtxStrides[2], 0);

    glBindAttribLocation(program, VERTEX_POS_INDX, "v_position");
    glBindAttribLocation(program, VERTEX_NORMAL_INDX, "v_normal");
    glBindAttribLocation(program, VERTEX_TEXCOORD0_INDX,
                         "v_texcoord");

    glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, 0);

    glDeleteBuffers(4, vboIds)
}

After the application is done using buffer objects, they can be deleted using the glDeleteBuffers command.

void

glDeleteBuffers(GLsizei n, const GLuint *buffers)

n

number of buffer objects to be deleted

buffers

array of n entries that contain the buffer objects to be deleted

glDeleteBuffers deletes the buffer objects specified in buffers. Once a buffer object has been deleted, it can be reused as a new buffer object that stores vertex attributes or element indices for a different primitive.

As you can see from these examples, using vertex buffer objects is very easy and requires very little extra work to implement over vertex arrays. This minimal extra work involved in supporting the vertex buffer is well worth it, considering the performance gain this feature provides. In the next chapter we discuss how to draw primitives using glDrawArrays and glDrawElements, and how the primitive assembly and rasterization pipeline stages in OpenGL ES 2.0 work.

Mapping Buffer Objects

The OES_map_buffer extension allows applications to map and unmap a vertex buffer object’s data storage into the application’s address space. The map command returns a pointer to this data storage. This pointer can be used by the application to update the contents of the buffer object. The unmap command is used to indicate that the updates have been completed and to release the mapped pointer. The following paragraphs provide an in-depth description of these commands and specific performance tips.

The glMapBufferOES command maps a vertex buffer object’s data storage into the application’s address space.

void

*glMapBufferOES(GLenum target, GLenum access)

target

must be set to GL_ARRAY_BUFFER

access

can only be set GL_WRITE_ONLY_OES

glMapBufferOES returns a pointer to this storage. The mapping operation provides write-only access to the vertex buffer object’s data storage. A read of the memory region mapped by the returned pointer is undefined. Depending on the operating system’s capabilities, the read operation may generate a fault or return bogus data. glMapBufferOES will return a valid pointer if the buffer could be mapped, or else returns zero.

The glUnmapBufferOES command unmaps a previously mapped buffer.

GLboolean

glUnmapBufferOES(GLenum target)

target

must be set to GL_ARRAY_BUFFER

glUnmapBufferOES returns GL_TRUE if the unmap operation is successful. The pointer returned by glMapBufferOES can no longer be used after a successful unmap has been performed. glUnmapBufferOES returns GL_FALSE if the data in the vertex buffer object’s data storage have become corrupted after the buffer has been mapped. This can occur due to a change in the screen resolution, multiple screens being used by OpenGL ES context, or an out-of-memory event that causes the mapped memory to be discarded.[1]

Performance Tip

glMapBufferOES should only be used if the whole buffer is being updated. Using glMapBufferOES to update a subregion is not recommended, as there is no mechanism in glMapBufferOES to specify a subregion.

Even if glMapBufferOES is being used to update the entire buffer, the operation can still be expensive compared to glBufferData. With glMapBufferOES, the application gets a pointer to the buffer object data. However, this buffer might be in use by rendering commands previously queued by the application. If the buffer is currently in use, the GPU must wait for previous rendering commands that use this buffer to complete before returning the pointer. However, if the entire region is being updated, there is no reason to wait. We already saw that there is no mechanism for the OpenGL ES implementation to know just from the glMapBufferOES command that the application is updating the entire region or just a few bytes. There is a way for an application to indicate that the entire region will be updated. This can be done by calling glBufferData with the data argument set to NULL followed by glMapBufferOES. Calling glBufferData with data = NULL tells the OpenGL ES implementation that the previous buffer data is invalidated and the succeeding call to glMapBufferOES can be correctly optimized by the implementation.



[1] If the screen resolution changes to a larger width, height, and bits per pixel at runtime, the mapped memory may have to be released. Note that this is not a very common issue on handheld devices. A backing store is typically not implemented on most handheld and embedded devices. Therefore, an out-of-memory event will result in memory being freed up and reused for critical needs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset