Open GL : Drawing a lot of Geometry Efficiently (part 1) – Combining Drawing Functions, Combining Geometry Using Primitive Restart

So far, you have seen how to send blocks of data to OpenGL to render using functions such as glDrawArrays. It’s possible to send huge numbers of vertices—millions if necessary—to OpenGL using a single call to this function. However, this is only of any use when the geometry is nicely arranged in a large contiguous block. In any nontrivial application, there will be many different, unrelated objects. There is likely to be a world or some kind of background, and each of these may require several calls to one of the drawing functions. It is not unusual to see a complex application making thousands or even hundreds of thousands of calls to the various drawing functions that OpenGL provides in every frame. In this section, we go over a number of methods that you can use to draw a lot of independent pieces of geometry with very few calls to OpenGL.

Combining Drawing Functions

If you have a lot of geometry to send to OpenGL in a single application, it’s likely that you will have one preferred method of drawing. This might be to use glDrawArrays or glDrawElements, for example. If you were to pack all of the vertex data for all of your objects into a single buffer, it would be reasonable to have a loop in your code that looks something like this:

for (int i = 0; i < num_objects; i++) {
    glDrawArrays(GL_TRIANGLES,
                 object[n]->first_vertex,
                 object[n]->vertex_count);
}

This might produce a lot of calls into OpenGL, and each one carries some overhead. If you have a large number of objects in your scene, and each has a relatively small number of triangles, the cost of each of these calls to glDrawArrays will start to add up and could negatively affect the performance of your application. A couple of functions that might help in this case are

void glMultiDrawArrays(GLenum mode, GLint *first, GLsizei *count, GLsizei primcount);	  

and

void glMultiDrawElements(GLenum mode, GLsizei *count, GLenum type, GLvoid **indices, GLsizei primcount);			  

These two functions operate similarly to the previous code. Each behaves as if its non-Multi versions had been called primcount times. For glMultiDrawArrays, first and count are arrays. Also, for glMultiDrawElements, count and indices are arrays. This allows OpenGL to perform all of its setup once, check that all the parameters are correct once, and if the driver supports it, send a single command to the graphics hardware. This can allow a lot of the overhead associated with calling OpenGL functions to be amortized across the number of function calls the glMultiDraw function replaces.

By rewriting this example, we can see that only one function call to glMultiDrawArrays can be used to replace the many (potentially thousands) calls to glDrawArrays. This new version is shown in Listing 1. Although there is more code, there are fewer calls to OpenGL, which often translates to better performance.

Listing 1. Simple Example of glMultiDrawArrays
// These arrays are assumed to be sized large enough to hold enough data to
// represent all of the objects in the scene
GLint first[];
GLsizei count[];

// Build our lists of first vertex and vertex count
for (int i = 0; i < num_objects; i++) {
    first[i] = object[n]->first_vertex;
    count[i] = object[n]->vertex_count;
}

// Now make a single call to glDrawArrays
glMultiDrawArrays(GL_TRIANGLES, first, count, num_objects);

 

If the list of objects doesn’t change (or doesn’t change very often), you can build the first and count arrays up front, removing the for-loop from the example entirely. For example, if you have a simple game with enemies and bonus items in a level, you may only need to update the first and count arrays when one of the enemies dies or a bonus item is collected by the player.

Combining Geometry Using Primitive Restart

There are many tools out there that “stripify” geometry. The idea of these tools is that by taking “triangle soup,” which means a large collection of unconnected triangles, and attempting to merge it into a set of triangle strips, performance can be improved. This works because individual triangles each take three vertices to represent, but a triangle strip reduces this to a single vertex per triangle (not counting the first triangle in the strip). By converting the geometry from triangle soup to triangle strips, there is less geometry data to process, and the system should run faster. If the tool does a good job and produces a small number of long strips containing many triangles each, this generally works well. There has been a lot of research into this type of algorithm, and a new method’s success is measured by passing some well-known models through the new “stripifier” and comparing the number and average length of the strips generated by the tool to that produced by current cutting-edge stripifiers.

Despite all of this research, the reality is that a soup can be rendered with a single call to glDrawArrays or glDrawElements, but unless the functionality that is about to be introduced is used, a set of strips needs to be rendered with separate calls to OpenGL. This means that there is likely to be a lot more function calls in a program that uses stripified geometry, and if the stripping application hasn’t done a decent job or if the model just doesn’t lend well to stripification, this can eat any performance gains seen by using strips in the first place. Even functions like glMultiDrawArrays and glMultiDrawElements don’t always help because the graphics hardware may not implement these functions directly, and so OpenGL essentially has to convert them to multiple calls to glDrawArrays internally anyway.

A feature that is almost universally supported by recent graphics hardware and is part of OpenGL is primitive restart. Primitive restart applies to the GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_LINE_STRIP, and GL_LINE_LOOP geometry types. It is a method of informing OpenGL when one strip (or fan or loop) has ended and that another should be started. To indicate the position in the geometry where one strip ends and the next starts, a special marker is placed as a reserved value in the element array. As OpenGL either fetches vertex indices from the element array or generates them internally, in the case of nonindexed draw commands like glDrawArrays, it checks for this special index value and whenever it comes across it, it ends the current strip and starts a new one with the next vertex. This mode is disabled by default but can be enabled by calling

glEnable(GL_PRIMITIVE_RESTART);

and disabled again by calling

glDisable(GL_PRIMITIVE_RESTART);

When primitive restart mode is enabled, OpenGL watches for the special index value as it fetches or generates them and when it comes across it, stops the current strip and starts a new one. To set the index that OpenGL should watch for, call

glPrimitiveRestartIndex(index);

OpenGL watches for the value specified by index and uses that as the primitive restart marker. Because the marker is a vertex index, primitive restart is best used with indexed drawing functions such as glDrawElements. You can still use primitive restart with glDrawArrays, for example. In this case, OpenGL may eventually generate the restart index internally, and when it does, it restarts the primitive. For example, if you set the restart index to ten and then draw 20 vertices using the GL_TRIANGLE_STRIP mode, you get two separate strips.

The default value of the primitive restart index is zero. Because that’s almost certainly the index of a real vertex that will be contained in the model, it’s a good idea to set the restart index to a new value whenever you’re using primitive restart mode. A good value to use is 0xFFFFFFFF because you can be almost certain that it will not be used as a valid index of a vertex. Many stripping tools have an option to either create separate strips or to create a single strip with the restart index in it. The stripping tool may use a predefined index or output the index it used when creating the stripped version of the model (for example, one greater than the number of vertices in the model). You need to know this and set it using the glPrimitiveRestartIndex function to use the output of the tool in your application.

The primitive restart feature is illustrated in Figure 1.

 

Figure 1. Triangle strips generated with primitive restart disabled and enabled.

 

 

In Figure 2, a triangle strip is pictured with the vertices marked with their indices. In (a), the strip is made up of 17 vertices, which produces a total of 15 triangles in a single, connected strip. By enabling primitive restart mode and setting the primitive restart index to 8, vertex 8 is recognized by OpenGL as the special restart marker, and the triangle strip is terminated at vertex 7. This is shown in (b). The actual position of vertex 8 is ignored because this is not seen by OpenGL as the index of a real vertex. The next vertex processed (vertex 9) becomes the start of a new triangle strip. So while 17 vertices are still sent to OpenGL, the result is that two separate triangle strips of 8 vertices and 6 triangles each are drawn.

Open GL : Drawing a lot of Geometry Efficiently (part 3) – Getting Your Data Automatically
Open GL : Drawing a lot of Geometry Efficiently (part 2) – Instanced Rendering