Chapter 4. OpenGL and X: Advanced Topics

This chapter helps you integrate your OpenGL program with the X Window System by describing several advanced topics. While understanding the techniques and concepts described here is not relevant for all applications, it is important that you master them for certain special situations. The chapter covers the following topics:

Using Animations

Animation in its simplest form consists of drawing an image, clearing it, and drawing a new, slightly different one in its place. However, attempting to draw into a window while that window is being displayed can cause problems such as flickering. The solution is double buffering.

Providing example code as appropriate, this section uses the following topics to describe double-buffered animation inside an X Window System environment:

Xt provides two mechanisms that are suited for continuous animation:

  • The section “Controlling an Animation With Workprocs” describes the fastest animation possible. If you use workprocs, the program swaps buffers as fast as possible; this is useful if rendering speed is variable enough that constant speed animation is not possible. Workproc animations also give other parts of the application priority. The controls do not become less responsive just because the animation is being done. The cost of this is that the animation slows down or may stop when the user brings up a menu or uses other controls.

  • The section “Controlling an Animation With Timeouts” describes constant-speed animation. Animations that use timeouts compete on even footing with other Xt events; the animation will not stop because the user interacts with other components of the animation.


    Note: Controlling animations with workprocs and timeouts applies only to Xt-based programs.


Swapping Buffers

A double-buffered animation displays one buffer while drawing into another (undisplayed) buffer then swaps the displayed buffer with the other. In OpenGL, the displayed buffer is called the front buffer, and the undisplayed buffer is called the back buffer. This sort of action is common in OpenGL programs; however, swapping buffers is a window-related function, not a rendering function; therefore, you cannot do it directly with OpenGL.

To swap buffers, use glXSwapBuffers() or, when using the widget, the convenience function GLwDrawingAreaSwapBuffers(). The glXSwapBuffers() function takes a display and a window as input—pixmaps do not support buffer swapping—and swaps the front and back buffers in the drawable. All renderers bound to the window in question continue to have the correct idea of the front buffer and the back buffer. Note that once you call glXSwapBuffers(), any further drawing to the given window is suspended until after the buffers have been swapped.

Silicon Graphics systems support hardware double buffering; this means the buffer swap is instantaneous during the vertical retrace of the monitor. As a result, there are no tearing artifacts; that is, you do not simultaneously see part of one buffer and part of the next.


Note: If the window's visual allows only one color buffer, or if the GLX drawable is a pixmap, glXSwapBuffers() has no effect (and generates no error).

There is no need to worry about which buffer the X server draws into if you are using X drawing functions as well as OpenGL; the X server draws only to the current front buffer and prevents any program from swapping buffers while such drawing is going on. Using the X double buffering extension (DBE), it is possible to render X into the back buffer.

Note that users like uniform frame rates such as 60 Hz, 30 Hz, or 20 Hz. Animation may otherwise look jerky. A slower consistent rate is therefore preferable to a faster but inconsistent rate. For additional information about optimizing frame rates, see “Optimizing Frame Rate Performance” in Chapter 15. See “SGI_swap_control—The Swap Control Extension” in Chapter 11 to learn how to set a minimum period of buffer swaps.

Controlling an Animation With Workprocs

A workproc (work procedure) is a procedure that Xt calls when the application is idle. The application registers workprocs with Xt and unregisters them when it is time to stop calling them.

Note that workprocs do not provide constant-speed animation but animate as fast as the application can.

General Workproc Information

Workprocs can be used to carry out a variety of useful tasks: animation, setting up widgets in the background (to improve application startup time), keeping a file up to date, and so on.

It is important that a workproc executes quickly. While a workproc is running, nothing else can run, and the application may appear sluggish or may even appear to hang.

Workprocs return Booleans. To set up a function as a workproc, first prototype the function then pass its name to XtAppAddWorkProc(). Xt then calls the function whenever there is idle time while Xt is waiting for an event. If the function returns True, it is removed from the list of workprocs; if it returns False, it is kept on the list and is called again when there is idle time.

To explicitly remove a workproc, call XtRemoveWorkProc(). The following shows the syntax for the add and remove functions:

XtWorkProcId XtAppAddWorkProc(XtAppContext app_context,
                              XtWorkProc proc, XtPointer client_data)
void XtRemoveWorkProc(XtWorkProcId id)

Similar to the equivalent parameter used in setting up a callback, the client_data parameter for XtAppAddWorkProc() lets you pass data from the application into the workproc.

Workproc Example

This section illustrates the use of workprocs. The example, motif/animate.c, is a simple animation driven by a workproc. When the user selects “animate” from the menu, the workproc is registered, as follows:

static void
menu(Widget w, XtPointer clientData, XtPointer callData) {
    int entry = (int) clientData;

    switch (entry) {
    case 0:
        if (state.animate_wpid) {
            XtRemoveWorkProc(state.animate_wpid);
            state.animate_wpid = 0;
        } else {
            /* register workproc */
            state.animate_wpid = XtAppAddWorkProc(state.appctx,
                                      redraw_proc, &state.glxwidget);
        }
        break;
    case 1:
        exit(EXIT_SUCCESS);
        break;
    default:
        break;
    }
}

The workproc starts executing if the window is mapped (that is, it could be visible but it may be overlapped):

static void
map_change(Widget w, XtPointer clientData, XEvent *event, Boolean
                                                             *cont) {
    switch (event->type) {
    case MapNotify:
    /* resume animation if we become mapped in the animated state */
        if (state.animate_wpid != 0)
             state.animate_wpid = XtAppAddWorkProc(state.appctx,
                                        redraw_proc, &state.glxwidget);
        break;
    case UnmapNotify:
    /* don't animate if we aren't mapped */
        if (state.animate_wpid) XtRemoveWorkProc(state.animate_wpid);
        break;
    }
}

If the window is mapped, the workproc calls redraw_proc():

static Boolean
redraw_proc(XtPointer clientData) {
    Widget *w = (Widget *)clientData;
    draw_scene(*w);
    return False;        
    /*call the workproc again as possible*/
}

The redraw_proc() function, in turn, calls draw_scene(), which swaps the buffers. Note that this program does not use glXSwapBuffers(), but instead the convenience function GLwDrawingAreaSwapBuffers().

static void
draw_scene(Widget w) {
    static float rot = 0.;

    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f(.1, .1, .8);
    glPushMatrix();
    if ((rot += 5.) > 360.) rot -= 360.;
    glRotatef(rot,0.,1.,0.);
    cube();
    glScalef(0.3,0.3,0.3);
    glColor3f(.8, .8, .1);
    cube();
    glPopMatrix();
    GLwDrawingAreaSwapBuffers(w);
}


Note: If an animation is running and the user selects a menu command, the event handling for the command and the animation may end up in a race condition.


Controlling an Animation With Timeouts

The program that performs an animation using timeouts is actually quite similar to the one using workprocs. The main difference is that the timeout interval has to be defined and functions that relied on the workproc now have to be defined to rely on the timeout. Note especially that redraw_proc() has to register a new timeout each time it is called.

You may find it most helpful to compare the full programs using xdiff or a similar tool. This section briefly points out the main differences between two example programs.

  • The redraw procedure is defined to have an additional argument, an interval ID.

    From work_animate: static Boolean redraw_proc(XtPointer clientData);
    From time_animate: static Boolean redraw_proc(XtPointer clientData, 
                                            XtIntervalId *id);
    

  • In time_animate, a timeout has to be defined; the example chooses 10 ms:

    #define TIMEOUT 10 /*timeout in milliseconds*/ 
    

  • In the state structure, which defines the global UI variables, the interval ID instead of the workproc ID is included.

    From work_animate:
    static struct {        /* global UI variables; keep them together */
        XtAppContext appctx;
        Widget glxwidget;
        Boolean direct;
        XtWorkProcId animate_wpid;
    } state;
    From time_animate:
    static struct {        /* global UI variables; keep them together */
        XtAppContext appctx;
        Widget glxwidget;
        Boolean direct;
        XtIntervalId animate_toid;
    } state;
    

  • The menu() function and the map_change() function are defined to remove or register the timeout instead of the workproc. The following are the two menu() functions as an example:

    From work_animate:
    static void
    menu(Widget w, XtPointer clientData, XtPointer callData) {
        int entry = (int) clientData;
    
        switch (entry) {
        case 0:
            if (state.animate_wpid) {
                XtRemoveWorkProc(state.animate_wpid);
                state.animate_wpid = 0;
            } else {
                /* register work proc */
                state.animate_wpid = XtAppAddWorkProc(state.appctx, 
                                         redraw_proc, &state.glxwidget);
            }
            break;
        case 1:
            exit(EXIT_SUCCESS);
            break;
        default:
            break;
        }
    }
    From time_animate:
    static void
    menu(Widget w, XtPointer clientData, XtPointer callData) {
        int entry = (int) clientData;
    
        switch (entry) {
        case 0:
            if (state.animate_toid) {
                XtRemoveTimeOut(state.animate_toid);
                state.animate_toid = 0;
            } else {
                /* register timeout */
                state.animate_toid = XtAppAddTimeOut(state.appctx,
                                TIMEOUT, redraw_proc, &state.glxwidget);
            }
            break;
        case 1:
            exit(EXIT_SUCCESS);
            break;
        default:
            break;
        }
    }
    

  • The redraw_proc() function has to register a new timeout each time it is called. Note that this differs from the workproc approach, where the application automatically continues animating as long as the system is not doing something else.

    static void
    redraw_proc(XtPointer clientData, XtIntervalId *id) {
        Widget *w = (Widget *)clientData;
        draw_scene(*w);
        /* register a new timeout */
        state.animate_toid = XtAppAddTimeOut(state.appctx, TIMEOUT, 
                                         redraw_proc, &state.glxwidget);
    }
    

