OpenGL provides functions for allocating OpenGL-specific resouces such as textures, lights, and call lists. These resources are obtained and released through OpenGL API calls. It can be problematic to manage these resources manually. If a resource is not freed this can leak video memory, system resources, and lead to stalled performance and instability.
GeoGL provides "smart" wrapper classes for allocating these objects. These classes can be used almost transparently in place of the OpenGL resource management functions, and provide automatic deallocation when the objects are no longer in scope.
The two most commonly used OpenGL resources are textures and call lists. Textures are normally allocated and freed via the OpenGL API calls glGenTextures(...) and glDeleteTextures(...). Similary, compiled call lists are obtained through the glGenLists(...) and glDeleteLists(...) functions. The goal of the geoGL wrappers is to ensure that every allocated resource has a single corresponding deallocation.
| Misuse of glGenTextures(...) |
|---|
// "Classic" texture loading routine
GLenum loadTexture(char *szFileName)
{
GLenum textureID; // GLenum for texture ID
glGenTextures(1, &textureID); // Make a texture for OpenGL
.
.
.
return textureID; // Return that texture
}
// Code to load textures
void doSomething()
{
GLenum myTexture; // GLenum for texture ID
myTexture = loadTexture("Texture1.bmp"); // Load a texture
glBindTexture(GL_TEXTURE_2D,myTexture); // Bind texture for use
.
.
.
myTexture = loadTexture("Texture2.bmp"); // Load another, oops!!
// Forgot to free it first!
}
|
In the above code, the first texture is not freed before the second one is assigned. The second texture is never freed either. To avoid these mistakes, declare myTexture as a TextureID. This type will automatically free itself when it loses scope, or is reassigned a new value. This is similar to the behavior of the C++ auto_ptr.
| Example using geoGL::TextureID |
|---|
// "Classic" texture loading routine
GLenum loadTexture(char *szFileName)
{
GLenum textureID; // GLenum for texture ID
// Don't use TextureID here!
glGenTextures(1, &textureID); // Make a texture for OpenGL
.
.
.
return textureID; // Return that texture
}
// Code to load textures
void doSomething()
{
TextureID myTexture; // Use TextureID object
myTexture = loadTexture("Texture1.bmp"); // Load a texture
glBindTexture(GL_TEXTURE_2D,myTexture); // Bind texture for use
.
.
.
myTexture = loadTexture("Texture2.bmp"); // Load another, no problem
// This one is freed too.
}
|
The TextureID can be transparently used as the second parameter to the glBindTexture(...) call since it provides a conversion operator to the GLenum type. The second assignment is also valid since the TextureID will automatically free the first texture when the second one is assigned. Be sure that the first texture is not being used or it will suddenly disappear! The second texture is automatically freed at the end of the doSomething() function.
Notice that the loadTexture(...) function does not use the TextureID type. Why not? This is because the TextureID would automatically be freed upon return! The loadTexture function would then be returning invalid texture ID numbers. TextureID objects should also not be copied. If two textureID types referred to the same texture, which one should perform the delete operation? These ambiguities could result in deleting a texture while another TextureID is using it. Worse yet, the OpenGL texture could be deleted twice. Some OpenGL implementations consider this an error, while others may even corrupt memory or crash.
The CallList object functions in the same way. When a CallList is reassigned a value, or leaves scope, it will be freed. The only difference between the two classes is the type of OpenGL ID numbers these classes contain. Do not assign a TextureID to a CallList or vice-verse, or the wrong object will be freed (and OpenGL will be very confused and irritable)
| Example of create and detach on a CallList |
|---|
// Compile a display list
GLenum compileStuff()
{
CallList compileID; // OpenGL CallList
compileID.create(); // Allocate a call list
glNewList(compileID, GL_COMPILE); // Begin OpenGL drawing
.
.
.
glEndList(); // Done with display list
return compileID.detach(); // Detach the ID number
}
// Code to use a compiled display list
void doSomething()
{
CallList myList; // Use CallList object
myList = compileStuff(); // Compile display list
glCallList(myList); // Call display list
// Automatically freed
}
|
The above code uses a CallList in both the compileStuff() and the doSomething() functions. CallList and TextureID provide a create() method which allocates a new ID number more easily than the equivalent OpenGL API call. To prevent the deallocation problem described previously, the detach() method is used on the return statement. This returns a GLenum without freeing the ID contained in the CallList (the CallList "detaches" ownership of the ID).
The RGBA class provides a wrapper for colors by storing the red, green, blue, and alpha color components in a nifty class. It can be used to contain color attributes, and manipulate them mathematically (add, subtract, scale). It is far more convenient than manipulating structures, arrays, or individual color components. RGBA is a template class containing the red, green, and allowing colors to be stored in many forms. Most commonly floats or bytes are used. Typedefs exist for common forms of RGBA. These are fRGBA, dRGBA, iRGBA, bRGBA corresponding with float, double, int, and byte respectively.
| Colors using OpenGL |
|---|
void doSomeStuffWithColors()
{
// Typical OpenGL stuff
glColor3f(1.0f,1.0f,1.0f); // Convenient
GLfloat lightAmbient[] = { 0.2f, 0.2f, 0.2f}; // Obfuscated
glLightfv(GL_LIGHT1, GL_AMBIENT, lightAmbient);
glMaterialfv(GL_FRONT, GL_AMBIENT, lightAmbient);
// Using geoGL::fRGBA
glColor3fv(fWhite); // Convenient!
fRGBA lightAmbient(0.2f,0.2f,0.2f); // Clear
glLightfv(GL_LIGHT1, GL_AMBIENT, lightAmbient);
glMaterialfv(GL_FRONT, GL_AMBIENT, fWhite * 0.2f); // Shortcut
}
|
RGBA provides operators for converting to an array structure compatible with OpenGL, so it can be used as a parameter to OpenGL functions. The structure contains all four color components so it works with functions needing either three or four values. If only 3 are needed, the alpha value will not be used. The fRGBA is the most useful version since many OpenGL implementations are optimized to use floating point values.
| More with RGBA |
|---|
void moreFunWithColors()
{
// Make some colors
fRGBA green(0,1,0);
fRGBA red(1,0,0);
fRGBA yellow = green + red;
fRGBA newColor = yellow/2;
bRGBA magenta(255,0,255); // Some cases call for bytes not floats
// Scalar for 50% brightness
float nBrightness = 0.5f;
// Quick shortcut to set a color
newColor(0.75f,0.5f,0.25f,0.5f); // 50% alpha
// Use fRGBA in place of arrays
glLightfv(GL_LIGHT1, GL_AMBIENT, green * nBrightness);
glLightfv(GL_LIGHT1, GL_DIFFUSE, red * nBrightness);
glLightfv(GL_LIGHT1, GL_SPECULAR, yellow * nBrightness);
if (newColor * nBrightness != yellow)
glMaterialfv(GL_FRONT, GL_AMBIENT, newColor);
}
|
Some predefined colors are defined in geoGL including fwhite, fgray75, fgray50, fgray25, and fblack.
To simplify calls to the OpenGL light functions, geoGL provides a Light3D class. This simple class serves two purposes:
Light3D is not a replacement for the OpenGL lighting commands, it is a tool to ease to management of light settings. When a Light3D object is created, it will allocate a light ID, and when it is destroyed it will release that Light ID. This function is similar to CallList and TextureID except that the ID is allocated automatically upon construction, and it cannot be cleared until destruction. Since OpenGL does not provide functions for managing light ID numbers, geoGL will do this internally. geoGL sets a maximum of 32 lights.
| Lights with OpenGL |
|---|
// "Classic" light setup routine
void setupLight()
{
// Create GLfloat arrays with light settings
GLfloat ambient[] = { 0.2f, 0.2f, 0.2f }; // Light colors
GLfloat diffuse[] = { 1.0f, 1.0f, 1.0f };
GLfloat specular[] = { 0.5f, 0.5f, 0.5f };
int shininess = 64; // Specular exponent
GLfloat position[] = { 0.0f, 0.0f, 10.0f }; // Light position
// OpenGL calls to set these values
glLightfv(GL_LIGHT0,GL_AMBIENT, ambient); // Set color
glLightfv(GL_LIGHT0,GL_DIFFUSE, diffuse);
glLightfv(GL_LIGHT0,GL_SPECULAR, specular);
glLighti (GL_LIGHT0,GL_SHININESS,shininess); // Set shininess
glLightfv(GL_LIGHT0,GL_POSITION, position); // Set position
// Turn on the light
glEnable(GL_LIGHT0);
}
|
The Light3D object stores ambient, diffuse, and specular light values, as well as the position of the light source. These values can be placed into the light by directly modifying member variables, or by using the setLight method. Once the Light3D object contains the proper colors, they can be sent to OpenGL using the executeLight method.
| Lights with Light3D |
|---|
Light3D mainLight;
// Light setup using geoGL::Light3D
void setupLight1()
{
// Assign individual colors
mainLight.ambient = fWhite * 0.2f;
mainLight.diffuse = fRGBA(1,1,1);
mainLight.specular(0.5f, 0.5f, 0.5f);
mainLight.shininess = 64;
// Set the position vector (see fVector3D)
mainLight.position = fVector3D(0.0f, 0.0f, 10.0f);
// Make the OpenGL calls and turn on the light
mainLight.executeColor();
mainLight.on();
}
// Shortcuts with geoGL::Light3D
void setupLight2()
{
// Setup color values with the setLight method
mainLight.setLight(0.2f, 1.0f, 0.5f, 64, fWhite);
// Set the position vector (see fVector3D)
mainLight.position(0.0f, 0.0f, 10.0f);
// Make the OpenGL calls and turn on the light
mainLight.executeColor();
mainLight.on();
}
|
The first code sample uses OpenGL to create a light. The light values are declared in arrays then pushed to OpenGL via API calls. The second code sample shows two ways to use Light3D to ease this process. The first step is to assign the values to the individual light components. Ambient, diffuse, and specular light values are assigned. Next, assign the position vector. This vector is a geoGL::fVector3D, discussed in more detail later on. Once the light values are established, call the executeColor() method to make the OpenGL calls to apply the light settings, and the on() method to enable the light.
In the setupLight1() function, each color value is set individually. This is powerful, but for most purposes unnecessary. If all the light values are the same color, you can use the setLight() method to shortcut the process. This method takes scalars for ambient, diffuse, and specular, then multiplies them times the color passed as the last parameter. Now all the colors can be set in one quick statement. If the color is not specified, white is assumed.
Light3D object is not intended as a simple space saver. The main purpose is to store light values, and manage the ID numbers. If many Light3D objects exist, all lights will receive unique ID numbers. They can be created and destroyed dynamically, and their light components can be changed and re-executed with executeLight() at any time.