Data Optimizations in OpenGL

Strips & Fans

For surfaces comprised of interconnected primitives, reduce the data transfer by using theStrip & Fan Primitives (e.g. GL_TRIANGLE_STRIP & GL_TRIANGLE_FAN)

Display Lists

A display list is a pre-compiled sequence of OpenGL commands. All the lighting calculations, textures, matrix operations and vertex instructions are calculated when the display list is created not when it is displayed. Using display lists, you can define the geometry once and execute multiple times.

Creating a Display List

A display list is created using the glNewList() and glEndList() functions. glNewlist takes two parameters, the first one is an integer uniquely identifying the list, the second parameter, either GL_COMPILE or GL_COMPILE_AND_EXECUTE, tells OpenGL whether to compile the list or to compile and execute (display) immediately.

// define a unique number for this list
#define MYLIST 1
  
// start list
glNewList(MYLIST, GL_COMPILE); // compile list (don't display now)
 	glBegin(...); // start drawing some primitives 
		// draw some stuff
	glEnd();
	// lost of primitives, transforms, lighting functions...
	//...
glEndList(); // list created	

Executing a Display List

To call the display list we created earlier, use;

glCallList(MYLIST);

When the list is executed, its as if the display list commands were inserted into the place where the glCallList() function is placed.

Note that a display list is compiled using the state variables (lighting, shading, depth checking,...) as set when the list is created. So for example, if the lighting mode is enabled when the list is created, but disabled when the list is executed, the object will be rendered lit.

 

Display lists can be used to great effect to draw text in OpenGL.

Vertex Arrays

Specifying all the vertex data (vertex position, material, normal & texture) for large complex objects involves calling functions like glVertex*() or glNormal*() thousands of times. We are drip-feeding the graphics pipeline with data. Often we are forcing the pipeline to process redundant data. Take a simple example of a cube, which has 8 vertices. To render a cube, we typically render the 6 sides individually, requiring 24 points to be processed, a 3-fold reduction in efficiency.

Modern graphics hardware have sophisticated systems for dealing with large vertex related data sets, so it makes sense to be able to deliver our data to the graphics pipeline in bulk.

Vertex arrays allow us to create a series of arrays of vertex data, which can be delivered in a single OpenGL call.

Vertex Arrays are supported for the following vertex data

Using vertex Arrays

Using vertex arrays, requires 3 steps;

  1. Specify which vertex data will be delivered in the form of an array (coordinates, colour, normal vector, texture coordinates and/or edge flags).
  2. Deliver the array containing data to OpenGL
  3. Draw the geometry with the data (what primitive to draw using the vertices in the array)

The first to steps are usually straightforward, the function glEnableClientState() will switch on Vertex Arrays for the different types of data. Delivering the array data requires a special function for each type of data. These are;

Consider the following code;

#define X 0.52573111f
#define Z 0.85065080f

GLfloat vertices[][3]={	{-X,0.0,Z},{X,0.0,Z},{-X,0.0,-Z},{X,0.0,-Z},
						{0.0, Z,X},{0.0,Z,-X},{0.0,-Z,X},{0.0,-Z,-X},
						{Z,X,0.0},{-Z,X,0.0 },{Z,-X,0.0 },{-Z,-X,0.0}
}; // coordinates for an icosahedron


//meanwhile, somewhere else...


	glEnableClientState(GL_VERTEX_ARRAY); // we want to use vertex arrays for coordinate info


	glVertexPointer(3,GL_FLOAT,0,(GLvoid*)vertices); // give openGL our array of vertices

 

We can draw with elements in this array using glArrayElement. glArrayElement(i) selects the ith vertex coordinates from the vertex array. The ith elements from the other arrays (normals, texture, colours etc) are also selected if those arrays are enabled.

	glBegin(GL_TRIANGLES);
	
		glArrayElement(1); 
		glArrayElement(4);
		glArrayElement(0); 	// draw a triangle with the 1st, 4th and 0th 
							// elements in the coordinate array


		glArrayElement(4);
		glArrayElement(9);
		glArrayElement(0);


		...


	}
	glEnd();
  

Index Arrays

 

Hard coding the vertex indices is obviously unsuitable for non-trivial code. A better way would be to store the triangles as an array of indices into the vertex array (see pointer to vertex list). This is coded as follows;

#define X 0.52573111f
#define Z 0.85065080f

GLfloat vertices[][3]={	{-X,0.0,Z},{X,0.0,Z},{-X,0.0,-Z},{X,0.0,-Z},
						{0.0, Z,X},{0.0,Z,-X},{0.0,-Z,X},{0.0,-Z,-X},
						{Z,X,0.0},{-Z,X,0.0 },{Z,-X,0.0 },{-Z,-X,0.0}
}; // coordinates for vertices an icosahedron


GLuint triangles[][3]={	{1,4,0}, {4,9,0},   {4,5,9},   {8,5,4},   {1,8,4},
{1,10,8}, {10,3,8}, {8,3,5}, {3,2,5}, {3,7,2},
{3,10,7}, {10,6,7}, {6,11,7}, {6,0,11}, {6,1,0},
{10,1,6}, {11,0,9}, {2,11,9}, {5,2,9}, {11,2,7}}; // each triplet of integer, represents a triangle, each integer is an index into the vertex array //meanwhile, somewhere else... glEnableClientState(GL_VERTEX_ARRAY); // we want to use vertex arrays for coordinate info glVertexPointer(3,GL_FLOAT,0,(GLvoid*)vertices); // give openGL our array of vertices glBegin(GL_TRIANGLES); for(int i=0;i<20;i++){ glArrayElement(triangles[i][0]); glArrayElement(triangles[i][1]); glArrayElement(triangles[i][2]); } glEnd();

 

DrawElements

In the final example, we will provide an array of normals, so the lighting is correct. We will also use glDrawElements, which is the "mother of all OpenGL drawing commands". Given an array of vertices, we can create an array containing indices to the vertex array. We can use this array to tell OpenGL, which primitive we wish to draw and the order of the primitives;

	void Ring::Draw(void){
	glPushMatrix();
	
	
	glRotatef(angle, 0.1f, 1.0f, 0.9f); //... then rotate everything around y-axis

	glDrawElements(GL_TRIANGLES, num_indices, GL_UNSIGNED_INT,triangles);
	
	glPopMatrix();
}

The above code is almost equivalent to;

	
glBegin(GL_TRIANGLES);
for(int i=0;i< num;i++)
	glArrayElement(quads[i]);
glEnd();

This code (ring.cpp, ring.h)shows an example of this technique in action. Note that ALL the vertex calculations are done once in prog_init(). The only drawing code in Prog_Loop() is a call to glDrawElements(). The Windows .exe for this program is here.