Using Overlays

Overlays are useful in situations where you want to preserve an underlying image while displaying some temporary information. Examples for this are popup menus, annotations, or rubber banding. Using the following topics, this section explains overlays and shows you how to use them :

Introduction to Overlays

An overlay plane is a set of bitplanes displayed preferentially to the normal planes. Non-transparent pixels in the overlay plane are displayed in preference to the underlying pixels in the normal planes. Windows in the overlay planes do not damage windows in the normal plane.

If you have something in the main window that is fairly expensive to draw into and want to have something else on top, such as an annotation, you can use a transparent overlay plane to avoid redrawing the more expensive main window. Overlays are well-suited for popup menus, dialog boxes, and “rubber-band” image resizing rectangles. You can also use overlay planes for text annotations floating “over” an image and for certain transparency effects.

Notes:

  • Transparency discussed here is distinct from transparency effects based on alpha buffer blending. See the section “Blending” in Chapter 7, “Blending, Anti-Aliasing, and Fog,” in the OpenGL Programming Guide.

  • On Silicon Graphics systems running the XFree86 server (for example, Onyx4 and Silicon Graphics Prism systems), you must configure the XFree86 server to support overlay planes. Refer to the platform-specific documentation for the details of configuring XFree86.

    Figure 4-1. Overlay Plane Used for Transient Information

    Overlay Plane Used for Transient Information

A special value in the overlay planes indicates transparency. On Silicon Graphics systems, it is always the value zero. Any pixel with the value zero in the overlay plane is not painted to allow the color of the corresponding pixel in the normal planes to show.

The concepts discussed in this section apply more generally to any number of framebuffer layers, for example, underlay planes (which are covered up by anything in equivalent regions of higher-level planes).

You can use overlays in the following two ways:

  • To draw additional graphics in the overlay plane on top of your normal plane OpenGL widget, create a separate GLwMDrawingArea widget in the overlay plane and set the GLX_LEVEL resource to 1. Position the overlay widget on top of the normal plane widget.

    Note that since the GLwMDrawingArea widget is not a manager widget, it is necessary to create both the normal and overlay widgets as children of some manager widget—for example, a form—and have that widget position the two on top of each other. Once the windows are realized, you must call XRaiseWindow() to guarantee that the overlay widget is on top of the normal widget. Code fragments in “Creating Overlays” illustrate this. The whole program is included as overlay.c in the source tree.

  • To create menus, look at examples in /usr/src/X11/motif/overlay_demos. They are present if you have the motif_dev.sw.demo subsystem installed. Placing the menus in the overlay plane avoids the need for expensive redrawing of the OpenGL window underneath them. While the demos do not deal specifically with OpenGL, they do show how to place menus in the overlay plane.

Creating Overlays

This section explains how to create overlay planes, using an example program based on Motif. If you create the window using Xlib, the same process is valid (and a parallel example program is available in the example program directory).

The example program from which the code fragments are taken, motif/overlay.c, uses the visual info extension to find a visual with a transparent pixel. See “EXT_visual_info—The Visual Info Extension” in Chapter 6 for more information.


Note: This example uses the visual info extension, which is supported on all current Silicon Graphics graphics systems. The visual info extension has also been promoted to a core feature of GLX 1.3. With new applications, use the GLX 1.3 interface instead of the extension.

To create the overlay, follow these steps:

  1. Define attribute lists for the two widgets (the window and the overlay). For the overlay, specify GLX_LEVEL as 1 and GLX_TRANSPARENT_TYPE_EXT as GLX_TRANSPARENT_RGB_EXT.

    static int attribs[] = { GLX_RGBA, GLX_DOUBLEBUFFER, None};
    static int ov_attribs[] = { 
                      GLX_BUFFER_SIZE, 2,
                      GLX_LEVEL, 1,
                      GLX_TRANSPARENT_TYPE_EXT, GLX_TRANSPARENT_RGB_EXT,
                      None };
    

  2. Create a frame and form, create the window widget, and attach it to the form on all four sides. Add expose, resize, and input callbacks.

    /* specify visual directly */
    if (!(visinfo = glXChooseVisual(dpy, DefaultScreen(dpy), attribs)))
    XtAppError(appctx, "no suitable RGB visual");
    
    /* attach to form on all 4 sides */
    n = 0;
    XtSetArg(args[n], XtNx, 0); n++;
    XtSetArg(args[n], XtNy, 0); n++;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], GLwNvisualInfo, visinfo); n++;
    state.w = XtCreateManagedWidget("glxwidget", 
        glwMDrawingAreaWidgetClass, form, args, n);
    XtAddCallback(state.w, GLwNexposeCallback, expose, NULL);
    XtAddCallback(state.w, GLwNresizeCallback, resize, &state);
    XtAddCallback(state.w, GLwNinputCallback, input, NULL);
    state.cx = glXCreateContext(dpy, visinfo, 0, GL_TRUE);
    

  3. Using the overlay visual attributes specified in step 1 and attaching it to the same form as the window, create the overlay widget. This assures that when the window is moved or resized, the overlay is moved or resized as well.

    if (!(visinfo = glXChooseVisual(dpy, DefaultScreen(dpy), 
        ov_attribs)))
        XtAppError(appctx, "no suitable overlay visual");
    XtSetArg(args[n-1], GLwNvisualInfo, visinfo);
    ov_state.w = XtCreateManagedWidget("overlay", 
        glwMDrawingAreaWidgetClass, form, args, n);
    

  4. Add callbacks to the overlay.

    XtAddCallback(ov_state.w, GLwNexposeCallback, ov_expose, NULL);
    XtAddCallback(ov_state.w, GLwNresizeCallback, resize, &ov_state);
    XtAddCallback(ov_state.w, GLwNinputCallback, input, NULL);
    ov_state.cx = glXCreateContext(dpy, visinfo, 0, GL_TRUE);
    

    Note that the overlay uses the same resize and input callback:

    • For resize, you may or may not wish to share callbacks, depending on the desired functionality; for example, if you have a weathermap with annotations, both should resize in the same fashion.

    • For input, the overlay usually sits on top of the normal window and receives the input events instead of the overlay window. Redirecting both to the same callback guarantees that you receive the events, regardless of which window actually received them.

    • The overlay has its own expose function: each time the overlay is exposed, it redraws itself.

  5. Call XRaiseWindow() to make sure the overlay is on top of the window.

       XRaiseWindow(dpy, XtWindow(ov_state.w));
    

Overlay Troubleshooting

