Chapter 4. OpenGL in the X Window System

This chapter provides some information about OpenGL programming in the X environment. The chapter focuses on information relevant to translating IRIS GL programs into programs using OpenGL and X—it doesn't provide a tutorial on Xt and IRIS IM.

This chapter discusses the following topics:

Several documents can help you with more detailed information (see “Where to Get More Information”):

X Window System Background

An X program can create one or more subwindows that use OpenGL for rendering. Such a program allows full access to the capabilities of X by completely removing OpenGL from any feature governed by the X server. You have direct control of all the areas governed by the X server: the event handling, window control, and menus. You also use X to handle color maps and fonts.

You can find examples of programs that use either OpenGL or IRIS GL in the usr/share directory and through the SGI home page.

In IRIS GL, you could use IRIS GL event and window management routines, such as winopen() or qread()—which would access the X Window System for you. In OpenGL, you can use the GLUT library for all basic windowing and event handling routines. If the GLUT library isn't sufficient for your purposes, you can modify your IRIS GL code to be an OpenGL program using GLX.

Function Naming Conventions

IRIS GL can draw in X11 windows with routines such as GLXgetconfig(), GLXlink(), GLXunlink(), and GLXwinset(). These functions don't have exact equivalents in OpenGL; see Appendix A for approximate equivalents.

The naming conventions for X-related functions may be confusing, as they depend largely on capitalization to differentiate between groups of functions:

GLX*() 

IRIS GL mixed-model support

Glx*() 

IRIS GL support for IRIS IM

glX*() 

OpenGL support for X

GLw*() 

OpenGL support for IRIS IM

Note that the glX*() routines are, collectively referred to as “GLX.” Note, too, that GLXgetconfig() (an IRIS GL mixed-model routine) isn't at all the same function as glXGetConfig() (an OpenGL GLX routine). The command

IRIS% man glxgetconfig

on a system with both IRIS GL and OpenGL lists both reference pages, one following the other.

Two Choices for Using OpenGL and X

When integrating your OpenGL program with the X Window System, you have two choices:

The first method, using Xt and a widget set, is easier and is commonly used by developers. It's recommended particularly for programmers with little or no previous experience with X.


Note: The manual OpenGL on Silicon Graphics Systems explores both approaches in some detail.

Whichever method you choose, you'll find more information on programming with Xlib and Xt in the X Window System series from O'Reilly & Associates. The material in this chapter is intended as a supplement to the O'Reilly guides, detailing X development features available on Silicon Graphics workstations.

Using Xt and a Widget Set

Silicon Graphics provides a widget library that simplifies programming with Xt. “Using Xt and a Widget Set” explains how to convert your IRIS GL program to an OpenGL program using Xt, the IRIS Widget Library, and the GL widget, GLwDrawingArea (Silicon Graphics also provides an IRIS IM—Motif—version of GLwDrawingArea, called GLwMDrawingArea.)

Using Xlib

If you prefer to use Xlib without using Xt (in effect working at a lower level), refer to the recommended references on X programming, and use the GLX routines described in the OpenGL Reference Manual (start with the glXIntro reference page). “Using Xlib and GLX Commands” provides more information and contains some code examples. Several complete programs using this method are included in Appendix F, “Example Mixed-Model Programs With Xlib,” along with IRIS GL versions of the same programs.

Advice for OpenGL Programs using the X Window System

This section briefly discusses two important issues:

Dealing With Window Depth and Display Mode

In OpenGL programs that use the X Window System, window depth and display mode are window attributes that are defined when the window is created, and they cannot be changed. To change these attributes, you must create a new window. If you need multiple display modes in your application, you can create multiple windows, then map and unmap them, or raise one above the others.

Installing Color Maps

It's a good idea to call XSetWMColormapWindows(); this ensures that its color maps are installed. If you don't call XSetWMColormapWindows(), the default X color map is used. Even if your program uses RGB mode, you should still call XSetWMColormapWindows() because some hardware (such as IRIS Indigo) simulates RGB with a color map.

Fonts and Strings

OpenGL contains no equivalents for the IRIS GL text-handling calls and Font Manager calls. To obtain full text- and font-handling facilities, call glXUseXFont() with display lists to get some text-display capabilities. You can also use the GLUT font rendering calls for some more limited text- and font-handling facilities.

This section gives you an example; to use display lists to do X bitmap fonts, your program should do the following:

  1. Use X calls to load information about the font you want to use.

  2. Using glXUseXFont(), generate a series of display lists, one for each character in the font.

  3. Put the bitmap for one character into each display list, in the order the characters appear in the font.

  4. To print out a string, use glListBase() to set the display list base to the base for your character series. Then pass the string as an argument to glCallLists().

