«*DPH 'HYHORSHUV &RQIHUHQFH $GYDQFHG 2SHQ*/ *DPH 'HYHORSPHQW Avoiding 19 Common OpenGL Pitfalls Mark J. Kilgard * NVIDIA Corporation Last updated: ...»
The resulting diffuse material state is what the programmer intended, but the resulting ambient material state is rather unexpectedly (1.0, 1.0, 1.0, 1.0). How did that happen? Well, remember that the color material mode immediately begins tracking the current color when enabled. The initial value for the color material settings is GL_FRONT_AND_BACK and GL_AMBIENT_AND_DIFFUSE (probably not what you expected!).
Since enabling the color material mode immediately begins tracking the current color, both the ambient and diffuse material states are updated to be (1.0, 1.0, 1.0, 1.0). Note that the effect of the initial glMaterialfv is lost. Next, the color material state is updated to just change the front diffuse material. Lastly, the glColor3f invocation changes the diffuse material to (0.3, 0.5, 0.6, 1.0). The ambient material state ends up being (1.0, 1.0, 1.0, 1.0).
The problem in the code fragment above is that the color material mode is enabled before calling glColorMaterial. The color material mode is very effective for efficient simple material changes, but to avoid the above pitfall, always be careful to set glColorMaterial before you enable GL_COLOR_MATERIAL.
15. Much OpenGL State Affects All Primitives
A fragment is OpenGL’s term for the bundle of state used to update a given pixel on the screen.
When a primitive such as a polygon or image rectangle is rasterized, the result is a set of fragments that are used to update the pixels that the primitive covers. Keep in mind that all OpenGL rendering operations share the same set of per-fragment operations. The same applies to OpenGL’s fog and texturing rasterization state.
For example, if you enabled depth testing and blending when you render polygons in your application, keep in mind that when you overlay some 2D text indicating the application’s status that you probably want to disable depth testing and blending. It is easy to forget that this state also affects images drawn and copied with glDrawPixels and glCopyPixels.
You will quickly notice when this shared state screws up your rendering, but also be aware that sometimes you can leave a mode enabled such as blending without noticing the extra expense involved. If you draw primitives with a constant alpha of 1.0, you may not notice that the blending is occurring and simply slowing you down.
This issue is not unique to the per-fragment and rasterization state. The pixel path state is shared by the draw pixels (glDrawPixels), read pixels (glReadPixels), copy pixels (glCopyPixels), and texture download (glTexImage2D) paths. If you are not careful, it is easy to get into situations where a texture download is screwed up because the pixel path was left configured for a pixel read back.
16. Be Sure to Allocate Ancillary Buffers that You Use If you intend to use an ancillary buffer such as a depth, stencil, or accumulation buffer, be sure that you application actually requests all the ancillary buffers that you intend to use. A common interoperability issue is developing an OpenGL application on a system with only a few frame buffer configurations that provide all the ancillary buffers that you use. For example, your system has no frame buffer configuration that advertises a depth buffer without a stencil buffer. So on your development system, you “get away with” not explicitly requesting a stencil buffer.
The problem comes when you take your supposedly debugged application and run it on a new fancy hardware accelerated OpenGL system only to find out that the application fails miserably when attempting to use the stencil buffer. Consider that the fancy hardware may support extra color resolution if you do not request a stencil buffer. If you application does not explicitly request the stencil buffer that it uses, the fancy hardware accelerated OpenGL implementation determines that the frame buffer configuration with no stencil but extra color resolution is the better choice for your application. If your application would have correctly requested a stencil buffer things would be fine. Make sure that you allocate what you use.
17. Do Not Assume a Maximum Extensions String Length
Various implementation-dependent strings can be retrieved with glGetString. You can use glGetString to retrieve the OpenGL version string (GL_VERSION), vendor string (GL_VENDOR), renderer description string (GL_RENDERER), and the extensions supported string (GL_EXTENSIONS).
The pointer returned by glGetString is a character array managed by the OpenGL implementation. The string is read-only and should not be written. Your application should not attempt to free the returned pointer. You should not make any assumptions on the length of the returned strings, but you can depend on the strings being null-terminated so its length can be determined with strlen.
Because the string is write-only, applications often copy these strings to application-allocated memory. A common pitfall is to assume that one or more of these strings is no longer than a fixed size buffer. If a longer-than-expected string is copied into a fixed size buffer that is too small, the result is usually memory corruption and eventually erroneous program behavior, often times a program crash.
This pitfall is particularly common for the OpenGL extensions string. Some OpenGL implementations support a large number of extensions. Indeed, the OpenGL extensions string will only get longer in future OpenGL implementations that will support more and more extensions. The maximum length of the extensions string for today’s OpenGL implementations is sure to be exceeded by tomorrow’s OpenGL implementations. The problem with this pitfall is that it is usually several months or years in the future that some end-user finds that your application crashes when using a new OpenGL implementation. And because the pitfall results in memory corruption, the way the corruption exhibits itself is quite unpredictable.
Here is what to avoid:
extensions = glGetString(GL_EXTENSIONS);
sprintf(buffer, “Extensions: %s\n”, extensions); /* BAD ASSUMPTION: Buffer overflow possible! */
extensions = glGetString(GL_EXTENSIONS);
extensionsLen = strlen(extensions);
buffer = (char*) malloc(extensionsLen+sizeof(“Extensions: \n”));
sprintf(buffer, “Extensions: %s\n”, extensions);
While the extensions string is usually the longest string and the string must subject to growth in the future, this pitfall can equally apply to length assumptions about any of the implementationdependent strings returned by glGetString.
18. Clip Plane and Eye-Linear Texture Coordinate Generation Plane Specification OpenGL supports application-defined clip planes and a mechanism called eye-linear texture coordinate generation. Texture coordinate generation is often called texgen for short. Clip planes provide a way to clip primitives when they fall on the wrong side of an application-specified clip plane. Eye-linear texture coordinate generation is a mechanism for having OpenGL automatically generate texture coordinates (instead of you explicitly specifying the coordinates with glTexCoord) such that the texture coordinates are a linear function of a vertex’s eye-space position.
Both mechanisms require four floating-point parameters that define a plane equation. Plane equations are typically specified as Ax + By + Cz + Dw = 0 where A, B, C, and D are the parameters of the plane equation. Clip planes are specified with the glClipPlane command. Eye-linear texgen planes are specified with the glTexGen command with the GL_EYE_LINEAR parameter.
What may not be obvious to OpenGL programmers, though it is well documented, is that the parameters used to specify the clip plane or eye-linear plane equations are transformed by the inverse of the current modelview matrix. The clip plane and texgen functionality uses the transformed version of the parameters, not the actual parameters that you pass to glClipPlane or glTexGen.
If you don’t understand this, the operation of clip planes and eye-linear texgen is very confusing.
By transforming the clip plane and eye-linear texgen parameters by the inverse of the modelview matrix, the plane equation is transformed into eye space. In fact, this is very convenient in many cases. If the current modelview matrix is loaded with your “look at” transform that defines the mapping from world space to eye space, then you can specify the parameters for the clip plane or eye-linear texgen plane as a world space plane equation.
If you want to specify your clip plane or eye-linear texgen plane directly in eye coordinates, make sure the current modelview matrix is the identity matrix. If you want to specify your clip plane or eye-linear texgen plane in world coordinates, make sure that your modelview matrix is loaded with your “look at” transform (typically specified with gluLookAt).
Other OpenGL state parameters are also transformed into eye space. When you specify a light position with glLightfv and GL_POSITION, the current modelview matrix transforms the light position. When you specify a light’s spotlight direction with glLightfv and GL_SPOT_DIRECTION, the upper leftmost 3x3 portion of the modelview matrix transforms that spotlight direction vector. Again, these automatic transformations are confusing if you are not aware of them, but they are very convenient when
you want to specify the light position or spotlight direction in world space coordinates. In this case, specify your light position and/or spotlight direction when the modelview matrix is loaded with your “look at” transform.
19. The OpenGL header directory name is capitalized This is a pitfall for Windows programmers trying to write portable OpenGL programs. When you include OpenGL’s standard header files, it should be done as follows
Note that the first GL is capitalized in each line. Windows file names are case insensitive (capitalization does not matter). This means on Windows machines, you can get away with writing
On Linux PCs and Unix workstations that support OpenGL, file names are case sensitive so “gl” is a different directory from “GL”. Source code that includes the OpenGL header files with gl/gl.h or gl/glu.h will have to be fixed for the code to compile portably on Linux and Unix systems. Save yourself some grief and always properly capitalize the OpenGL include file names.
Also, if you use GLUT (the OpenGL Utility Toolkit) to develop portable applications, be sure that you always include GL/glut.h and do not explicitly include GL/gl.h or GL/glu.h in your source code. On Windows operating systems, the GL/gl.h and GL/glu.h header files cannot be successfully included without a bunch of Windows-dependent types and macros being setup which are normally defined in windows.h. Unfortunately (or fortunately depending on your predisposition about Microsoft operating systems), windows.h is not available on non-Windows operating systems.
And even under Windows where it is available, including windows.h will define literally thousands of macros, type definitions, and function prototypes that get in the way of writing portable programs. The good news is that GL/glut.h automatically includes GL/gl.h and GL/glu.h for you in a portable way that works on both Windows and non-Windows operating systems. By including GL/glut.h, you can maximize the portability of your GLUT programs to both Windows and nonWindows operating systems.
Conclusion I hope that this review of various OpenGL pitfalls saves you much time and debugging grief. I wish that I could have simply read about these pitfalls instead of learning most of them the hard way.
Visualization has always been the key to enlightenment. If computer graphics changes the world