This section gives some advice on issues that can easily cause problems in a program using overlays:

  • Colormaps

    Overlays have their own colormaps. Therefore, you should call XSetWMColormapWindows() to create the colormap, populate it with colors, and to install it.


    Note: Overlays on Silicon Graphics systems reserve pixel 0 as the transparent pixel. If you attempt to create the colormap with AllocAll, the XCreateColormap() function will fail with a BadAlloc X protocol error. Instead of AllocAll, use AllocNone and allocate all the color cells except 0.


  • W indow hierarchy

    Overlay windows are created like other windows; their parent window depends on what you pass in at window creation time. Overlay windows can be part of the same window hierarchy as normal windows and can be children of the normal windows. An overlay and its parent window are handled as a single hierarchy for events like clipping, event distribution, and so on.

  • Color limitations

    Most Silicon Graphics systems support 8-bit overlay planes. In some cases, as with Onyx4 and Silicon Graphics Prism systems, overlay planes and stereo visuals may be mutually exclusive, as chosen when the X server is initialized.

  • Input events

    The overlay window usually sits on top of the normal window. Thus, it receives all input events such as mouse and keyboard events. If the application is only waiting for events on the normal window, it will not get any of those events. It is necessary to select events on the overlay window as well.

  • Missing overlay visuals

    On Silicon Graphics systems running the XFree86 server (for example, Onyx4 and Silicon Graphics Prism systems), there may be no overlay planes configured. Hence, there will be no visuals at framebuffer levels other than 0. If glXChooseVisual() returns no visuals when GLX_LEVEL is specified as 1 in the attribute list, the application must use a different strategy to display content that would otherwise go in the overlay planes.

  • Not seeing the overlay

    Although overlay planes are conceptually considered to be “above” the normal plane, an overlay window can be below a normal window and thus clipped by it. When creating an overlay and a normal window, use XRaiseWindow() to ensure that the overlay window is on top of the normal window. If you use Xt, you must call XRaiseWindow() after the widget hierarchy has been realized.

Rubber Banding

Rubber banding can be used for cases where applications have to draw a few lines over a scene in response to a mouse movement. An example is the movable window outline that you see when resizing or moving a window. Rubber banding is also used frequently by drawing programs.

The 4Dwm window manager provides rubber banding for moving and resizing windows. However, if you need rubber banding features inside your application, you must manage it yourself.

The following procedure is the best way to perform rubber banding with overlays (this is the method used by 4Dwm, the default Silicon Graphics window manager):

  1. Map an overlay window with its background pixmap set to None (background is passed in as a parameter to XCreateWindow()).

    This window should be as large as the area over which rubber banding could take place.

  2. Draw rubber bands in the new overlay window.

    Ignore resulting damage to other windows in the overlay plane.

  3. Unmap the rubber band window.

    This action causes Expose events to be sent to other windows in the overlay plane .

Using Popup Menus With the GLwMDrawingArea Widget

Popups are used by many applications to allow user input. A sample program, simple-popup.c, is included in the source tree. It uses the function XmCreateSimplePopupMenu() to add a popup to a drawing area widget.

Note that if you are not careful when you create a popup menu as a child of GLwMDrawingArea widget, you may get a BadMatch X protocol error. The menu (like all other Xt shell widgets) inherits its default colormap and depth from the GLwMDrawingArea widget but its default visual from the parent (root) window. Because the GLwMDrawingArea widget is normally not the default visual, the menu inherits a nondefault depth and colormap from the GLwMDrawingArea widget but also inherits its visual from the root window (that is, inherits the default visual); this leads to a BadMatch X protocol error. For more details and for information on finding the error, see “Inheritance Issues” in Chapter 3.

The following are two ways to work around this problem:

  • Specify the visual, depth, and colormap of the menu explicitly. If you do that, consider putting the menu in the overlay plane.

  • Make the menu a child of a widget that is in the default visual; for example, if the GLwMDrawingArea widget is a child of an XmFrame, make the menu a child of XmFrame as well. Example 4-1 provides a code fragment from motif/simple-popup.c.

    Example 4-1. Popup Code Fragment

    static void
    create_popup(Widget parent) {
        Arg args[10];
        static Widget popup;
        int n;
        XmButtonType button_types[] = {
            XmPUSHBUTTON, XmPUSHBUTTON, XmSEPARATOR, XmPUSHBUTTON, };
       
        XmString button_labels[XtNumber(button_types)];
    
        button_labels[0] = XmStringCreateLocalized(“draw filled”);
        button_labels[1] = XmStringCreateLocalized(“draw lines”);
        button_labels[2] = NULL;
        button_labels[3] = XmStringCreateLocalized(“quit”);
    
        n = 0;
        XtSetArg(args[n], XmNbuttonCount, XtNumber(button_types)); n++;
        XtSetArg(args[n], XmNbuttonType, button_types); n++;
        XtSetArg(args[n], XmNbuttons, button_labels); n++;
        XtSetArg(args[n], XmNsimpleCallback, menu); n++;
        popup = XmCreateSimplePopupMenu(parent, “popup”, args, n);
        XtAddEventHandler(parent, ButtonPressMask, False, activate_menu,
                         &popup);
        XmStringFree(button_labels[0]);
        XmStringFree(button_labels[1]);
        XmStringFree(button_labels[3]);
    }
    main(int argc, char *argv[]) {
        Display        *dpy;
        XtAppContext    app;
        XVisualInfo    *visinfo;
        GLXContext      glxcontext;
        Widget          toplevel, frame, glxwidget;
    
        toplevel = XtOpenApplication(&app, “simple-popup”, NULL, 0, &argc,
                      argv, fallbackResources, applicationShellWidgetClass,
                      NULL, 0);
        dpy = XtDisplay(toplevel);
    
        frame = XmCreateFrame(toplevel, “frame”, NULL, 0);
        XtManageChild(frame);
    
        /* specify visual directly */
        if (!(visinfo = glXChooseVisual(dpy, DefaultScreen(dpy), attribs)))
            XtAppError(app, “no suitable RGB visual”);
    
        glxwidget = XtVaCreateManagedWidget(“glxwidget”, 
                       glwMDrawingAreaWidgetClass, frame, GLwNvisualInfo, 
                       visinfo, NULL);
        XtAddCallback(glxwidget, GLwNexposeCallback, expose, NULL);
        XtAddCallback(glxwidget, GLwNresizeCallback, resize, NULL);
        XtAddCallback(glxwidget, GLwNinputCallback, input, NULL);
    
        create_popup(frame);
    
        XtRealizeWidget(toplevel);
    
        glxcontext = glXCreateContext(dpy, visinfo, 0, GL_TRUE);
        GLwDrawingAreaMakeCurrent(glxwidget, glxcontext);
    
        XtAppMainLoop(app);
    }
    


Using Visuals and Framebuffer Configurations

This section explains how to choose and use visuals and on Silicon Graphics workstations. It uses the following topics:

Some Background on Visuals

An X visual defines how pixels in a window are mapped to colors on the screen. Each window has an associated visual, which determines how pixels within the window are displayed on screen. GLX overloads X visuals with additional framebuffer capabilities needed by OpenGL.

Table 4-1 lists the X visuals supported for different types of OpenGL rendering, indentifies the Silicon Graphics systems supporting the X visuals, and indicates whether the colormaps for those visuals are writable or not.

Table 4-1. X Visuals and Supported OpenGL Rendering Modes

OpenGL Rendering Mode

X Visual

Writable Colormap?

Supporting Systems

RGBA

TrueColo r

No

All

RGBA

DirectColor

Yes

Onyx4 and Silicon Graphics Prism systems

color index

PseudoColor

Yes

All except Onyx4 and Silicon Graphics Prism systems

color index

StaticColor

No

Not supported

Not supported

GrayScale

Yes

Not supported

Not supported

StaticGray

No

Not supported

Depending on the available hardware and software, an X server can provide multiple visuals. Each server has a default visual, which can be specified when the server starts. You can determine the default visual with the Xlib macro DefaultVisual().

Because you cannot predict the configuration of every X server, and you may not know the system configuration where your program will run , it is best to find out what visual classes are available on a case-by-case basis.

  • From the command line, use the xdpyinfo command for a list of all visuals the server supports.

  • Usethe glxinfo or findvis command to find visuals that are capable of OpenGL rendering. The findvis command can actually look for available visuals with attributes you specify. See the man page for more information.

  • From within your application, use the Xlib functions XGetVisualInfo() and XMatchVisualInfo()—or glXGetConfig()—or the GLX function glXChooseVisual().


    Note: For most applications, using OpenGL RGBA color mode and a TrueColor visual is recommended.


Running OpenGL Applications Using a Single Visual


Note: This section applies only to IRIS IM.

In previous chapters, this guide has assumed separate visuals for the X and OpenGL portions of the program. The top-level windows and all parts of the application that are not written in OpenGL use the default visual (typically 8-bit PseudoColor, but it depends on the configuration of the server). OpenGL runs in a single window that uses an OpenGL visual.

An alternative approach is to run the whole application using an OpenGL visual. To do this, determine the suitable OpenGL visual (and colormap and pixel depth) at the start of the program and create the top-level window using that visual (and colormap and pixel depth). Other windows, including the OpenGL window, inherit the visual. When you use this approach, there is no need to use the GLwMDrawingArea widget; the standard IRIS IM XmDrawingArea works just as well.