The following code fragment gives you an example, using Adobe Times Medium to print out the string “The quick brown fox jumps over a lazy dog.” It also prints out the entire character set, from ASCII 32 to 127.

Example 4-1. OpenGL Character Rendering

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glx.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

GLuint base;

void makeRasterFont(Display *dpy)
{
    XFontStruct *fontInfo;
    Font id;
    unsigned int first, last;
    fontInfo = XLoadQueryFont(dpy, 
        "-adobe-times-medium-r-normal--17-120-100-100-p-88-iso8859-1");
    

if (fontInfo == NULL) {
        printf ("no font found\n");
        exit (0);
    }

    id = fontInfo->fid;
    first = fontInfo->min_char_or_byte2;
    last = fontInfo->max_char_or_byte2;

    base = glGenLists(last+1);
    if (base == 0) {
        printf ("out of display lists\n");
    exit (0);
    }
    glXUseXFont(id, first, last-first+1, base+first);
}

void printString(char *s)
{
    glListBase(base);
    glCallLists(strlen(s), GL_UNSIGNED_BYTE, (unsigned char *)s);
}

void display(void)
{
    GLfloat white[3] = { 1.0, 1.0, 1.0 };
    long i, j;
    char teststring[33];

    glClear(GL_COLOR_BUFFER_BIT);
    glColor3fv(white);
    for (i = 32; i < 127; i += 32) {
        glRasterPos2i(20, 200 - 18*i/32);
        for (j = 0; j < 32; j++)
            teststring[j] = i+j;
        teststring[32] = 0;
        printString(teststring);
    }
    glRasterPos2i(20, 100);
    printString("The quick brown fox jumps");
    glRasterPos2i(20, 82);
    printString("over a lazy dog.");
    glFlush ();
}



Note: You can also use the OpenGL character renderer (glc) to render characters. See the glcintro reference page for more information.


Using Xt and a Widget Set

In general, you can bypass many of the complexities of X by using the Xt toolkit and a widget set such as IRIS IM.

When mixing OpenGL with Xt, IRIS IM, or Athena widgets, you can use the Silicon Graphics GLwDrawingArea widget, which simplifies programming with IRIS IM or any other widget set. The GLwDrawingArea widget is also compatible with User Interface Language (UIL). This section explains how to use the GLwMDrawingArea widget for embedding GL in an Xt or IRIS IM program. It discusses these topics:

What You Need to Know About Xt and IRIS IM

The examples shown in this chapter use Xt and IRIS IM. Although knowing Xt and IRIS IM isn't required to read this chapter, understanding the details of the examples does require some Xt and IRIS IM knowledge. This chapter points out a few areas of the Xt and IRIS IM toolkits relevant for OpenGL programmers; it doesn't provide a tutorial on Xt and IRIS IM.

For more information on the relevant features of Xt and IRIS IM, consult the OSF/Motif series, and Digital's X Window System Toolkit: The Complete Programmer's Guide and Specification, or O'Reilly's Volumes 4 and 5 on the X Toolkit Intrinsics, or refer to the OpenGL on Silicon Graphics Systems manual.

About Xt

Xt, also known as the X Toolkit Intrinsics, is a C library that provides routines for creating and using user interface components called widgets. It's usually easier to convert your IRIS GL program using Xt than it is to use the low-level Xlib programming library.

Since Xt doesn't dictate the “look and feel” of the GUI, you must use it in conjunction with a widget set (a library of pre-built widgets), such as the Athena widget set or IRIS IM.

About IRIS IM

IRIS IM is the Silicon Graphics port of OSF/Motif. Motif is an extensible widget set of user interface objects, such as buttons, scroll bars, menu systems, and dialog boxes. These widgets are accessible via a library of C routines. These widgets are supported by Xt. Ultimately, the X Window System is the foundation for both the Motif and Athena widget sets.

Motif is also a style guide, which describes the “look and feel” of a Motif compliant user interface.

IRIS IM and Other Widget Sets

This section refers frequently to IRIS IM because it is commonly used with OpenGL programs; however, unless otherwise specified, you can use the features discussed here with other widget sets, such as the Athena widget set. The features discussed in this chapter exist either within the widget itself or are based on the X toolkit. You therefore have a choice:

  • Use the generic GLwDrawingArea widget.

  • Use the IRIS IM (Motif) widget GLwMDrawingArea.

