SIGGRAPH '97

Course 24: OpenGL and Window System Integration

GLX Portability Notes



Contents



1. Introduction

GLX is the OpenGL interface to the X Window System. GLX defines both an API and a wire protocol which allows remote display of OpenGL applications on GLX-capable X servers.

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.



2. GLX fundamentals

After we've established a connection to an X server (perhaps with 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.



3. GLX visuals

A GLX visual is basically an X visual augmented with ancillary (depth, stencil, accumulation, etc) buffer information.

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.

A footnote-

I've lost count of how many people have reported that depth buffering doesn't work on system XYZ or doesn't work with Mesa. In all cases the problem has been that the programmer neglected to specify/request a depth-buffered visual. Many OpenGL servers have depth buffers associated with all GLX visuals so even if a depth buffer isn't requested one may get lucky and get a depth-buffered visual anyway.

The point is: be careful that the attribute list passed to glXChooseVisual really specifies what you need.



4. Colormaps

The best way to handle X colormaps depends on whether one is rendering in RGB or color index mode.

4.1 RGB mode colormaps

When rendering in RGB mode the colormap is usually never altered (using a 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.

4.2 Color index mode colormaps

When designing a color index mode application we must decide if we need a writable colormap and/or need specific colors associated with specific pixel values. For lighting and fog effects to work in color index mode one has to store specific colors in consecutive colormap entries. Therefore, a private, writable colormap is required. It should be allocated/created with 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 );


5. Double buffering

Surprisingly, double buffered visuals are not required by OpenGL. If a 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.



6. GLX Pixmaps

GLX pixmaps are used for off-screen OpenGL rendering. A GLX pixmap is basically an X Pixmap augmented with OpenGL ancillary buffers (depth, stencil, etc). The advantages of GLX pixmaps are they take no screen space, are never damaged, and not constrained by the size of the screen. The disadvantage of GLX pixmaps is that 3-D graphics hardware is often unable to render into them; a software renderer executes the OpenGL instructions.

The usual steps in creating and using a GLX pixmap are:

Notes: There is a special problem in using GLX pixmaps with Mesa in RGB mode. Since Mesa supports RGB mode rendering into any kind of X visual it often needs colormap information so that RGB values can be converted into logical pixel values. The GLX pixmap facility does not provide a way to indicate which X colormap is associated with a GLX pixmap.

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.



7. Mesa-specific

Since Mesa doesn't really implement the GLX protocol it isn't 100% compliant with the GLX specification. Most of the significant differences have been explained above. The remaining differences are discussed here.

7.1 GLX_MESA_release_buffers extension

The first time an X window is specified to Mesa's 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 );



Last edited on April 13, 1997 by Brian Paul.