The advantages of using a single visual include the following:

  • Simplicity

    Everything uses the same visual; so, you do not have to worry about things like colormap installation more than once in the application. However, if you use the GLwMDrawingArea widget, it does colormap installation for you; see “Drawing-Area Widget Setup and Creation” in Chapter 3.

  • Reduced colormap flashing

    Colormap flashing happens if several applications are running, each using its own colormap, and you exceed the system's capacity for installed hardware colormaps. Flashing is reduced for a single visual because the entire application uses a single colormap. The application can still cause other applications to flash, but all recent Silicon Graphics systems have multiple hardware colormaps to reduce flashing.

  • Easier mixing of OpenGL and X

    If you run in a single visual, you can render OpenGL to any window in the application, not just to a dedicated window. For example, you could create an XmDrawnButton and render OpenGL into it.

The advantages of using separate visuals for X and OpenGL include the following:

  • Consistent colors in the X visual

    If the OpenGL visual has a limited number of colors, you may want to allow more colors for X. For example, if you are using double buffering on an 8-bit machine, you have only 4 bitplanes (16 colors) per buffer. You can have OpenGL dither in such a circumstance to obtain approximations of other colors, but X will not dither; so, if you are using the same visual for OpenGL and X, the X portion of your application will be limited to 16 colors as well.

    This limiting of colors would be particularly unfortunate if your program uses the Silicon Graphics color-scheme system. While X chooses a color as close as possible to the requested color, the choice is usually noticeably different from the requested color. As a result, your application looks noticeably different from the other applications on the screen.

  • Memory savings

    The amount of memory used by a pixmap within the X server depends on the depth of the associated visual. Most applications use X pixmaps for shadows, pictures, and so on that are part of the user interface widgets. If you are using a 12-bit or 24-bit visual for OpenGL rendering and your program also uses X pixmaps, those pixmaps would use less memory in the default 8-bit visual than in the OpenGL visual

  • Easier menu handling in IRIS IM

    If the top-level shell is not in the default visual, there will be inheritance problems during menu creation (see “Inheritance Issues” in Chapter 3). You must explicitly specify the visual depth and colormap when creating a menu. For cascading menus, specify depth and colormap separately for each pane.

Using Framebuffer Configurations

The framebuffer configuration functions in GLX 1.3 are analogous to GLX visuals but provide the following additional features:

  • They introduce a new way to describe the capabilities of a GLX drawable—that is, to describe the resolution of color buffer components and the type and size of ancillary buffers by providing a GLXFBConfig construct (also called an FBConfig).

  • They relax the “similarity” requirement when associating a current context with a drawable.

  • They support RGBA rendering to one- and two-component windows (luminance and luminance alpha rendering) and GLX pixmaps as well as pixel buffers (pbuffers). Section “Using Pixel Buffers” describes pbuffers.

The following are reasons to use framebuffer configurations:

  • To use pbuffers.

  • To render luminance data to a TrueColor visual.

  • To replace glXChooseVisual(), because framebuffer configurations provide visual selection for all GLX drawables, including pbuffers, and incorporates the visual info and visual rating extensions.

This section briefly describes the following features framebuffer configurations provide:

Describing a Drawable With a GLXFBConfig Construct (FBConfig)

Currently, GLX overloads X visuals so that they have additional buffers and other characteristics needed for OpenGL rendering. FBConfigs package GLX drawables by defining a new construct, a GLXFBConfig, which encapsulates GLX drawable capabilities and has the following properties:

  • It may or may not have an associated X visual. If it does have an associated X visual, then it is possible to create windows that have the capabilities described by the FBConfig.

  • A particular FBConfig is not required to work with all GLX drawables. For example, it is possible for implementations to export FBConfigs that work only with GLX pixmaps.

Less-Rigid Similarity Requirements When Matching Context and Drawable

In OpenGL without FBConfigs, if you associate a drawable with a GLX context by calling glXMakeCurrent(), the two have to be similar—that is, created with the same visual. FBConfigs relax the requirement; they only require the context and drawable to be compatible. This is less restrictive and implies the following:

  • The render_type attribute for the context must be supported by the FBConfig that created the drawable. For example, if the context was created for RGBA rendering, it can be used only if the FBConfig supports RGBA rendering.

  • All color buffers and ancillary buffers that exist in both FBConfigs must have the same size. For example, a GLX drawable that has a front left buffer and a back left buffer with red, green, and blue sizes of 4 is not compatible with an FBConfig that has only a front left buffer with red, green, and blue sizes of 8. However, it is compatible with an FBConfig that has only a front left buffer if the red, green, and blue sizes are 4.

Note that when a context is created, it has an associated rendering type, GLX_RGBA_TYPE or GLX_COLOR_INDEX_TYPE.

Less-Rigid Match of GLX Visual and X Visual

The current GLX specification requires that the GLX_RGBA visual attribute be associated only with TrueColor and DirectColor X visuals. FBConfigs make it possible to do RGBA rendering to windows created with visuals of type PseudoColor, StaticColor, GrayScale, and StaticGray. In each case, the red component is used to generate the framebuffer values and the green and blue fragments are discarded.

The OpenGL RGBA rendering semantics are more powerful than the OpenGL index rendering semantics. By extending the X visual types that can be associated with an RGBA color buffer, FBConfigs allow RGBA rendering semantics to be used with pseudo-color and gray-scale displays. A particularly useful application of FBConfigs is that they allow you to work with single-component images with texture mapping, then use a pseudo-color visual to map the luminance values to color.

FBConfig Constructs

An FBConfig describes the format, type, and size of the color and ancillary buffers for a GLX drawable. If the GLX drawable is a window, then the FBConfig that describes it has an associated X visual; for a GLXPixmap or GLXPbuffer, there may or may not be an X visual associated with the FBConfig.

Choosing an FBConfig Construct

Use glXGetFBConfigs() to get a list of all FBConfigs that are available on the specified screen. The format of the function is as follows:

GLXFBConfig *glXGetFBConfigs(Display *dpy, int screen,int *nitems)

The number of FBConfigs returned is stored in the value nitems.

Use glXChooseFBConfig() to get FBConfig constructs that match a specified list of attributes:

GLXFBConfig *glXChooseFBConfig(Display *dpy, int screen,
                                      const int *attrib_list, int *nitems)

Like glXGetFBConfigs(), function glXChooseFBConfig() returns an array of FBConfigs that are available on the specified screen if attrib_list is NULL; otherwise, this call returns an array of FBConfigs that match the specified attributes. Table 4-2 shows only attributes specific to FBConfigs; additional attributes are listed on the glXChooseVisual man page.

Table 4-2. Visual Attributes Introduced by the FBConfigs

Attribute

Type

Description

GLX_DRAWABLE_TYPE

bitmask

Mask indicating which GLX drawables are supported. Valid bits are GLX_WINDOW_BIT and GLX_PIXMAP_BIT.

GLX_RENDER_TYPE

bitmask

Mask indicating which OpenGL rendering modes are supported. Valid bits are GLX_RGBA_BIT and GLX_COLOR_INDEX_BIT.

GLX_X_RENDERABLE

boolean

True if X can render to drawable.

GLX_FBCONFIG_ID

XID

XID of the FBConfig.

The attributes are matched in an attribute-specific manner. Some attributes, such as GLX_LEVEL, must match the specified value exactly; others, such as GLX_RED_SIZE, must meet or exceed the specified minimum values.

The sorting criteria are defined as follows:

smaller 

FBConfigs with an attribute value that meets or exceeds the specified value are matched. Precedence is given to smaller values. When a value is not explicitly requested, the default is implied.

larger  

When the value is requested explicitly, only FBConfigs with a corresponding attribute value that meets or exceeds the specified value are matched. Precedence is given to larger values. When the value is not requested explicitly, larger behaves exactly smaller.

exact 

Only FBConfigs whose corresponding attribute value exactly matches the requested value are considered.

mask 

For an FBConfig to be considered, all the bits that are set in the requested value must be set in the corresponding attribute. Additional bits might be set in the attribute.

Table 4-3 illustrates how each attribute is matched. Note that “No effect” means that the default behavior is to have no preference when searching for a matching FBConfig.

Table 4-3. FBConfig Attribute Defaults and Sorting Criteria

Attribute

Default

Sorting Criteria

GLX_BUFFER_SIZE

0

Smaller

GLX_LEVEL

0

Smaller

GLX_DOUBLEBUFFER

No effect

Smaller

GLX_STEREO

False

Exact