Combining OpenGL and Motif is made easier by a specially supplied OpenGL drawing area widget, GLwDrawingArea. Use the GLwDrawingArea widget when integrating your OpenGL program with Xt. The GLwDrawingArea widget sets up a configuration for GL drawing and provides resources and callbacks that are useful to the OpenGL programmer. The GLwDrawingArea widget also provides support for overlays.

There are actually two GLwDrawingArea widgets. The widget known as GLwDrawingArea is a generic widget, suitable for use with any widget set that's based on the Xt intrinsics. There is also a version known as GLwMDrawingArea (note the M) for use with IRIS IM programs.

The two widgets are very similar, but they do have these differences:

  • GLwMDrawingArea is a subclass of the IRIS IM XmPrimitive widget, rather than being a subclass of the Xt Core widget and, therefore, has various defaults such as background color.

  • GLwMDrawingArea understands IRIS IM traversal, although traversal is turned off by default.

  • You can create GLwMDrawingArea directly through Xt or use an IRIS IM creation function, GlxCreateMDrawingArea().

In all other respects, the two widgets are identical. The remainder of this chapter refers to the GlxMDraw widget, but unless otherwise specified, everything stated refers to both.

Converting Your IRIS GL Program

This section discusses the actual conversion process:

Finding Areas for Porting

When porting to OpenGL, you have to replace any IRIS GL windowing and event handling code. One way to do this is to run toogl and then search through the output for the toogl warnings marked “OGLXXX.” It should be reasonably straightforward to determine which warnings relate to X.

Using the OpenGL Widget

This section shows a simple example of a program that uses the IRIS IM version of the OpenGL widget and explains how the code works.

The generic version of the widget can be used in the same way. To compile this example, use this command line:

% cc -O -o mixed mixed.c -lXm -lGL -lGLw -lGLU

When the OpenGL widget is initially opened, its visual must be set. In other words, you must first declare the display mode of the visual: single or double buffer, color index or RGBA mode. You may also specify how many bits will be used by the components of the frame buffer: for example, depth, stencil, and accumulation bits.

In the program shown in Example 4-2, the function init_window(), which is registered with the GlxNginitCallback callback, calls glXCreateContext() to set the visual of the OpenGL widget. In this case, the resources for the widget are set to support RGBA and double buffer mode. (See the fallback_resources[] array in the main() procedure.)

Example 4-2. OpenGL Program Using IRIS IM OpenGL Widget

/* mixed.c
 */

#include <Xm/Xm.h>
#include <Xm/Form.h>
#include <X11/keysym.h>
#include <X11/StringDefs.h>
#include "GL/GLwMDrawA.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <stdio.h>
#include <stdlib.h>

static void input(Widget, XtPointer, XtPointer);
static void draw_scene_callback (Widget, XtPointer,
                                 XtPointer);
static void do_resize(Widget, XtPointer, XtPointer);
static void init_window(Widget, XtPointer, XtPointer);

static GLXContext glx_context;

void main(int argc, char** argv)
{
    Arg args[20];
    int n;
    Widget glw, toplevel, form;
    static XtAppContext app_context;
    static String fallback_resources[] = {
        "*glwidget*width: 300",
        "*glwidget*height: 300",
        "*glwidget*rgba: TRUE",
        "*glwidget*doublebuffer: TRUE",
        "*glwidget*allocateBackground: TRUE",
        NULL
    };

    toplevel = XtAppInitialize(&app_context, "Mixed", NULL,
                               0, &argc, argv,
                               fallback_resources, NULL, 0);
    n = 0;
    form = XmCreateForm(toplevel, "form", args, n);
    XtManageChild(form);

    n = 0;
    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM);
    n++;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM);
    n++;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM);
    n++;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM);
    n++;
    glw = GLwCreateMDrawingArea(form, "glwidget", args, n);
    XtManageChild (glw);
    XtAddCallback(glw, GLwNexposeCallback,
                  draw_scene_callback, (XtPointer) NULL);
    XtAddCallback(glw, GLwNresizeCallback, do_resize,
                  (XtPointer) NULL);
    XtAddCallback(glw, GLwNginitCallback, init_window,
                  (XtPointer) NULL);
    XtAddCallback(glw, GLwNinputCallback, input,
                  (XtPointer) NULL);

    XtRealizeWidget(toplevel);
    XtAppMainLoop(app_context);
}

static int rotation = 0;

void spin (void)
{
    rotation = (rotation + 5) % 360;
}

