Many OpenGL portability problems can be traced to GLX programming errors. The purpose of this document is to help the GLX programmer avoid a number of common problems.
Information relevant to using Mesa is also included. Even if an OpenGL developer isn't targetting Mesa it's a good idea to be aware of Mesa's idiosyncrasies since it will expand the range of systems on which the application can be used.
XOpenDisplay) we have to check that the X server
actually supports OpenGL and the GLX X server extension.
The glXQueryExtension(dpy, errorBase, eventBase) function
serves this purpose.
The returned errorBase and eventBase values are
usually ignored.
If glXQueryExtension returns false then the application should
inform the user that the display does not support OpenGL.
Next, we'll proceed with GLX setup which includes selecting a GLX visual, creating a GLX context, selecting a colormap and creating a window.
A visual is usually chosen with glXChooseVisual.
Per the OpenGL GLX specification, if an RGB mode is requested,
glXChooseVisual will return either a TrueColor
or DirectColor visual.
Otherwise, a PseudoColor or StaticColor visual
will be returned for color index mode.
Mesa, however, may potentially return any X visual type for RGB mode.
This is because some X displays on which Mesa may be used do not have
TrueColor or DirectColor visuals.
Mese prefers visual types in the order TrueColor,
DirectColor, PseudoColor, StaticColor,
GrayScale, and StaticGray
and visuals depths from deepest to shallowest.
There is one exception: 8-bit PseudoColor is preferred over
8-bit TrueColor. This is a convention many people prefer for
low-end displays which use an 8-bit PseudoColor visual for the
default and only have one hardware colormap.
Similarly, Mesa may return a PseudoColor,
StaticColor, GrayScale or
StaticGray visual if color index mode is requested.
Mesa violates the GLX specification but allows rendering on more types of displays than OpenGL would.
Dealing with Mesa's expanded offering of visuals is mostly just a matter of handling colormaps correctly.
The point is: be careful that the attribute list passed to
glXChooseVisual really specifies what you need.
DirectColor visual may be an exception).
In general we
want to share read-only colormaps among windows to minimize colormap flashing.
Colormap flashing (aka the technicolor effect) occurs when the demand for
colormaps exceeds the hardware's capacity.
As the mouse is moved from window to window different colormaps may be
installed; some windows will be forced to use the wrong colormap.
The following algorithm should pick a good RGB colormap in most cases:
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h> /* for XA_RGB_DEFAULT_MAP atom */
#if defined(__vms)
#include <X11/StdCmap.h> /* for XmuLookupStandardColormap */
#else
#include <X11/Xmu/StdCmap.h> /* for XmuLookupStandardColormap */
#endif
#include <GL/glx.h>
#include <string.h>
/*
* Return an X colormap to use for OpenGL RGB-mode rendering.
* Input: dpy - the X display
* scrnum - the X screen number
* visinfo - the XVisualInfo as returned by glXChooseVisual()
* Return: an X Colormap or 0 if there's a _serious_ error.
*/
Colormap
get_rgb_colormap( Display *dpy, int scrnum, XVisualInfo *visinfo )
{
Atom hp_cr_maps;
Status status;
int numCmaps;
int i;
XStandardColormap *standardCmaps;
Window root = RootWindow(dpy,scrnum);
int using_mesa;
/*
* First check if visinfo's visual matches the default/root visual.
*/
if (visinfo->visual==DefaultVisual(dpy,scrnum)) {
/* use the default/root colormap */
return DefaultColormap( dpy, scrnum );
}
/*
* Check if we're using Mesa.
*/
if (strstr(glXQueryServerString( dpy, scrnum, GLX_VERSION ), "Mesa")) {
using_mesa = 1;
}
else {
using_mesa = 0;
}
/*
* Next, if we're using Mesa and displaying on an HP with the "Color
* Recovery" feature and the visual is 8-bit TrueColor, search for a
* special colormap initialized for dithering. Mesa will know how to
* dither using this colormap.
*/
if (using_mesa) {
hp_cr_maps = XInternAtom( dpy, "_HP_RGB_SMOOTH_MAP_LIST", True );
if (hp_cr_maps
&& visinfo->visual->class==TrueColor
&& visinfo->depth==8) {
status = XGetRGBColormaps( dpy, root, &standardCmaps,
&numCmaps, hp_cr_maps );
if (status) {
for (i=0; i < numCmaps; i++) {
if (standardCmaps[i].visualid==visinfo->visual->visualid) {
Colormap cmap = standardCmaps[i].colormap;
XFree(standardCmaps);
return cmap;
}
}
XFree(standardCmaps);
}
}
}
/*
* Next, try to find a standard X colormap.
*/
#ifndef SOLARIS_BUG
status = XmuLookupStandardColormap( dpy, visinfo->screen,
visinfo->visualid,
visinfo->depth,
XA_RGB_DEFAULT_MAP,
/* replace */ False,
/* retain */ True);
if (status == 1) {
status = XGetRGBColormaps( dpy, root, &standardCmaps,
&numCmaps, XA_RGB_DEFAULT_MAP);
if (status == 1) {
for (i = 0; i < numCmaps; i++) {
if (standardCmaps[i].visualid == visinfo->visualid) {
Colormap cmap = standardCmaps[i].colormap;
XFree(standardCmaps);
return cmap;
}
}
XFree(standardCmaps);
}
}
#endif
/*
* If we get here, give up and just allocate a new colormap.
*/
return XCreateColormap( dpy, root, visinfo->visual, AllocNone );
}
Basically, we use the default/root colormap if the visual matches the
default/root visual.
Otherwise we look for a standard colormap.
If that fails we must allocate a new, private colormap.
If using Mesa on an 8-bit TrueColor HP display then we
look for a special "Color Recovery" colormap which helps to produce
high-quality dithered images.
Caveat: this algorithm may not work on Sun systems due to a bug
in the XmuLookupStandardColormap function.
By defining the SOLARIS_BUG symbol the code in question can
be omitted.
Finally, if one intends to render into several different windows with the same RGB context those window should share the same colormap. This is required with Mesa and helps to reduce colormap flashing with OpenGL.
XCreateColormap( dpy, win, visual, AllocAll ).
Otherwise, if your GLX visual type and depth matches the default/root visual
then you can probably use the default/root colormap.
To allocate a read/write colorcell from the colormap use
XAllocColorCells.
To allocate read-only cells use XAllocColor.
In both cases, X will return to you the index of a colorcell.
If XAllocColor fails then you may have to search the colormap
for a close match.
The following function will search a colormap for the closest match to
your requested color:
#include <X11/Xlib.h>
#include <stdlib.h>
/* A replacement for XAllocColor.
* This function should never fail to allocate a color. When
* XAllocColor fails, we return the nearest matching color. If
* we have to allocate many colors this function isn't a great
* solution; the XQueryColors() could be done just once.
*/
static void
noFaultXAllocColor(Display * dpy, Colormap cmap, int cmapSize, XColor * color)
{
XColor *ctable, subColor;
int i, bestmatch;
double mindist; /* 3*2^16^2 exceeds long int precision. */
/* First try just using XAllocColor. */
if (XAllocColor(dpy, cmap, color))
return;
/* Retrieve color table entries. */
/* XXX alloca canidate. */
ctable = (XColor *) malloc(cmapSize * sizeof(XColor));
for (i = 0; i < cmapSize; i++)
ctable[i].pixel = i;
XQueryColors(dpy, cmap, ctable, cmapSize);
/* Find best match. */
bestmatch = -1;
mindist = 0.0;
for (i = 0; i < cmapSize; i++) {
double dr = (double) color->red - (double) ctable[i].red;
double dg = (double) color->green - (double) ctable[i].green;
double db = (double) color->blue - (double) ctable[i].blue;
double dist = dr * dr + dg * dg + db * db;
if (bestmatch < 0 || dist < mindist) {
bestmatch = i;
mindist = dist;
}
}
/* Return result. */
subColor.red = ctable[bestmatch].red;
subColor.green = ctable[bestmatch].green;
subColor.blue = ctable[bestmatch].blue;
free(ctable);
if (!XAllocColor(dpy, cmap, &subColor)) {
subColor.pixel = (unsigned long) bestmatch;
}
*color = subColor;
}
If your application needs several color index mode windows it's a good
idea to try to share one colormap among the windows.
Finally, be sure that glXChooseVisual returns
a PseudoColor (or for Mesa, GrayScale) visual
if a writable colormap is needed.
After the colormap has been selected you can create your window, specifying
the colormap in the XSetWindowAttributes structure passed to
XCreateWindow.
Furthermore, you should inform the window manager if your top-level window
contains children with non-default colormaps. This is done with the
XSetWMColormapWindows function:
XSetWMColormapWindows( display, top_level_window,
&window_list, num );
glXChooseVisual request for a double buffered visual
fails you should try to get a single buffered visual.
Be sure to call glFlush to force completion of rendering
where glXSwapBuffers would have been called.
Similarly, OpenGL does not require single buffered visuals to be offered.
If you want a single buffered window but glXChooseVisual fails,
you should try again specifying double buffering.
Then, issue glDrawBuffer(GL_FRONT) to direct drawing to the
front color buffer.
Be aware that many systems advertised as having 24-bit color, in fact, only offer 12-bit color in double buffer mode. This is because the 24-bit frame buffer is divided into two 12-bit buffers. Dithering usually makes up for the loss of color accuracy.
Suppose you want both double buffering and full 24-bit color in this situation. For example, during animation one may want double buffering but to show a static image a full-color single buffered window would look best.
IRIS GL allowed one to reconfigure a window to single or double buffering
on the fly with doublebuffer, singlebuffer and
gconfig.
This can't be done with OpenGL.
Instead, you can create two subwindows contained by
a common parent, one window single buffered and the other window double
buffered, and use XMapWidnow/XUnmapWindow to display the one
you want to use.
Remember to use separate contexts for each window since they will have
different visuals.
The usual steps in creating and using a GLX pixmap are:
glXChooseVisual
XCreatePixmap using the depth
of the visual returned by glXChooseVisual
glXCreateGLXPixmap.
glXCreateContext,
usually specifying the indirect option.
glXMakeCurrent
glXMakeCurrent
succeeds.
Mesa (version 1.2.8 and later) has a GLX extension which lets the user specify
the colormap associated with a GLX pixmap. The extension provides a new
function very similar to glXCreateGLXPixmap:
GLXPixmap glXCreateGLXPixmapMESA( Display *dpy, XVisualInfo *visual,
Pixmap pixmap, Colormap cmap )
Strictly speaking, the colormap argument is only needed when rendering in
RGB mode into a GLX pixmap which uses a PseudoColor,
StaticColor, GrayScale or StaticGray
visual.
If the colormap is not specified but is in fact needed, the
glXMakeCurrent call will return False.
The proper way to use this function is:
Pixmap p;
GLXPixmap q;
...
#ifdef GLX_MESA_pixmap_colormap
q = glXCreateGLXPixmapMESA( display, visual, p, colormap );
#else
q = glXCreateGLXPixmap( display, visual, p );
#endif
Since the GLX_MESA_pixmap_color extension symbol is only defined if using
Mesa's header files this technique will be portable to any GLX implementation.
glXMakeCurrent
the X window is augmented with ancillary (back color, depth, stencil, etc)
buffers.
Unfortunately, Mesa's GLX has no way of detecting when the X window is
destroyed with XDestroyWindow.
The best Mesa can do is to check for recently destroyed windows whenever
the client calls the glXCreateContext or
glXDestroyContext functions.
This may not be sufficient in all situations though.
If many windows are used by the application a great deal of memory may be
wasted.
The solution is to call the glXReleaseBuffersMESA function
just before destroying the X window.
For example:
#ifdef GLX_MESA_release_buffers
glXReleaseBuffersMESA( dpy, window );
#endif
XDestroyWindow( dpy, window );