GLX_AUX_BUFFERS

0

Smaller

GLX_RED_SIZE

0

Larger

GLX_GREEN_SIZE

0

Larger

GLX_BLUE_SIZE

0

Larger

GLX_ALPHA_SIZE

0

Larger

GLX_DEPTH_SIZE

0

Larger

GLX_STENCIL_SIZE

0

Larger

GLX_ACCUM_RED_SIZE

0

Larger

GLX_ACCUM_GREEN_SIZE

0

Larger

GLX_ACCUM_BLUE_SIZE

0

Larger

GLX_ACCUM_ALPHA_SIZE

0

Larger

GLX_SAMPLE_BUFFERS_ARB

0 if GLX_SAMPLES_ARB = 0;
otherwise, 1.

Smaller

GLX_SAMPLES_ARB

0

Smaller

GLX_X_VISUAL_TYPE

No effect

Exact

GLX_TRANSPARENT_TYPE

GLX_NONE

Exact

GLX_TRANSPARENT_INDEX_VALUE

No effect

Exact

GLX_TRANSPARENT_RED_VALUE

No effect

Exact

GLX_TRANSPARENT_GREEN_VALUE

No effect

Exact

GLX_TRANSPARENT_BLUE_VALUE

No effect

Exact

GLX_TRANSPARENT_ALPHA_VALUE

No effect

Exact

GLX_VISUAL_CAVEAT

GLX_NONE

Exact if specified; otherwise, minimum

GLX_DRAWABLE_TYPE

GLX_WINDOW_BIT

Mask

GLX_RENDER_TYPE

GLX_RGBA_BIT

Mask

GLX_X_RENDERABLE

No effect

Exact

GLX_FBCONFIG_ID

No effect

Exact

There are several uses for the glXChooseFBConfig() function:

  • Retrieve an FBConfig with a given ID specified with GLX_FBCONFIG_ID.

  • Retrieve the FBConfig that is the best match for a given list of visual attributes.

  • Retrieve first a list of FBConfigs that match some criteria—for example, each FBConfig available on the screen or all double-buffered visuals available on the screen. Then call glXGetFBConfigAttrib() to find their attributes and choose the one that best fits your needs.

Once the FBConfig is obtained, you can use it to create a GLX pixmap, window, or pbuffer (see “Using Pixel Buffers”).

Below is a description of what happens when you call glXChooseFBConfig():

  • If no matching FBConfig exists or if an error occurs (that is, an undefined GLX attribute is encountered in attrib_list, screen is invalid, or dpy does not support the GLX extension), then NULL is returned.

  • If attrib_list is not NULL and more than one FBConfig is found, then an ordered list is returned with the FBConfigs that form the “best” match at the beginning of the list. (“How an FBConfig Is Selected” describes the selection process.) Use XFree() to free the memory returned by glXChooseFBConfig().

  • If GLX_RENDER_TYPE is in attrib_list, the value that follows is a mask indicating which types of drawables will be created with it. For example, if GLX_RGBA_BIT | GLX_COLOR_INDEX_BIT is specified as the mask, then glXChooseFBConfig() searches for FBConfigs that can be used to create drawables that work with both RGBA and color index rendering contexts. The default value for GLX_RENDER_TYPE is GLX_RGBA_BIT.

    The attribute GLX_DRAWABLE_TYPE as as its value a mask indicating which drawables to consider. Use it to choose FBConfigs that can be used to create and render to a particular GLXDrawable. For example, if GLX_WINDOW_BIT | GLX_PIXMAP_BIT is specified as the mask for GLX_DRAWABLE_TYPE then glXChooseFBConfig() searches for FBConfigs that support both windows and GLX pixmaps. The default value for GLX_DRAWABLE_TYPE is GLX_WINDOW_BIT.

If an FBConfig supports windows it has an associated X visual. Use the GLX_X_VISUAL_TYPE attribute to request a particular type of X visual.

Note that RGBA rendering may be supported for any of the six visual types, but color index rendering can be supported only for PseudoColor, StaticColor, GrayScale, and StaticGray visuals (that is, single-channel visuals). The GLX_X_VISUAL_TYPE attribute is ignored if GLX_DRAWABLE_TYPE is specified in attrib_list and the mask that follows does not have GLX_WINDOW_BIT set.

GLX_X_RENDERABLE is a Boolean indicating whether X can be used to render into a drawable created with the FBConfig. This attribute is always true if the FBConfig supports windows and/or GLX pixmaps.

Retrieving FBConfig Attribute Values

To get the value of a GLX attribute for an FBConfig, call the following function:

int glXGetFBConfigAttrib(Display *dpy, GLXFBConfig config,
                            int attribute, int *value)

If glXGetFBConfigAttrib() succeeds, it returns Success, and the value for the specified attribute is returned in value; otherwise, it returns an error.


Note: An FBConfig has an associated X visual if and only if the GLX_DRAWABLE_TYPE value has the GLX_WINDOW_BIT bit set.

To retrieve the associated visual, call the following function:

XVisualInfo *glXGetVisualFromFBConfig(Display *dpy,
                                         GLXFBConfig config)

If config is a valid FBConfig and it has an associated X visual, then information describing that visual is returned; otherwise, NULL is returned. Use XFree() to free the returned data.

To create a GLX rendering context, a GLX window, or a GLX pixmap using an FBConfig, call glXCreateNewContext(), glXCreateWindow(), or glXCreatePixmap(). Their formats follow:

GLXContext glXCreateNewContext( Display *dpy, GLXFBConfig config,
                                int render_type, GLXContext share_list,
                                Bool direct )
GLXWindow glXCreateWindow( Display *dpy,GLXFBConfig config,
                              Window win, const int *attrib_list)
GLXPixmap glXCreatePixmap( Display *dpy,GLXFBConfig config,
                              Pixmap pixmap, const int *attrib_list)

The window passed to glXCreateWindow() must be created with an X visual corresponding to config; that is, it should be created using the same visual returned by glXGetVisualFromFBConfig() for that FBConfig. Similarly, the pixmap passed to glXCreatePixmap() must have the same color depth as config. If these requirements are not met, creating the window or pixmap will fail with an X BadMatch error.

The attrib_list argument specifies optional additional attributes to use in creating windows or pixmaps. Currently no additional attributes are defined; so, this parameter must always be NULL.

These functions are analogous to the glXCreateContext() and glXCreateGLXPixmap() functions, but they use GLXFBConfigs instead of visuals. See the glXCreateNewContext, glXCreateWindow, and glXCreatePixmap man pages for detailed information.

To create a pixel buffer using an FBConfig, see “Using Pixel Buffers”.

How an FBConfig Is Selected

If more than one FBConfig matches the specification, they are prioritized as follows (Table 4-3 summarizes this information):

  • Preference is given to FBConfigs with the largest GLX_RED_SIZE, GLX_GREEN_SIZE, and GLX_BLUE_SIZE.

  • If the requested GLX_ALPHA_SIZE is zero, preference is given to FBConfigs that have GLX_ALPHA_SIZE set to zero; otherwise, preference is given to FBConfigs that have the largest GLX_ALPHA_SIZE value.

  • If the requested number of GLX_AUX_BUFFERS is zero, preference is given to FBConfigs that have GLX_AUX_BUFFERS set to zero; otherwise, preference is given to FBConfigs that have the smallest GLX_AUX_BUFFERS value.

  • If the requested size of a particular ancillary buffer is zero (for example, GLX_DEPTH_BUFFER is zero), preference is given to FBConfigs that also have that size set to zero; otherwise, preference is given to FBConfigs that have the largest size.

  • If the requested value of either GLX_SAMPLE_BUFFERS_ARB or GLX_SAMPLES_ARB is zero, preference is given to FBConfigs that also have these attributes set to zero; otherwise, preference is given to FBConfigs that have the smallest size.

  • If GLX_X_VISUAL_TYPE is not specified but there is an X visual associated with the FBConfig, the visual type is used to prioritize the FBConfig.

  • If GLX_RENDER_TYPE has GLX_RGBA_BIT set, the visual types are prioritized as follows: TrueColor, DirectColor, PseudoColor, StaticColor, GrayScale, and StaticGray.

  • If only the GLX_COLOR_INDEX is set in GLX_RENDER_TYPE, visual types are prioritized as PseudoColor, StaticColor, GrayScale, and StaticGray.

  • If GLX_VISUAL_CAVEAT is set, the implementation for the particular system on which you run determines which visuals are returned. See “EXT_visual_rating—The Visual Rating Extension” in Chapter 6 for more information.

