SIGGRAPH '96

Course 22: OpenGL and Window System Integration

OpenGL Portability Notes



Contents

1. Introduction

Though OpenGL is designed for portability there are many factors which influence the portability of an OpenGL application. Most of these factors are related to the window system and operating system interfaces.

This document presents information which will help to maximize OpenGL across different computer systems and within different configurations of similar systems.



2. GLX and Xlib and Mesa

X supports a wide variety of display devices. Dealing with X visuals and colormaps correctly is very important for portability. If you want your application to operate with Mesa in addition to OpenGL then there are several special issues to be aware of.

2.1 Visuals

A GLX-based program will usually obtain an X visual 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.

2.2 Colormaps

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

2.2.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 colormap if the visuals match. 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.

Finally, if one intends to render into several different window 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.

2.2.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 );

2.3 Double buffering

Double buffered visuals are not required by OpenGL. Therefore if one requests double buffering but glXChooseVisual fails, you should try again specifying single buffering. 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 sure to call glFlush to force rendering to complete at critical points.

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 may become evident in this situation.

What if you would like both double buffering and full 24-bit color? 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.

2.4 Alpha planes

Some systems implement the alpha buffer in client or server memory rather than in video or frame buffer memory. This can be quite slow. Do not not request alpha planes if you do not need them. Alpha planes are seldom needed in practice.

2.5 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.



3. Organization for portability

How can an OpenGL application be organized such that the code may be used for several window systems? Or, how can an application be organized to use both OpenGL and another graphics library?

Why would we want to do this? One, we may want our application to work on both X and Windows platforms. Two, we may want to support both OpenGL and IRIS GL (or PEX) during a transition period.

The basics:

The practicality of this depends on the nature and size of the application. One one hand, modern window system toolkits are quite similar in that GUIs are designed with the callback/event loop paradigm: Furthermore, rendering can be encapsulated in wrapper functions which present an API independent of the graphics library.

On the other hand, a complex application may be so tightly integrated with a user interface toolkit or graphics library that it's impractical to support alternative interfaces or libraries.

3.1 An example: Vis5D

Vis5D is a system for interactive visualization of three dimensional atmospheric data. It can use OpenGL, IRIS GL, or PEX for 3-D rendering. An Xlib-based GUI toolkit provides the only user interface at this time but it's quite feasible to write a new one.

OpenGL, IRIS GL and PEX code is isolated into separate source files:

Each file performs the rendering functions defined by a single header file, graphics.h, defining functions such as: which graphics.ogl.c, graphics.gl.c and grahics.pex.c each implements in its own way. The Makefile determines which source file is compiled.

The core of Vis5Ds functionality is isolated from the user interface by an internal API. Everything "below" the API is GUI independent. Everything "above" the API is considered user interface code. While Vis5D's user interface code is substantial, it could be replaced by an alternative toolkit with minimal impact on the rest of the system.

3.2 Graphics library functionality

When supporting multiple graphics libraries, a difficult problem to deal with is subsetting. While OpenGL mandates that all its features be implemented other graphics libraries aren't as stringently defined. PEX implementations, for example, vary greatly in terms of what features are implemented.

The simplest solution to this problem is to only use functionality which is common to all libraries. This can actually be quite practical in simple applications which don't require elaborate renderering techniques.

The other solution is to poll the graphics system to determine its capabilities and work around those it doesn't support. Vis5D, for example, offers volume rendering only on systems with alpha blending capability.

3.3 Multi- window system applications

Suppose your OpenGL application must work on several window system such as X and Microsoft Windows. How can this be accomplished? It depends on the nature of the application.

A small application using GLUT or Tcl/Tk is potentially portable to many systems because GLUT and Tcl/Tk are available on most platforms.

Larger applications which use native window system toolkits will have to be partitioned into modules which isolate window and operating system- specific code.

The OpenGL window system interface call should be considered window system code and not be put in the OpenGL modules. This includes the swapbuffers operation.



4. Transitioning to OpenGL from other libs

As OpenGL becomes the predominate 3-D graphics library there are reasons to port existing applications to OpenGL from older libraries: Porting can take a lot of effort; there aren't many shortcuts. Below are some observations and issues to be aware of.

4.1 PEX to OpenGL

PEX is a 3-D graphics extension to the X Window System. The API is similar to Xlib in that there are many pointers, structures and complicated function calls. OpenGL by comparison is much cleaner and simpler.

Here are the highlights of PEX vs OpenGL and porting:

4.2 IRIS GL to OpenGL

Since OpenGL's roots are in IRIS GL one may expect porting from IRIS GL to OpenGL to be easy. Conceptually, IRIS GL and OpenGL are very similar, but in practice porting is not an easy job.

SGI's OpenGL Porting Guide is a good place to begin a porting project. Below are the highlights of the similarities and differences in OpenGL and IRIS GL.

SGI developers also have at their disposal the toogl utility which converts IRIS GL code to similar OpenGL code. toogl does not do all the work for you but it can at least do much of the more tedious work.

4.2.1 Similarities

Basic Rendering
OpenGL and IRIS GL are very similar in how they specify geometric primitives; both use the begin/vertex/color/normal/end paradigm. In many cases, IRIS GL drawing commands directly map to OpenGL equivalents.

Transformation and viewing
OpenGL and IRIS GL use similar functions for coordinate transformation and viewing. Both have modelview and projection matrices which can be constructed from simple transformation calls (scale, translate, rotate). Be aware that projection functions such as glOrtho() and glFrustum() are multiplied onto the projection matrix rather than replace the projection matrix as IRIS GL's ortho() and window() do.
Immediate mode rendering and display lists
Immediate mode rendering and display list are supported by both libraries. OpenGL, however, does not support editing display lists as IRIS GL does.
Picking and feedback
Picking (selection) works similar in OpenGL and IRIS GL; both use a name stack. Feedback in OpenGL is nicer than IRIS GL because OpenGL feedback is identical on all implementations, while IRIS GL implemented it differently on some systems.
Depth testing, blending, stenciling, accumulation
Depth (Z) buffering, alpha blending, stencil buffers and accumulation buffers are all implemented similarly in OpenGL and IRIS GL. In many cases there is a direct mapping of functions between the libraries.

4.2.2 Differences

OpenGL contains no window system functions like IRIS GL
If your IRIS GL program is a "mixed model" program, using IRIS GL for rendering but X for window/event handling, then much of your window management and event handling code should be portable to OpenGL.

If your IRIS GL program makes heavy use of IRIS GL's input devices, window management, pop-up menus, etc porting will be more difficult. One possibility is to use GLUT. GLUT provides much of the IRIS GL functionality which OpenGL lacks.

Lighting
While OpenGL and IRIS GL lighting are functionally similar, the implementations are quite different. IRIS GL's lmdef() and lmbind() functions are replaced by separate functions for setting light, material, and lighting model parameters in OpenGL. The tables of IRIS GL lighting parameters one might be using can be replaced by display lists in OpenGL.
Texture mapping
IRIS GL supports defining tables of textures, one of which can be bound at a time with texbind(). OpenGL only directly supports one texture map definition at a time. However, the recent texture object extension or display lists can be used to simulate the IRIS GL texture system.
No subsetting of OpenGL
One especially nice difference between IRIS GL and OpenGL is the fact that OpenGL does not allow subsetting. That is, the entire functionality of OpenGL will always be implemented. IRIS GL unfortunately implemented different features on different systems.
These points only describe the high-level differences in the graphics libraries. As mentioned above, the OpenGL Porting Guide goes into much more detail.




Last edited on May 14, 1996 by Brian Paul.