«*DPH 'HYHORSHUV &RQIHUHQFH $GYDQFHG 2SHQ*/ *DPH 'HYHORSPHQW Avoiding 19 Common OpenGL Pitfalls Mark J. Kilgard * NVIDIA Corporation Last updated: ...»
With OpenGL’s state set this way, glReadPixels will have cut each color component by a third before adding the components during luminance conversion. In the previous example of reading back a pixel composed of 0.5 red, 0.5 green, and 0.5 blue, the resulting luminance value is 0.5.
However, as you may be aware, your eye does not equally perceive the contribution of the red, green, and blue color components. A standard linear weighting for combining red, green, and blue into luminance was defined by the National Television Standard Committee (NTSC) when the US color television format was standardized. These weightings are based on the human eye’s sensitivity to different wavelengths of visible light and are based on extensive research. To set up OpenGL to convert RGB to luminance according to the NTSC standard, you would change
OpenGL’s default pixel transfer state like this:
If you are reading back a luminance version of an RGB image that is intended for human viewing, you probably will want to use the NTSC scale factors.
Something to appreciate in all this is how OpenGL itself does not mandate a particular scale factor or bias for combining color components into a luminance value; instead, OpenGL’s flexible pixel path capabilities give the application control. For example, you could easily read back a luminance image where you had suppressed any contribution from the green color component if that was valuable to you by setting the green pixel transfer scale to be 0.0 and re-weighting red and blue appropriately.
You could also use the biasing capability of OpenGL’s pixel transfer path to enhance the
contribution of red in your image by adding a bias like this:
That will add 0.1 to each red component as it is read back.
Please note that the default scale factor is 1.0 and the default bias is 0.0. Also be aware that these same modes are not simply used for the luminance read back case, but all pixel or texture copying, reading, or writing. If you program changes the scales and biases for reading luminance values, it will probably want to restore the default pixel transfer modes when downloading textures.
7. Watch Your Pixel Store Alignment
OpenGL’s pixel store state controls how a pixel rectangle or texture is read from or written to your application’s memory. Consider what happens when you call glDrawPixels. You pass a pointer to the pixel rectangle to OpenGL. But how exactly do pixels in your application’s linear address space get turned into an image?
The answer sounds like it should be straightforward. Since glDrawPixels takes a width and height in pixels and a format (that implies some number of bytes per pixel), you could just assume the pixels were all packed in a tight array based on the parameters passed to glDrawPixels.
Each row of pixels would immediately follow the previous row.
In practice though, applications often need to extract a sub-rectangle of pixels from a larger packed pixel rectangle. Or for performance reasons, each row of pixels is setup to begin on some regular byte alignment. Or the pixel data was read from a file generated on a machine with a different byte order (Intel and DEC processors are little-endian; Sun, SGI, and Motorola processors are big-endian).
Figure 2: Relationship of the image layout pixel store modes.
So OpenGL’s pixel store state determines how bytes in your application’s address space get unpacked from or packed to OpenGL images. Figure 2 shows how the pixel state determines the image layout. In addition to the image layout, other pixel store state determines the byte order and bit ordering for pixel data.
One likely source of surprise for OpenGL programmers is the default state of the GL_PACK_ALIGNMENT and GL_UNPACK_ALIGNMENT values. Instead of being 1, meaning that pixels are packed into rows with no extra bytes between rows, the actual default for these modes is 4.
Say that your application needs to read back an 11 by 8 pixel area of the screen as RGB pixels (3 bytes per pixel, one byte per color component). The following glReadPixels call would read
How large should the pixels array need to be to store the image? Assume that the
GL_UNPACK_ALIGNMENT state is still 4 (the initial value). Naively, your application might call:
Unfortunately, the above code is wrong since it does not account for OpenGL’s default 4-byte row alignment. Each row of pixels will be 33 bytes wide, but then each row is padded to be 4 byte aligned. The effective row width in bytes is then 36. The above malloc call will not allocate enough space; the result is that glReadPixels will write several pixels beyond the allocated range and corrupt memory.
With a 4 byte row alignment, the actual space required is not simply BytesPerPixel × Width × Height, but instead ((BytesPerPixel × Width + 3) 2) 2) × Height. Despite the fact that OpenGL’s initial pack and unpack alignment state is 4, most programs should not use a 4 byte row alignment and instead request that OpenGL tightly pack and unpack pixel rows. To avoid the complications of excess bytes at the end of pixel rows for alignment, change OpenGL’s row
alignment state to be “tight” like this:
Be extra cautious when your program is written assuming a 1 byte row alignment because bugs caused by OpenGL's initial 4 byte row alignment can easily go unnoticed. For example, if such a program is tested only with images and textures of width divisible by 4, no memory corruption problem is noticed since the test images and textures result in a tight row packing. And because lots of textures and images, by luck or design, have a width divisible by 4, such a bug can easily slip by your testing. However, the memory corruption bug is bound to surface as soon as a customer tries to load a 37 pixel width image.
Unless you really want a row alignment of 4, be sure you change this state when using pixel rectangles, 2D and 1D textures, bitmaps, and stipple patterns. And remember that there is a distinct pack and unpack row alignment.
8. Know Your Pixel Store State Keep in mind that your pixel store state gets used for textures, pixel rectangles, stipple patterns, and bitmaps. Depending on what sort of 2D image data you are passing to (or reading back from) OpenGL, you may need to load the pixel store unpack (or pack) state.
Not properly configuring the pixel store state (as described in the previous section) is one common pitfall. Yet another pitfall is changing the pixel store modes to those needed by a particular OpenGL commands and later issuing some other OpenGL commands requiring the Additional OpenGL extensions also use the pixel store state for passing and returning other types of arrayed data to OpenGL. For example, the convolution extension uses the pixel store modes when specifying convolution kernels. The convolution functionality is a part of the OpenGL 1.2 ARB_imaging subset.
original pixel store mode settings. To be on the safe side, it is usually a good idea to save and restore the previous pixel store modes when you need to change them.
Here is an example of such a save and restore. The following code saves the pixel store unpack
GLint swapbytes, lsbfirst, rowlength, skiprows, skippixels, alignment;
/* Save current pixel store state. */ glGetIntegerv(GL_UNPACK_SWAP_BYTES, &swapbytes);
/* Set desired pixel store state. */ glPixelStorei(GL_UNPACK_SWAP_BYTES, GL_FALSE);
Then, this code restores the pixel store unpack modes:
/* Restore current pixel store state. */ glPixelStorei(GL_UNPACK_SWAP_BYTES, swapbytes);
Similar code could be written to save and restore OpenGL’s pixel store pack modes (change UNPACK to PACK in the code above).
With OpenGL 1.1, the coding effort to save and restore these modes is simpler. To save, the
pixel store state, you can call:
Then, this code restores the pixel store unpack modes:
The above routines (introduced in OpenGL 1.1) save and restore the pixel store state by pushing and popping the state using a stack maintained within the OpenGL library.
Observant readers may wonder why glPushClientAttrib is used instead of the shorter glPushAttrib routine. The answer involves the difference between OpenGL client-side and server-side state. It is worth clearly understanding the practical considerations that surround the distinction between OpenGL’s server-side and client-side state.
There is not actually the option to use glPushAttrib to push the pixel store state because glPushAttrib and glPopAttrib only affects the server-state attribute stack and the pixel pack and unpack pixel store state is client-side OpenGL state.
Think of your OpenGL application as a client of the OpenGL rendering service provided by the host computer’s OpenGL implementation.
The pixel store modes are client-side state. However, most of OpenGL’s state is server-side.
The term server-side state refers to the fact that the state actually resides within the OpenGL implementation itself, possibly within the graphics hardware itself. Server-side OpenGL state is concerned with how OpenGL commands are rendered, but client-side OpenGL state is concerned with how image or vertex data is extracted from the application address space.
Server-side OpenGL state is often expensive to retrieve because the state may reside only within the graphics hardware. To return such hardware-resident state (for example with glGetIntegerv) requires all preceding graphics commands to be issued before the state is retrievable. While OpenGL makes it possible to read back nearly all OpenGL server-side state, well-written programs should always avoid reading back OpenGL server-side state in performance sensitive situations.
Client-side state however is not state that will ever reside only within the rendering hardware.
This means that using glGetIntegerv to read back pixel store state is relatively inexpensive because the state is client-side. This is why the above code that explicitly reads back each pixel store unpack mode can be recommended. Similar OpenGL code that tried to save and restore server-side state could severely undermine OpenGL rendering performance.
Consider that whether it is better to use glGetIntegerv and glPixelStorei to explicitly save and restore the modes or whether you use OpenGL 1.1’s glPushClientAttrib and glPopClientAttrib will depend on your situation. When pushing and popping the client attribute stack, you do have to be careful not to overflow the stack. An advantage to pushing and popping the client attribute state is that both the pixel store and vertex array client-side state can be pushed or popped with a single call. Still, you may find that only the pack or only the unpack modes need to be saved and restored and sometimes only one or two of the modes. If that is the case, an explicit save and restore may be faster.
9. Careful Updating that Raster Position OpenGL’s raster position determines where pixel rectangles and bitmaps will be rasterized. The glRasterPos2f family of commands specifies the coordinates for the raster position. The raster position gets transformed just as if it was a vertex. This symmetry makes it easy to position images or text within a scene along side 3D geometry. Just like a vertex, the raster position is logically an (x,y,z,w) coordinate. It also means that when the raster position is specified, OpenGL’s modelview and projection matrix transformations, lighting, clipping, and even texture coordinate generation are all performed on the raster position vertex in exactly the same manner as a vertex coordinate passed to OpenGL via glVertex3f.
While this is all very symmetric, it rarely if ever makes sense to light or generate a texture coordinate for the raster position. It can even be quite confusing when you attempt to render a bitmap based on the current color and find out that because lighting is enabled, the bitmap color gets determined by lighting calculations. Similarly, if you draw a pixel rectangle with texture The buzzword client-server has become closely associated with networking between machines.
However, client-server is really a description of a relationship between entities where the communication is through a well-defined protocol.
The vertex array state is also client-side.
mapping enabled, your pixel rectangle may end up being modulated with the single texel determined by the current raster texture coordinate.