Related Functions

The FBConfig feature introduces the following functions:

  • glXGetFBConfigAttrib()

  • glXGetFBConfigs()

  • glXChooseFBConfig()

  • glXCreateWindow()

  • glXCreatePixmap()

  • glXCreateNewContext()

  • glXGetVisualFromFBConfig()

Using Colormaps

This section describes the use of colormaps in some detail. Note that in many cases, you will not need to worry about colormaps: just use the drawing area widget and create a TrueColor visual for your RGBA OpenGL program. However, under certain circumstances, for example, if the OpenGL program uses indexed color, the information in this section is important. The section discusses these topics:

Background Information About Colormaps

OpenGL supports two rendering modes: RGBA mode and color-index mode.

  • In RGBA mode, color buffers store red, green, blue, and alpha components directly.

  • In color-index mode, color buffers store indexes (names) of colors that are dereferenced by the display hardware. A color index represents a color by name rather than by value. A colormap is a table of index-to-RGB mappings.


    Note: Onyx4 and Silicon Graphics Prism systems do not support color index rendering; only RGBA mode is available.


OpenGL color modes are described in some detail in the section “RGBA versus Color-Index Mode” in Chapter 5, “Color,” of the OpenGL Programming Guide.

The X Window System supports six different types of visuals, with each type using a different type of colormap (see Table 4-1). Although working with X colormaps may initially seem somewhat complicated, the X Window System does allow you a great deal of flexibility in choosing and allocating colormaps. Colormaps are described in detail with example programs in Chapter 7, “Color,” of O'Reilly
Volume One.

The rest of this section addresses some issues having to do with X colormaps.

Color Variation Across Colormaps

The same index in different X colormaps does not necessarily represent the same color. Ensure that you have the correct color index values for the colormap you are using.

If you use a nondefault colormap, avoid color macros such as BlackPixel() and WhitePixel(). As is required by X11, these macros return pixel values that are correct for the default colormap but inappropriate for your application. The pixel value returned by the macro is likely to represent a color different from black or white in your colormap, or worse yet, be out of range for it. If the pixel value does not exist in your colormap (such as any pixel greater than three for a 2-bit overlay colormap), an X protocol error results.

A “right index–wrong map” type of mistake is most likely if you use the macros BlackPixel() and WhitePixel(). For example, the BlackPixel() macro returns zero, which is black in the default colormap. That value is always transparent (not black) in a popup or overlay colormap (if it supports transparent pixels).

You might also experience problems with colors not appearing correctly on the screen because the colormap for your window is not installed in the hardware.

Multiple Colormap Issues

The need to deal with multiple colormaps of various sizes raises new issues. Some of these issues do not have well-defined solutions.

There is no default colormap for any visual other than the default visual. You must tell the window manager which colormaps to install using XSetWMColormapWindows(), unless you use the GLwMDrawingArea widget, which does this for you.

  • With multiple colormaps in use, colormap flashing may occur if you exceed the hardware colormap resources.

  • An application has as many of its colormaps installed as possible only when it has colormap focus.

    • At that time, the window manager attempts to install all the application's colormaps, regardless of whether or not all are currently needed. These colormaps remain installed until another application needs to have one of them replaced.

    • If another application gets colormap focus, the window manager installs that application's (possibly conflicting) colormaps. Some widgets may be affected while other widgets remain unchanged.

    • The window manager does not reinstall the colormaps for your application until your application has the colormap focus again.

The getColormap() call defined in Example 4-2 returns a sharable colormap (the ICCCM RGB_DEFAULT_MAP) for a TrueColor visual given a pointer to XVisualInfo. This is useful to reduce colormap flashing for non-default visuals.

Example 4-2. Retrieving the Default Colormap for a Visual

Colormap
getColormap(XVisualInfo * vi)
{
    Status          status;
    XStandardColormap *standardCmaps;
    Colormap        cmap;
    int             i, numCmaps;

    /* be lazy; using DirectColor too involved for this example */
    if (vi->class != TrueColor)
        fatalError(“no support for non-TrueColor visual”);
    /* if no standard colormap but TrueColor, make an unshared one */
    status = XmuLookupStandardColormap(dpy, vi->screen, vi->visualid,
        vi->depth, XA_RGB_DEFAULT_MAP, 
        /* replace */ False, /* retain */ True);
    if (status == 1) {
        status = XGetRGBColormaps(dpy, RootWindow(dpy, vi->screen),
                             &standardCmaps, &numCmaps, 
                             XA_RGB_DEFAULT_MAP);
        if (status == 1)
            for (i = 0; i < numCmaps; i++)
                if (standardCmaps[i].visualid == vi->visualid) {
                    cmap = standardCmaps[i].colormap;
                    XFree(standardCmaps);
                    return cmap;
                }
    }
    cmap = XCreateColormap(dpy, RootWindow(dpy, vi->screen),
        vi->visual, AllocNone);
    return cmap;
}


Choosing Which Colormap to Use

When choosing which colormap to use, follow these heuristics:

  1. Decide whether your program will use RGBA or color-index mode.

    Some operations, such as texturing and blending, are not supported in color-index mode; others, such as lighting, work differently in the two modes. Because of that, RGBA rendering is usually the right choice. (See “Choosing between RGBA and Color-Index Mode” in Chapter 5, “Color,” of the OpenGL Programming Guide).

    OpenGL and GLX require an RGBA mode program to use a TrueColor or DirectColor visual and require a color-index mode program to use a PseudoColor or StaticColor visual.


    Note: Remember that RGBA is usually the right choice for OpenGL on a Silicon Graphics system. Onyx4 and Silicon Graphics Prism systems support only RGBA mode.


  2. Choose a visual.

    If you intend to use RGBA mode, specify RGBA in the attribute list when calling glXChooseVisual().

    If RGBA is not specified in the attribute list, glXChooseVisual() selects a PseudoColor visual to support color index mode (or a StaticColor visual if no PseudoColor visual is available).

  3. Create a colormap that can be used with the selected visual.

  4. If a PseudoColor or DirectColor visual has been selected, initialize the colors in the colormap.


    Note: DirectColor visuals are not supported on Silicon Graphics systems. Colormaps for TrueColor and StaticColor visuals are not writable.


  5. Make sure the colormap is installed.

    Depending on what approach you use, you may or may not have to install it yourself:

    • If you use the GLwMDrawingArea widget, the widget automatically calls XSetWMColormapWindows() when the GLwNinstallColormap resource is enabled.

    • The colormap of the top-level window is used if your whole application uses a single colormap. In that case, you have to make sure the colormap of the top-level window supports OpenGL.

    • Call XSetWMColormapWindows() to ensure that the window manager knows about your window's colormap. The following is the syntax for XSetWMColormapWindows():

      Status XSetWMColormapWindows(Display *display, Window w,
                                   Window *colormap_windows, int count)
      

Many OpenGL applications use a 24-bit TrueColor visual (by specifying GLX_RGBA in the visual attribute list when choosing a visual). Colors usually look right in TrueColor, and some overhead is saved by not having to look up values in a table. On some systems, using 24-bit color can slow down the frame rate because more bits must be updated per pixel, but this is not usually a problem.

If you want to adjust or rearrange values in a colormap, you can use a PseudoColor visual.

Lighting and antialiasing are difficult in color-index mode, and texturing and accumulation do not work at all. It may be easier to use double buffering and redraw to produce a new differently colored image, or use the overlay plane. In general, avoid using PseudoColor visuals if possible. Overlays, which always have PseudoColor colormaps on current systems, are an exception to this.

Colormap Example

The following is a brief example that demonstrates how to store colors into a given colormap cell:

XColor xc;

display = XOpenDisplay(0);

visual = glXChooseVisual(display, DefaultScreen(display), 

                         attributeList);

context = glXCreateContext (display, visual, 0, GL_FALSE);

colorMap = XCreateColormap (display, RootWindow(display,

    visual->screen), visual->visual, AllocAll);

    ...

if (ind < visual->colormap_size) {

    xc.pixel = ind;

    xc.red = (unsigned short)(red * 65535.0 + 0.5);

    xc.green = (unsigned short)(green * 65535.0 + 0.5);

    xc.blue = (unsigned short)(blue * 65535.0 + 0.5);

    xc.flags = DoRed | DoGreen | DoBlue;

    XStoreColor (display, colorMap, &xc);

}


Note: Do not use AllocAll on overlay visuals with transparency. If you do, XCreateColormap() fails because the transparent cell is read-only.


Stereo Rendering