static void draw_scene (Widget w)
{
    GLUquadricObj *quadObj;

    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f (1.0, 1.0, 1.0);
    glPushMatrix();
    glTranslatef (0.0, 0.0, -5.0);
    glRotatef ((GLfloat) rotation, 1.0, 0.0, 0.0);

    glPushMatrix ();
    glRotatef (90.0, 1.0, 0.0, 0.0);
    glTranslatef (0.0, 0.0, -1.0);
    quadObj = gluNewQuadric ();
    gluQuadricDrawStyle (quadObj, GLU_LINE);
    gluCylinder (quadObj, 1.0, 1.0, 2.0, 12, 2);
    glPopMatrix ();

    glPopMatrix();
    glFlush();
    glXSwapBuffers (XtDisplay(w), XtWindow(w));
}

/* Process all Input callbacks*/
static void input(Widget w, XtPointer client_data,
                  XtPointer call)
{
    char buffer[1];
    KeySym keysym;
    GLwDrawingAreaCallbackStruct *call_data;

    call_data = (GLwDrawingAreaCallbackStruct *) call;

    switch(call_data->event->type)
    {
    case KeyRelease:
         /* It is necessary to convert the keycode to a
          * keysym before it is possible to check if it is
          * an escape.
          */
         if (XLookupString( (XKeyEvent *) call_data->event,
                            buffer, 1, &keysym,
                            (XComposeStatus *) NULL ) == 1
             && keysym == (KeySym) XK_Escape)
             exit(0);
    break;

    case ButtonPress:
        switch (call_data->event->xbutton.button)
        {
        case Button1:
            spin();
            draw_scene(w);
        break;
        }
    break;

    default:
    break;
    }
}

static void draw_scene_callback(Widget w, XtPointer client_data, XtPointer call)
{
    static char firstTime = 0x1;
    GLwDrawingAreaCallbackStruct *call_data;

    call_data = (GLwDrawingAreaCallbackStruct *) call;
    GLwDrawingAreaMakeCurrent(w, glx_context);

    if (firstTime) {
        glViewport(0, 0, call_data->width,call_data->height);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluPerspective(65.0, (float) call_data->width /
                       (float)call_data->height, 1.0, 20.0);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        firstTime = 0;
    }
    draw_scene (w);
}

