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.
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.
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);
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 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.
|
|
| specifies the generic vertex attribute index. This value is 0 to max vertex attributes supported – 1 |
| number of components specified in the vertex array for the vertex attribute referenced by index. Valid values are 1–4 |
data format. Valid values are: | |
| |
| is used to indicate whether the non-floating data format type should be normalized or not when converted to floating point |
| the components of vertex attribute specified by size are stored sequentially for each vertex. |
| Pointer to the buffer holding vertex attribute data |
[*] |
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.
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);
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.
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
.
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.
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.
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.
|
|
|
|
| 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.
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.
|
|
| name of a program object that was successfully linked previously |
| specifies the vertex attribute to query and will be a value between 0 ... |
| specifies the maximum number of characters that may be written into name, including the null terminator |
| returns the number of characters written into name excluding the null terminator, if length is not |
| returns the type of the attribute. Valid values are: |
| |
| |
| |
| |
| |
| |
| |
| returns the size of the attribute. This is specified in units of the type returned by |
| 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.
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.
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.
|
|
name of a program object | |
| generic vertex attribute index |
| 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.
|
|
| program object |
| 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.
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.
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.
|
|
| number of buffer objects names to return |
| 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.
|
|
| can be set to |
| 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 |
---|---|
| The buffer object data will be specified once by the application and used many times to draw primitives. |
| The buffer object data will be specified repeatedly by the application and used many times to draw primitives. |
| 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.
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.
|
|
| can be set to |
| size of buffer data store in bytes |
| pointer to the buffer data supplied by the application |
| 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.
|
|
| can be set to |
| offset into the buffer data store and number of bytes of the data store that is being modified |
| 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.
|
|
| number of buffer objects to be deleted |
| 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.
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.
|
|
| must be set to |
| can only be set |
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.
|
|
| must be set to |
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]
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.