Silicon Graphics systems and OpenGL both support stereo rendering. In stereo rendering, the program displays a scene from two slightly different viewpoints to simulate stereoscopic vision, resulting in a 3D image to a user wearing a special viewing device. Various viewing devices exist. Most of them cover one eye while the computer displays the image for the other eye and then cover the second eye while the computer displays the image for the first eye.

This section describes the following topics:

Stereo Rendering Background Information

Stereo rendering is done only using quad-buffered stereo. Legacy, low-end Silicon Graphics systems support a different stereo interface referred to as divided-screen stereo, which is no longer described in this document.

Quad-buffered stereo uses a separate buffer for the left and right eye; this results in four buffers if the program is already using a front and back buffer for animation. Quad-buffered stereo is supported on all current Silicon Graphics systems .

For more information on stereo rendering, see the man pages for the following functions:

  • XSGIStereoQueryExtension()

  • XSGIStereoQueryVersion()

  • XSGIQueryStereoMode()

  • XSGISetStereoMode()

  • XSGISetStereoBuffer()


    Note: The stereo man page includes sample code fragments and pointers to sample code as well as general information on stereo rendering.


Performing Stereo Rendering

To perform stereo rendering, follow these steps:

  1. Perform initialization; that is, make sure the GLX extension is supported and so on.

  2. Put the monitor in stereo mode with the setmon command.

  3. Choose a visual with front left, front right, back left, and back right buffers.

  4. Perform all other setup operations illustrated in the examples in Chapter 2, “OpenGL and X: Getting Started” and Chapter 3, “OpenGL and X: Examples”.

    Create a window, create a context, make the context current, and so on.

  5. Start the event loop.

  6. Draw the stereo image as shown in the following code:

    glDrawBuffer(GL_BACK_LEFT);
    < draw left image >
    glDrawBuffer(GL_BACK_RIGHT);
    < draw right image >
    glXSwapBuffers(...);
    

For more information, see the glDrawBuffer() man page.

Using Pixel Buffers

In addition to rendering on windows and GLX pixmaps, you can render to a pixel buffer (GLXPbuffer or pbuffer for short). This section describes the GLX features that allow you render to pbuffers.

About GLXPbuffers

A GLXPbuffer or pbuffer is an additional non-visible rendering pbuffer for an OpenGL renderer. A pbuffer has the following distinguishing characteristics:

  • Support hardware-accelerated rendering

    Pbuffers support hardware-accelerated rendering in an off-screen buffer unlike pixmaps, which typically do not allow accelerated rendering.

  • Window-independent

    Pbuffers differ from auxiliary buffers (aux buffers) because they are not related to any displayable window; so, a pbuffer may not be the same size as the application's window while an aux buffer must be the same size as its associated window.

PBuffers and Pixmaps

A pbuffer is equivalent to a GLXPixmap with the following exceptions:

  • There is no associated X pixmap. Also, since pbuffers are a GLX resource, it may not be possible to render to them using X or an X extension other than GLX.

  • The format of the color buffers and the type and size of associated ancillary buffers for a pbuffer can be described only with an FBConfig; an X visual cannot be used.

  • It is possible to create a pbuffer whose contents may be arbitrarily and asynchronously lost at any time.

  • A pbuffer works with both direct and indirect rendering contexts.

A pbuffer is allocated in non-visible framebuffer memory—that is, areas for which hardware-accelerated rendering is possible. Applications include additional color buffers for rendering or image processing algorithms.

Volatile and Preserved Pbuffers

Pbuffers can be either volatile—that is, their contents can be destroyed by another window or pbuffer—or preserved—that is, their contents are guaranteed to be correct and are swapped out to virtual memory when other windows need to share the same framebuffer space. The contents of a preserved pbuffer are swapped back in when the pbuffer is needed. The swapping operation incurs a performance penalty. Therefore, use preserved pbuffers only if re-rendering the contents is not feasible.

A pbuffer is intended to be a static resource: a program typically allocates it only once, rather than as a part of its rendering loop. The framebuffer resources that are associated with a pbuffer are also static. They are deallocated only when the pbuffer is destroyed or, in the case of volatile pbuffers, as the result of X server activity that changes framebuffer requirements of the server.

Creating a Pbuffer

To create a pbuffer, call glXCreatePbuffer():

GLXPbuffer glXCreatePbuffer(Display *dpy, GLXFBConfig config,
                             int attrib_list)

This call creates a single pbuffer and returns its XID.

The parameter attrib_list specifies a list of attributes for the pbuffer. Note that the attribute list is defined in the same way as the list for glXChooseFBConfig(): attributes are immediately followed by the corresponding desired value and the list is terminated with None.

The following attributes can be specified in attrib_list:

GLX_PBUFFER_WIDTH 

Determines the pixel width of the rectangular pbuffer. This token must be followed by an integer specifying the desired width. If not specified, the default value is 0.

GLX_PBUFFER_HEIGHT 

Determines the pixel height of the rectangular pbuffer. This token must be followed by an integer specifying the desired height. If not specified, the default value is 0.

GLX_PRESERVED_CONTENTS 

If specified with a value of False, an volatile pbuffer is created, and its contents may be lost at any time. If this attribute is not specified or if it is specified with a value of True, the contents of the pbuffer are preserved, typically, by swapping out portions of the pbuffer to main memory when a resource conflict occurs. In either case, the client can register to receive a buffer clobber event and be notified when the pbuffer contents have been swapped out or have been damaged.

GLX_LARGEST_PBUFFER 

If specified with a value of True, the largest available pbuffer (not exceeding the requested size specified by the values of GLX_PBUFFER_WIDTH and GLX_PBUFFER_HEIGHT) will be created when allocation of the pbuffer would otherwise fail due to lack of graphics memory. If this attribute is not specified or is specified with a value of False, allocation will fail if the requested size is too large even if a smaller pbuffer could be successfully created. The glXQueryDrawable() function may be used to determine the actual allocated size of a pbuffer.

The resulting pbuffer contains color buffers and ancillary buffers as specified by config. It is possible to create a pbuffer with back buffers and to swap the front and back buffers by calling glXSwapBuffers(). Note that a pbuffer uses framebuffer resources; so, applications should deallocate it when not in use—for example, when the application windows are iconified.

If glXCreatePbuffer() fails to create a pbuffer due to insufficient resources, a BadAlloc X protocol error is generated and NULL is returned. If config is not a valid FBConfig, then a GLXBadFBConfig error is generated; if config does not support pbuffers, a BadMatch X protocol error is generated.

Rendering to a Pbuffer

Any GLX rendering context created with an FBConfig or X visual that is compatible with an FBConfig may be used to render into the pbuffer. For the definition of compatible, see the man pages for glXCreateNewContext(), glXMakeCurrent(), and glXMakeCurrentReadSGI().

If a pbuffer is created with GLX_PRESERVED_CONTENTS set to false, the storage for the buffer contents—or a portion of the buffer contents—may be lost at any time. It is not an error to render to a pbuffer that is in this state, but the effect of rendering to it is undefined. It is also not an error to query the pixel contents of such a pbuffer, but the values of the returned pixels are undefined.

Because the contents of a volatile pbuffer can be lost at any time with only asynchronous notification (using the buffer clobber event), the only way a client can guarantee that valid pixels are read back with glReadPixels() is by grabbing the X server. Note that this operation is potentially expensive and you should not do it frequently. Also, because grabbing the X server locks out other X clients, you should do it only for short periods of time. Clients that do not wish to grab the X server can check whether the data returned by glReadPixels() is valid by calling XSync() and then checking the event queue for “ buffer clobber events (assuming that any previous clobber events were pulled off of the queue before the glReadPixels() call).

To destroy a pbuffer call  glXDestroyPbuffer(), whose format follows:

void glXDestroyPbuffer(Display *dpy, GLXPbuffer pbuf)

To query an attribute associated with a GLX drawable (GLXWindow, GLXPixmap, or GLXPbuffer), call glXQueryDrawable(), whose format follows:

void glXQueryDrawable(Display *dpy, GLXDrawable drawable, int attribute
                           unsigned int *value)

The GLX_WIDTH, GLX_HEIGHT, and GLX_FBCONFIG_ID attributes may be queried for all types of drawables. The query returns respectively the allocated pixel width, pixel height, and the XID of the FBConfig with respect to which the drawable was created.

The GLX_PRESERVED_CONTENTS and GLX_LARGEST_PBUFFER attributes are meaningful only for GLXPbuffer drawables and return the values specified when the pbuffer was created. The values returned when querying these attributes for GLXWindow or GLXPixmap drawables are undefined.