static void do_resize(Widget w, XtPointer client_data,
                      XtPointer call)
{
    GLwDrawingAreaCallbackStruct *call_data;

    call_data = (GLwDrawingAreaCallbackStruct *) call;

    GLwDrawingAreaMakeCurrent(w, glx_context);
    glViewport(0, 0, call_data->width, call_data->height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(65.0, (GLfloat) call_data->width /
                   (GLfloat)call_data->height, 1.0, 20.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

static void init_window(Widget w, XtPointer client_data,
                        XtPointer call_data)
{
    Arg args[1];
    XVisualInfo *vi;
    GLUquadricObj *quadObj;

    XtSetArg(args[0], GLwNvisualInfo, &vi);
    XtGetValues(w, args, 1);
    glx_context = glXCreateContext(XtDisplay(w), vi, 0,
                                   GL_FALSE);
}

It's a good idea to always call GlxDrawingAreaMakeCurrent() to set the current widget. In Example 4-2, GlxDrawingAreaMakeCurrent() is called from the callback functions.

Example 4-2 draws a wire frame cylinder using OpenGL. The GlxNinputCallback calls input(), which handles mouse and keyboard input. Pressing the Escape key causes the program to exit. Pressing Button1 (usually the left mouse button) calls spin(), which changes the rotation of the cylinder. Then the scene is completely redrawn.

The mixed.c program has absolutely basic placement of widgets. The OpenGL drawing area widget is attached to all sides of its parent, an IRIS IM XmForm widget. This is a minimal arrangement—you can add additional IRIS IM widgets for a more sophisticated interface.

You might also want to add a WorkProc (or idle) function, which executes when no other events are occurring. A WorkProc is useful for rendering continuous motion, which doesn't require steady input events; for example, an animation. Appendix E, “Example Program Using Xt and a WorkProc,” contains an example program using Xt and a WorkProc.

Background Reading

The most complete information about OpenGL and X can be found in

Kilgard, Mark J. OpenGL Programming for the X Window System. Menlo Park, CA: Addison-Wesley Developer's Press. 1996. ISBN 0-201-48369-9

For more information on mixed-model programming in general, you can refer to the OpenGL Reference Manual, which contains reference pages for the OpenGL GLX command, as well as an introductory reference page, glXIntro.

For more detailed information on programming with Xt, see Volume IV of the X Window System Series, X Toolkit Intrinsics Programming Manual, by Adrian Nye and Tim O'Reilly, published by O'Reilly & Associates, Inc. (If you're using IRIS IM, you'll probably want the Motif version of Volume IV.)

For more information on IRIS IM, refer to documentation on Motif, such as the OSF/Motif Series published by Prentice Hall.

Using Xlib and GLX Commands

Using Xlib and GLX can be more complex than using Xt and a widget set, and Silicon Graphics doesn't recommend it unless you're already familiar with Xlib programming. This section provides an overview of the necessary steps for using Xlib and GLX. It also provides some simple code examples.You'll almost certainly need to refer to more substantial Xlib documentation (such as the O'Reilly volumes), as well as the OpenGL Reference Manual. The glXIntro reference page is a good starting point.

This section discusses the most important aspects of using Xlib and GLX and also provides several example programs in the following sections:

Getting Started With Xlib and GLX


Note: Another example of using XLib is included in OpenGL on Silicon Graphics Systems.

To port your OpenGL code to use Xlib and GLX calls, follow these steps:

  1. Add the necessary include files to your program. (See “Header Files” for information on what files to include.)

  2. Open a connection to a display: XOpenDisplay().

  3. Choose an X visual: glXChooseVisual().

  4. Create a GLX context: glXCreateContext().

  5. Create an X window or pixmap: XCreateWindow().

  6. Connect the GLX context to the X window: glXMakeCurrent().

Opening a Window With GLX

Example 4-3 shows a simple way of following the steps given in the previous section.You can find a version of this code in the glXIntro reference page. This sample is more heavily commented than the one in the reference page and contains some additional examples.

Example 4-3. OpenGL and GLX Program

#include <X11/Xlib.h>
#include <GL/glx.h>
#include <GL/gl.h>
#include <stdio.h>

static int attributeList[] = { GLX_RGBA, None };

static Bool WaitForNotify(Display *d, XEvent *e, char *arg)
    { return(e->type == MapNotify) && (e->xmap.window == (Window)arg); }

int main(int argc, char**argv)
{
    Display *dpy;
    XVisualInfo *vi;
    Colormap cmap;
    XSetWindowAttributes swa;
    Window win;
    GLXContext cx;
    XEvent event;

/* get a connection   */
    dpy   = XOpenDisplay(0);
    if (!dpy) {
        fprintf(stderr, "Cannot open display.\n");
        exit(-1);
    }

/* get an appropriate visual */
    vi = glXChooseVisual(dpy, DefaultScreen(dpy),
         attributeList);
    if (!vi) {
        fprintf(stderr, "Cannot find visual with desired attributes.\n");
        exit(-1);
    }

/* create a GLX context */
    cx = glXCreateContext(dpy, vi, 0, GL_FALSE);
    if (!cx) {
        fprintf(stderr, "Cannot create context.\n");
        exit(-1);
    }

/* create a colormap -- AllocAll for color index mode */
    cmap = XCreateColormap(dpy, RootWindow(dpy, vi->screen),
           vi->visual, AllocNone);
    if (!cmap) {
        fprintf(stderr, "Cannot allocate colormap.\n");
        exit(-1);
    }

    /* create a   window */
    swa.colormap = cmap;
    swa.border_pixel = 0;
    /* connect the context to the window */
    glXMakeCurrent(dpy, win, cx);

/* clear the buffer */
    glClearColor(1,1,0,1);
    glClear(GL_COLOR_BUFFER_BIT);
    glFlush();

/wait for a while */
    sleep(10);
/* exit cleanly */
    XCloseDisplay(dpy);
    exit(0);
}


Using X Color Maps

Here's a brief example of OpenGL GLX code that demonstrates the use of color maps:

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

/* Note: if you don't say AllocAll, you can't load */
/* the color maps! */
    ...

if (index < visual->colormap_size) {

    xc.pixel = index;

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

}


Using X Events

Here's a simple example of a program that uses Xlib and OpenGL GLX calls for event handling:

swa.event_mask = ExposureMask | StructureNotifyMask 
                 | KeyPressMask | KeyReleaseMask;
do {
    XNextEvent(dpy, &event);
    switch (event.type) {
        case Expose:
            doRedraw = GL_TRUE;
            break;
        case ConfigureNotify:
            width = event.xconfigure.width;
            height = event.xconfigure.height;
            doRedraw = GL_TRUE;
            break;
        case KeyPress:
        {
            char buf[100];
            int rv;
            KeySym ks;

            rv = XLookupString(&event.xkey, buf, sizeof(buf), &ks, 0);
            switch (ks) {
                case XK_s:
                case XK_S:
                    doSave = GL_TRUE;
                    break;
                case XK_Escape:
                    return 0;
                    break;
            }
        }
    }
} while (XPending(dpy));