To find the FBConfig for a drawable, first retrieve the ID for the FBConfig using glXQueryDrawable() and then call glXChooseFBConfig() with that ID specified as the GLX_FBCONFIG_ID attribute value in the attribute list. For more details, see “Using Framebuffer Configurations”.

Directing the Buffer Clobber Event

An X client can ask to receive GLX events on a GLXWindow or GLXPbuffer by calling glXSelectEvent():

void glXSelectEvent(Display *dpy, GLXDrawable drawable,
                       unsigned long event_mask)

Currently, you can only select the GLX_BUFFER_CLOBBER_MASK GLX event bit in event_mask. The event structure is as follows:

typedef struct {
  int event_type;           /* GLX_DAMAGED or GLX_SAVED */
  int draw_type;            /* GLX_WINDOW or GLX_PBUFFER */
  unsigned long serial;     /* Number of last request processed */
                            /* by server */
  Bool send_event;          /* True if event was generated by a */
                            /* SendEvent request */
  Display *display;         /* Display the event was read from */
  GLXDrawable drawable;     /* XID of Drawable */
  unsigned int buffer_mask; /* Mask indicating which buffers are */
                            /* affected */
  unsigned int aux_buffer;  /* Which aux buffer was affected */
  int x, y;
  int width, height;
  int count;                /* If nonzero, at least this many more */
                            /* events*/
} GLXPbufferClobberEvent;

A single X server operation can cause several buffer clobber events to be sent; for example, a single pbuffer may be damaged and cause multiple buffer clobber events to be generated. Each event specifies one region of the GLXDrawable that was affected by the X server operation.

Events are sent to the application and queried using the normal X event commands (XNextEvent(), XPending(), and so on). The event_mask value returned in the event structure indicates which color and ancillary buffers were affected. The following values can be set in the event structure:

GLX_FRONT_LEFT_BUFFER_BIT
GLX_FRONT_RIGHT_BUFFER_BIT
GLX_BACK_LEFT_BUFFER_BIT
GLX_BACK_RIGHT_BUFFER_BIT
GLX_AUX_BUFFERS_BIT
GLX_DEPTH_BUFFER_BIT
GLX_STENCIL_BUFFER_BIT
GLX_ACCUM_BUFFER_BIT

All the buffer clobber events generated by a single X server action are guaranteed to be contiguous in the event queue. The conditions under which this event is generated and the event type vary, depending on the type of the GLXDrawable:

  • For a preserved pbuffer, a buffer clobber event with event_type GLX_SAVED is generated whenever the contents of the pbuffer are swapped out to host memory. The event(s) describes which portions of the pbuffer were affected. Clients that receive many buffer clobber events referring to different save actions should consider freeing the pbuffer resource to prevent the system from thrashing due to insufficient resources.

  • For a volatile pbuffer, a buffer clobber event with event_type GLX_DAMAGED is generated whenever a portion of the pbuffer becomes invalid. The client may wish to regenerate the invalid portions of the pbuffer.

  • For a window, a clobber event with event_type GLX_SAVED is genererated whenever an ancillary buffer associated with the window is moved out of off-screen memory. The event indicates which color or ancillary buffers and which portions of those buffers were affected. Windows do not generate clobber events when clobbering each other's ancillary buffers—only standard X damage events.

Calling glXSelectEvent() overrides any previous event mask that was set by the client for the drawable. Note that it does not affect the event masks that other clients may have specified for a drawable, because each client rendering to a drawable has a separate event mask for it.

To find out which GLX events are selected for a window or pbuffer, call glXGetSelectedEvent():

void glXSelectEvent(Display *dpy, GLXDrawable drawable,
                       unsigned long event_mask)

Related Functions

The GLX pbuffer feature provides the following functions:

  • glXCreatePbuffer()

  • glXDestroyPbuffer()

  • glXQueryDrawable()

  • glXSelectEvent()

  • glXGetSelectedEvent()

Using Pixmaps

An OpenGL program can render to three kinds of drawables: windows, pbuffers, and pixmaps. A pixmap is an offscreen rendering area. On Silicon Graphics systems, pixmap rendering is not hardware-accelerated. Furthermore, pixmap rendering does not support all features and extensions of the underlying graphics hardware.

Figure 4-2. X Pixmaps and GLX Pixmaps

X Pixmaps and GLX Pixmaps

In contrast to windows, where drawing has no effect if the window is not visible, a pixmap can be drawn to at any time because it resides in memory. Before the pixels in the pixmap become visible, they have to be copied into a visible window. The unaccelerated rendering for pixmap pixels has performance penalties.

This section explains how to create and use a pixmap and identifies some related issues:

Creating and Using Pixmaps

Integrating an OpenGL program with a pixmap is very similar to integrating it with a window. The following steps describe how you create and use pixmaps.


Note: Steps 1–3 and step 6 are described in detail in “Integrating Your OpenGL Program With IRIS IM” in Chapter 2.


  1. Open the connection to the X server.

  2. Choose a visual.

  3. Create a rendering context with the chosen visual.

    This context must be indirect.

  4. Create an X pixmap using XCreatePixmap().

  5. Create a GLX pixmap using glXCreateGLXPixmap(), whose syntax is shown in the following:

    GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis,
                                   Pixmap pixmap)
    

    The GLX pixmap “wraps” the pixmap with ancillary buffers determined by vis (see Figure 4-2).

    The pixmap parameter must specify a pixmap that has the same depth as the visual to which vis points (as indicated by the visual's GLX_BUFFER_SIZE value). Otherwise, a BadMatch X protocol error results.

  6. Use glXMakeCurrent() to bind the pixmap to the context.

    You can now render into the GLX pixmap.

Direct and Indirect Rendering

OpenGL rendering is done differently in different rendering contexts (and on different platforms).

  • Direct rendering

    Direct rendering contexts support rendering directly from OpenGL using the hardware, bypassing X entirely. Direct rendering is much faster than indirect rendering, and all Silicon Graphics systems can do direct rendering to a window.

  • Indirect rendering

    In indirect rendering contexts, OpenGL calls are passed by GLX protocol to the X server, which does the actual rendering. Remote rendering has to be done indirectly; pixmap rendering is implemented to work only indirectly.


    Note: As a rule, use direct rendering unless you are using pixmaps. If you ask for direct and your DISPLAY is remote, the library automatically switches to indirect rendering.


In indirect rendering, OpenGL rendering commands are added to the GLX protocol stream, which in turn is part of the X protocol stream. Commands are encoded and sent to the X server. Upon receiving the commands, the X server decodes them and dispatches them to the GLX extension. Control is then given to the GLX process (with a context switch) so that the rendering commands can be processed. The faster the graphics hardware, the higher the overhead from indirect rendering.

You can obtain maximum indirect-rendering speed by using display lists; they require a minimum of interaction with the X server. Unfortunately, not all applications can take full advantage of display lists; this is particularly a problem in applications using rapidly-changing scene structures. Display lists are efficient because they reside in the X server.

You may see multiple X processes on your workstation when you are running indirect rendering OpenGL programs.

Performance Considerations for X and OpenGL

Due to synchronization and context switching overhead, there is a possible performance penalty for mixing OpenGL and X in the same window. GLX does not constrain the order in which OpenGL commands and X requests are executed. To ensure a particular order, use the GLX commands glXWaitGL() and glXWaitX(). Use the following guidelines:

  • glXWaitGL() prevents any subsequent X calls from executing until all pending OpenGL calls complete. When you use indirect rendering, this function does not contact the X server and is therefore more efficient than glFinish().

  • glXWaitX(), when used with indirect rendering, is just the opposite: it ensures that all pending X calls complete before any further OpenGL calls are made. Also, giving this function an advantage over XSync() when rendering indirectly, glXWaitX() does not need to contact the X server.

  • Remember also to batch Expose events. See “Exposing a Window” in Chapter 3.

  • Make sure no additional Expose events are already queued after the current one. You can discard all but the last event.

Portability

If you expect to port your program from X to other windowing systems (such as Microsoft Windows), certain programming practices make porting easier. The following is a partial list:

  • Isolate your windowing functions and calls from your rendering functions. The more modular your code is in this respect, the easier it is to switch to another windowing system.

  • For Microsoft Windows porting only, avoid naming variables with any variation of the words “near” and “far”. They are reserved words in Intel xx86 compilers. For instance, you should avoid the names _near, _far, __near, __far, near, far, Near, Far, NEAR, FAR, and so on.

  • Microsoft Windows does not have an equivalent to glXCopyContext().