Some aspects of integrating your OpenGL program with the X Window System depend on whether you choose IRIS IM widgets or Xlib. This chapter's main focus is to help you with those aspects by looking at example programs:
“Using Widgets” illustrates how to create a window using IRIS IM drawing-area widgets and how to handle input and other events using callbacks.
“Using Xlib” illustrates how to create a colormap and a window for OpenGL drawing. It also provides a brief discussion of event handling with Xlib.
This chapter also briefly describes fonts: “Using Fonts and Strings” describes a simple example of using fonts with the glXUseFont() function.
Note: All integration aspects that are not dependent on your choice of Xlib or Motif are described in “Integrating Your OpenGL Program With IRIS IM” in Chapter 2 in Chapter 2, “OpenGL and X: Getting Started”. |
This section explains how to use IRIS IM widgets for creating windows, handling input, and performing other activities that the OpenGL part of a program does not manage. The section desribes the following topics:
Using an OpenGL drawing-area widget facilitates rendering OpenGL into an X window. The widget does the following:
Provides an environment for OpenGL rendering, including a visual and a colormap.
Provides a set of callback routines for redrawing, resizing, input, and initialization (see “Using Drawing-Area Widget Callbacks”).
OpenGL provides two drawing-area widgets: GLwMDrawingArea—note the M in the name—for use with IRIS IM (or with OSF/Motif), and GLwDrawingArea for use with any other widget sets. Both drawing-area widgets provide the following two convenience functions:
The functions allow you to supply a widget instead of the display and window required by the corresponding GLX functions glXMakeCurrent() and glXSwapBuffers().
Because the two widgets are nearly identical and because IRIS IM is available on all Silicon Graphics systems, this chapter uses only the IRIS IM version, even though most of the information also applies to the general version. Here are some of the distinguishing characteristics of GLwMDrawingArea:
GLwMDrawingArea understands IRIS IM keyboard traversal (moving around widgets with keyboard entries rather than a mouse), although keyboard traversal is turned off by default.
GLwMDrawingArea is a subclass of the IRIS IM XmPrimitive widget, not a direct subclass of the Xt Core widget. Therefore, it has various defaults such as background and foreground colors. GLwMDrawingArea is not derived from the standard Motif drawing-area widget class. For more information, see O'Reilly Volume One or the man pages for Core and for XmPrimitive.
Note that the default background colors provided by the widget are used during X rendering, not during OpenGL rendering. Therefore, it is not advisable to rely on default background rendering from the widget. Even when the background colors are not used directly, XtGetValues() can be used to query them to allow the graphics to blend better with the program.
GLwMDrawingArea has the creation function GLwCreateMDrawingArea() in the style of IRIS IM. You can also create the widget directly through Xt.
For information specific to GLwDrawingArea, see the manpage.
Most of the steps for writing a program that uses a GLwMDrawingArea widget are already described in “Integrating Your OpenGL Program With IRIS IM” in Chapter 2. This section explains how to initialize IRIS IM and how to create the drawing-area widget using code fragments from the motif/simplest.c example program (Example 2-1). This section has the following topics:
This section briefly explains how to work with resources in the context of an OpenGL program. In Xt, resources provide widget properties, allowing you to customize how your widgets will look. Note that the term “resource” used here refers to window properties stored by a resource manager in a resource database, not to the X server data structures for windows, pixmaps, and context described earlier.
F allback resources inside a program are used when a widget is created and the application cannot open the class resource file when it calls XtOpenApplication() to open the connection to the X server. (In the code fragment below, the first two resources are specific to Silicon Graphics and give the application a Silicon Graphics look and feel.)
static String fallbackResources[] = { "*useSchemes: all",”*sgimode:True”, "*glxwidget*width: 300", "*glxwidget*height: 300", "*frame*shadowType: SHADOW_IN", NULL}; |
Widgets always exist in a hierarchy with each widget contributing to what is visible on screen. There is always a top-level widget and almost always a container widget (for example, form or frame). In addition, you may decide to add buttons or scroll bars, which are also part of the IRIS IM widget set. Therefore, creating your drawing surface consists of the following two steps:
Create parent widgets, namely the top-level widget and a container widget. The program motif/simplest.c, Example 2-1, uses a Form container widget and a Frame widget to draw the 3D box:
toplevel = XtOpenApplication(&app, "simplest", NULL, 0, &argc, argv, fallbackResources, applicationShellWidgetClass, NULL, 0); ... form = XmCreateForm(toplevel, "form", args, n); XtManageChild(form); .... frame = XmCreateFrame (form, "frame", args, n); ... |
For more information, see the man pages for XmForm and XmFrame.
Create the GLwMDrawingArea widget itself in either of two ways:
Call GLwCreateMDrawingArea(). You can specify each attribute as an individual resource or pass in an XVisualInfo pointer obtained with glXChooseVisual(). This is discussed in more detail in the next section, “Choosing the Visual for the Drawing-Area Widget”.
n = 0 XSetArg(args[n] GLwNvisualinfo, (XtArgVal)visinfo); n++; glw = GLwCreateMDrawingArea(frame, "glwidget", args, n); |
Call XtVaCreateManagedWidget() and pass it a pointer to the visual you have chosen. In that case, use glwMDrawingAreaWidgetClass as the parent and GLwNvisualInfo to specify the pointer. The following is an example from motif/simplest.c:
glxwidget = XtVaCreateManagedWidget ("glxwidget", glwMDrawingAreaWidgetClass, frame, GLwNvisualInfo, visinfo, NULL); |
Note that unlike most other Motif user interface widgets, the OpenGL widget explicitly sets the visual. Once a visual is set and the widget is realized, the visual can no longer be changed.
When calling the widget creation function,there are three ways of configuring the GLwMDrawingArea widget (all done through resources):
Pass in separate resources for each attribute (for example GLwNrgba, GLwNdoublebuffer).
Pass in an attribute list of the type used by glXChooseVisual() using the GLwNattribList resource.
Select the visual yourself using glXChooseVisual() and pass in the returned XVisualInfo* as the GLwNvisualInfo resource.
Appropriate error handling is critical to a robust program. If you wish to provide error handling, call glXChooseVisual(), as all the example programs do (although for the sake of brevity, none of the examples actually provides error handling). If you provide the resources and let the widget choose the visual, the widget just prints an error message and quits. Note that a certain visual may be supported on one system but not on another.
The advantage of using a list of resources is that you can override them with the app-defaults file.
Most applications have one context per widget, though sharing is possible. If you want to use multiple widgets with the same configuration, you must use the same visual for each widget. Windows with different visuals cannot share contexts. To share contexts, do the following:
Extract the GLwNvisualInfo resource from the first widget you create.
Use that visual in the creation of subsequent widgets.
The GLwMDrawingArea widget provides callbacks for redrawing, resizing, input, and initialization, as well as the standard XmNdestroyCallback provided by all widgets.
Each callback must first be defined and then added to the widget. In some cases, this is quite simple, as, for example, the resize callback from motif/simplest.c:
static void resize(Widget w, XtPointer client_data, XtPointer call) { GLwDrawingAreaCallbackStruct *call_data; call_data = (GLwDrawingAreaCallbackStruct *) call; glXWaitX(); glViewport(0, 0, call_data->width, call_data->height); } |
Note: The X and OpenGL command streams are asynchronous, meaning that the order in which OpenGL and X commands complete is not strictly defined. In a few cases, it is important to explicitly synchronize X and OpenGL command completion. For example, if an X call is used to resize a window within a widget program, call glXWaitX() before calling glViewport() to ensure that the window resize operation is complete. |
Other cases are slightly more complex, such as the input callback from motif/simplest.c, which exits when the user presses the Esc key:
static void input(Widget w, XtPointer client_data, XtPointer call) { char buffer[31]; KeySym keysym; XEvent *event = ((GLwDrawingAreaCallbackStruct *)call) ->event; switch(event->type) { case KeyRelease: XLookupString(&event->xkey, buffer, 30, &keysym, NULL); switch(keysym) { case XK_Escape : exit(EXIT_SUCCESS); break; default: break; } break; } } |
To add callbacks to a widget, use XtAddCallback(); for example:
XtAddCallback(glxwidget, GLwNexposeCallback, expose, NULL); XtAddCallback(glxwidget, GLwNresizeCallback, resize, NULL); XtAddCallback(glxwidget, GLwNinputCallback, input, NULL); |
Each callback must ensure that the thread is made current with the correct context to the window associated with the widget generating the callback. You can do this by calling either GLwMDrawingAreaMakeCurrent() or glXMakeCurrent().
If you are using only one GLwMDrawingArea, you can call a routine to make the widget “current” just once after initializing the widget. However, if you are using more than one GLwMDrawingArea or rendering context, you need to make the correct context and the window current for each callback (see “Binding the Context to the Window” in Chapter 2).
The following callbacks are available:
Callback | Description | |
GLwNginitCallback() | Specifies the callbacks to be called when the widget is first realized. You can use this callback to perform OpenGL initialization, such as creating a context, because no OpenGL operations can be done before the widget is realized. The callback reason is GLwCR_GINIT. | |
GLwNexposeCallback() | Specifies the callbacks to be called when the widget receives an Expose event. The callback reason is GLwCR_EXPOSE. The callback structure also includes information about the Expose event. Usually the application should redraw the scene whenever this callback is called. | |
GLwNinputCallback() | Specifies the callbacks to be called when the widget receives a keyboard or mouse event. The callback structure includes information about the input event. The callback reason is GLwCR_INPUT. | |
GLwNresizeCallback() | Specifies the callbacks to be called when the GLwDrawingArea is resized. The callback reason is GLwCR_RESIZE. Normally, programs resize the OpenGL viewport and possibly reload the OpenGL projection matrix (see the OpenGL Programming Guide). An expose callback follows. Avoid performing rendering inside the resize callback. |
Using the following topics, this section explains how to perform input handling with widgets and Xt:
Motif programs are callback-driven. They differ in that respect from IRIS GL programs, which implement their own event loops to process events. To handle input with a widget, you can either use the input callback built into the widget or use actions and translations (Xt-provided mechanisms that map keyboard input into user-provided routines). Both approaches have advantages:
Input callbacks are usually simpler to write, and they are more unified; all input is handled by a single routine that can maintain a private state (see “Using the Input Callback ”).
The actions-and-translations method is more modular, because translations have one function for each action. Also, with translations the system does the keyboard parsing so your program does not have to do it. Finally, translations allow the user to customize the application's key bindings. See “Using Actions and Translations ”.
Note: To allow smooth porting to other systems, as well as for easier integration of X and OpenGL, always separate event handling from the rest of your program. |
By default, the input callback is called with every key press and release, with every mouse button press and release, and whenever the mouse is moved while a mouse button is pressed. You can change this by providing a different translation table, although the default setting should be suitable for most applications.
For example, to have the input callback called on all pointer motions, not just on mouse button presses, add the following to the app-defaults file:
*widgetname.translations : \ <KeyDown>: glwInput() \n\ <KeyUp>: glwInput() \n\ <BtnDown>: glwInput() \n\ <BtnUp>: glwInput() \n\ <BtnMotion>: glwInput() \n\ <PtrMoved>: glwInput() |
When the callback is passed an X event, the callback interprets the X event and performs the appropriate action. It is your application's responsibility to interpret the event—for example, to convert an X key code into a key symbol and to decide what to do with it.
Example 3-1 is from motif/mouse.c, a double-buffered RGBA program that uses mouse motion events.
Example 3-1. Motif Program That Handles Mouse Events
static void input(Widget w, XtPointer client_data, XtPointer call) { char buffer[31]; KeySym keysym; XEvent *event = ((GLwDrawingAreaCallbackStruct *) call)->event; static mstate, omx, omy, mx, my; switch(event->type) { case KeyRelease: XLookupString(&event->xkey, buffer, 30, &keysym, NULL); switch(keysym) { case XK_Escape: exit(EXIT_SUCCESS); break; default: break; } break; case ButtonPress: if (event->xbutton.button == Button2) { mstate |= 2; mx = event->xbutton.x; my = event->xbutton.y; } else if (event->xbutton.button == Button1) { mstate |= 1; mx = event->xbutton.x; my = event->xbutton.y; } break; case ButtonRelease: if (event->xbutton.button == Button2) mstate &= ~2; else if (event->xbutton.button == Button1) mstate &= ~1; break; case MotionNotify: if (mstate) { omx = mx; omy = my; mx = event->xbutton.x; my = event->xbutton.y; update_view(mstate, omx,mx,omy,my); } break; } |
Actions and translations provide a mechanism for binding a key or mouse event to a function call. For example, you can structure your program to take the following actions:
When you press the Esc key, the exit routine quit() is called.
When you press the left mouse button, rotation occurs.
When you press f, the program zooms in.
The translations need to be combined with an action task that maps string names like quit() to real function pointers. Below is an example of a translation table:
program*glwidget*translations: #override \n <Btn1Down>: start_rotate() \n\ <Btn1Up>: stop_rotate() \n\ <Btn1Motion>: rotate() \n\ <Key>f: zoom_in() \n\ <Key>b: zoom_out() \n\ <KeyUp>osfCancel: quit() |
When you press the left mouse button, the start_rotate() action is called; when it is released, the stop_rotate() action is called.
The last entry is a little cryptic. It specifies that when the user presses the Esc key, quit() is called. However, OSF has implemented virtual bindings, which allow the same programs to work on computers with different keyboards that may be missing various keys. If a key has a virtual binding, the virtual binding name must be specified in the translation. Thus, the example above specifies osfCancel rather than Esc. To use the above translation in a program that is not based on IRIS IM or OSF/Motif, replace KeyUp+osfCancel with KeyUp+Esc.
The translation is only half of what it takes to set up this binding. Although the translation table above contains apparent function names, they are really action names. Your program must also create an action table to bind the action names to actual functions in the program.
For more information on actions and translations, see O'Reilly, X Toolkit Intrinsics Programming Manual (Volume Four), most notably Chapter 4, “An Example Application,” and Chapter 8, “Events, Translations, and Accelerators.” You can view this manual on the SGI Technical Publications Library.
By default, a widget creates a colormap automatically. For many programs, this is sufficient. However, it is occasionally necessary to create a colormap explicitly, especially when using color index mode. See “Creating a Colormap and a Window” and “Using Colormaps” in Chapter 4 for more information.
This section provides troubleshooting information by describing some common pitfalls when working with widgets.
Note: Additional debugging information is provided in “General Tips for Debugging Graphics Programs” in Chapter 15. |
A common problem in IRIS IM programs is that keyboard input disappears. This is caused by how IRIS IM handles keyboard focus. When a widget hierarchy has keyboard focus, only one component of the hierarchy receives the keyboard events. The keyboard input might be going to the wrong widget. The following are two solutions to this problem:
The easiest solution is to set the following resource for the application:
keyboardFocusPolicy: POINTER |
This overrides the default traversal method (explicit traversal) where you can select widgets with keyboard keys rather than the mouse so that input focus follows the pointer only. The disadvantages of this method are that it eliminates explicit traversal for users who prefer it and it forces a nondefault model.
A better solution is to do the following:
Set the following resource:
*widget.traversalOn: TRUE |
The field widget is the name of the widget.
Whenever mouse button 1 is pressed in the widget, call the following function:
XmProcessTraversal(widget, XmTRAVERSE_CURRENT); |
Turning process traversal on causes the window to respond to traversal (it normally does not), and calling XmProcessTraversal() actually traverses into the widget when appropriate.
In Xt, shell widgets include top-level windows, popup windows, and menus. Shell widgets inherit their colormap and pixel depth from their parent widget and inherit their visual from the parent window. If the visual does not match the colormap and depth, this leads to a BadMatch X protocol error.
In a typical IRIS IM program, everything runs in the default visual, and the inheritance from two different places does not cause problems. However, when a program uses both OpenGL and IRIS IM, it requires multiple visuals, and you must be careful. Whenever you create a shell widget as a child of a widget in a non-default visual, specify pixel depth, colormap, and a visual for that widget explicitly. This happens with menus or popup windows that are children of OpenGL widgets. See “Using Popup Menus With the GLwMDrawingArea Widget” in Chapter 4.
If you do get a bad match error, follow these steps to determine its cause:
Run the application under a C debugger, such as dbx or cvd (the Case Vision debugger) with the –sync flag.
The –sync flag tells Xt to call XSynchronize(), forcing all calls to be made synchronously. If your program is not based on Xt, or if you are not using standard argument parsing, call XSynchronize(display, TRUE) directly inside your program.
Using the debugger, set a breakpoint in exit() and run the program.
When the program fails, you have a stack trace you can use to determine what Xlib routine caused the error.
Note: If you do not use the –sync option, the stack dump on failure is meaningless: X batches multiple requests and the error is delayed. |
This section explains how to use Xlib for creating windows, handling input, and performing other activities that the OpenGL part of a program does not manage. This section has the following topics:
Because the complete example program in Chapter 2, “OpenGL and X: Getting Started” used widgets, this section starts with a complete annotated example program for Xlib so that you have both available as needed. Example 3-2 lists the complete Xlib/simplest.c example program.
Example 3-2. Simple Xlib Example Program
/* * simplest - simple single buffered RGBA xlib program. */ /* compile: cc -o simplest simplest.c -lGL -lX11 */ #include <GL/glx.h> #include <X11/keysym.h> #include <stdlib.h> #include <stdio.h> static int attributeList[] = { GLX_RGBA, None }; static void draw_scene(void) { glClearColor(0.5, 0.5, 0.5, 1.0); glClear(GL_COLOR_BUFFER_BIT); glColor3f(1.0,0.0,0.0); glRectf(-.5,-.5,.5,.5); glColor3f(0.0,1.0,0.0); glRectf(-.4,-.4,.4,.4); glColor3f(0.0,0.0,1.0); glRectf(-.3,-.3,.3,.3); glFlush(); } static void process_input(Display *dpy) { XEvent event; Bool redraw = 0; do { char buf[31]; KeySym keysym; XNextEvent(dpy, &event); switch(event.type) { case Expose: redraw = 1; break; case ConfigureNotify: glViewport(0, 0, event.xconfigure.width, event.xconfigure.height); redraw = 1; break; case KeyPress: (void) XLookupString(&event.xkey, buf, sizeof(buf), &keysym, NULL); switch (keysym) { case XK_Escape: exit(EXIT_SUCCESS); default: break; } default: break; } } while (XPending(dpy)); if (redraw) draw_scene(); } static void error(const char *prog, const char *msg) { fprintf(stderr, “%s: %s\n”, prog, msg); exit(EXIT_FAILURE); } int main(int argc, char **argv) { Display *dpy; XVisualInfo *vi; XSetWindowAttributes swa; Window win; GLXContext cx; /* get a connection */ dpy = XOpenDisplay(0); if (!dpy) error(argv[0], “can't open display”); /* get an appropriate visual */ vi = glXChooseVisual(dpy, DefaultScreen(dpy), attributeList); if (!vi) error(argv[0], “no suitable visual”); /* create a GLX context */ cx = glXCreateContext(dpy, vi, 0, GL_TRUE); /* create a colormap */ swa.colormap = XCreateColormap(dpy, RootWindow(dpy, vi->screen), vi->visual, AllocNone); /* create a window */ swa.border_pixel = 0; swa.event_mask = ExposureMask | StructureNotifyMask | KeyPressMask; win = XCreateWindow(dpy, RootWindow(dpy, vi->screen), 0, 0, 300, 300, 0, vi->depth, InputOutput, vi->visual, CWBorderPixel|CWColormap|CWEventMask, &swa); XStoreName(dpy, win, “simplest”); XMapWindow(dpy, win); /* connect the context to the window */ glXMakeCurrent(dpy, win, cx); for(;;) process_input(dpy); } |
A colormap determines the mapping of pixel values in the framebuffer to color values on the screen. Colormaps are created with respect to a specific visual.
When you create a window, you must supply a colormap for it. The visual associated with a colormap must match the visual of the window using the colormap. Most X programs use the default colormap because most X programs use the default visual. The easiest way to obtain the colormap for a particular visual is to call XCreateColormap():
Colormap XCreateColormap (Display *display, Window w, Visual *visual, int alloc) |
Here's how Example 3-2 calls XCreateColormap() in the following manner:
swa.colormap = XCreateColormap(dpy, RootWindow(dpy, vi->screen), vi->visual, AllocNone); |
The parameters specify the display, window, visual, and the number of colormap entries to allocate. The alloc parameter can have the special value AllocAll or AllocNone. While it is easy to simply call XCreateColormap(), you are encouraged to share colormaps. See Example 4-2 for details on how to do this.
Note that you cannot use AllocAll if the colormap corresponds to a visual that has transparent pixels, because the colormap cell that corresponds to the transparent pixel cannot be allocated with AllocAll. For more information about colormaps, see “Using Colormaps” in Chapter 4. For information on overlays, which use a visual with a transparent pixel, see “Using Overlays” in Chapter 4.
After creating a colormap, you can create a window using XCreateWindow(). Before calling XCreateWindow(), set the attributes you want in the attributes variable. When you make the call, indicate valuemask by OR-ing the symbolic constants that specify the attributes you have set. Here's how Example 3-2 does it in the following way:
swa.background_pixmap = None; swa.border_pixel = 0; swa.event_mask = ExposureMask | StructureNotifyMask | KeyPressMask; win = XCreateWindow( dpy, /*display*/ RootWindow(dpy, vi->screen), /*parent*/ 0, /*x coordinate*/ 0, /*y coordinate*/ 300, /*width*/ 300, /*height*/ 0, /*border width*/ vi->depth, /*depth*/ InputOutput, /*class*/ vi->visual, /*visual*/ CWBackPixmap|CWBorderPixel|CWColormap|CWEventMask, /*valuemask*/ &swa /*attributes*/ ); |
Most of the parameters are self-explanatory. However, the following three are noteworthy:
If the window you are creating is a top-level window (meaning it was created as a child of the root window), consider calling XSetWMProperties() to set the window's properties after you have created it.
void XSetWMProperties(Display *display, Window w, XTextProperty *window_name, XTextProperty *icon_name, char **argv, int argc, XSizeHints *normal_hints, XWMHints *wm_hints, XClassHint *class_hints) |
XSetWMProperties() provides a convenient interface for setting a variety of important window properties at once. It merely calls a series of other property-setting functions, passing along the values you pass in. For more information, see the man page.
Note that two useful properties are the window name and the icon name. The example program calls XStoreName() instead to set the window and icon names.
Applications should generally rely on the window manager to install the colormaps instead of calling XInstallColormap() directly. The window manager automatically installs the appropriate colormaps for a window whenever that window gets keyboard focus. Popup overlay menus are an exception.
By default, the window manager looks at the top-level window of a window hierarchy and installs that colormap when the window gets keyboard focus. For a typical X-based application, this is sufficient, but an application based on OpenGL typically uses multiple colormaps: the top-level window uses the default X colormap, and the Open GL window uses a colormap suitable for OpenGL.
To address this multiple colormap issue, call the function XSetWMColormapWindows() to pass the display, the top-level window, a list of windows whose colormaps should be installed, and the number of windows in the list.
The list of windows should include one window for each colormap, including the top-level window's colormap (normally represented by the top-level window). For a typical OpenGL program that does not use overlays, the list contains two windows: the OpenGL window and the top-level window. The top-level window should normally be last in the list. Xt programs may use XtSetWMColormapWindows() instead of XSetWMColormapWindows(), which uses widgets instead of windows.
Note: The program must call XSetWMColormapWindows() even if it is using a TrueColor visual. Some hardware simulates TrueColor through the use of a colormap. Even though the application does not interact with the colormap directly, it is still there. If you do not call XSetWMColormapWindows(), your program may run correctly only some of the time and only on some systems. |
Use the xprop program to determine whether XSetWMColormapWindows() was called. Click the window and look for the WM_COLORMAP_WINDOWS property. This should be a list of the windows. The last one should be the top-level window. Use xwininfo, providing the ID of the window as an argument, to determine what colormap the specified window is using and whether that colormap is installed.
This section describes different kinds of user input and explains how you can use Xlib to perform them. OpenGL programs running under the X Window System are responsible for responding to events sent by the X server. Examples of X events are Expose, ButtonPress, ConfigureNotify, and so on.
Note: In addition to mouse devices, Silicon Graphics systems support various other input devices (for example, spaceballs). You can integrate them with your OpenGL program using the X input extension. For more information, see the X Input Extension Library Specification available on the SGI Technical Publications Library. |
To handle mouse events, your program first has to request them and then use them in the main (event handling) loop. Here is an example code fragment from Xlib/mouse.c, an Xlib program that uses mouse motion events. Example 3-3 shows how the mouse processing, along with the other event processing, is defined.
Example 3-3. Event Handling With Xlib
static int process_input(Display *dpy) { XEvent event; Bool redraw = 0; static int mstate, omx, omy, mx, my; do { char buf[31]; KeySym keysym; XNextEvent(dpy, &event); switch(event.type) { case Expose: redraw = 1; break; case ConfigureNotify: glViewport(0, 0, event.xconfigure.width, event.xconfigure.height); redraw = 1; break; case KeyPress: (void) XLookupString(&event.xkey, buf, sizeof(buf), &keysym, NULL); switch (keysym) { case XK_Escape: exit(EXIT_SUCCESS); default: break; } case ButtonPress: if (event.xbutton.button == Button2) { mstate |= 2; mx = event.xbutton.x; my = event.xbutton.y; } else if (event.xbutton.button == Button1) { mstate |= 1; mx = event.xbutton.x; my = event.xbutton.y; } break; case ButtonRelease: if (event.xbutton.button == Button2) mstate &= ~2; else if (event.xbutton.button == Button1) mstate &= ~1; break; case MotionNotify: if (mstate) { omx = mx; omy = my; mx = event.xbutton.x; my = event.xbutton.y; update_view(mstate, omx,mx,omy,my); redraw = 1; } break; default: break; } } while (XPending(dpy)); return redraw; } |
The process_input() function is then used by the main loop:
while (1) { if (process_input(dpy)) { draw_scene(); ... } } |
When a user selects a window that has been completely or partly covered, the X server generates one or more Expose events. It is difficult to determine exactly what was drawn in the now-exposed region and redraw only that portion of the window. Instead, OpenGL programs usually just redraw the entire window.
If redrawing is not an acceptable solution, the OpenGL program can do all your rendering into a GLXPixmap instead of directly to the window; then, any time the program needs to redraw the window, you can simply copy the GLXPixmap's contents into the window using XCopyArea(). For more information, see “Using Pixmaps” in Chapter 4.
Note: Rendering to a GLXPixmap is much slower than rendering to a window and may not allow access to many features of the graphics hardware. |
When handling X events for OpenGL programs, remember that Expose events come in batches. When you expose a window that is partly covered by two or more other windows, two or more Expose events are generated, one for each exposed region. Each one indicates a simple rectangle in the window to be redrawn. If you are going to redraw the entire window, read the entire batch of Expose events. It is wasteful and inefficient to redraw the window for each Expose event.
The simplest approach to text and font handling in GLX is using the glXUseXFont() function together with display lists. This section shows you how to use the function by providing an example program. Note that this information is relevant regardless of whether you use widgets or program in Xlib.
The advantage of glXUseXFont() is that bitmaps for X glyphs in the font match exactly what OpenGL draws. This solves the problem of font matching between X and OpenGL display areas in your application.
To use display lists to display X bitmap fonts, your code should do the following:
Use X calls to load information about the font you want to use.
Generate a series of display lists using glXUseXFont(), one for each glyph in the font.
The glXUseXFont() function automatically generates display lists (one per glyph) for a contiguous range of glyphs in a font.
To display 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().
Each glyph display list contains a glBitmap() call to render the glyph and update the current raster position based on the glyph's width.
The example code fragment provided in Example 3-4 prints the string “The quick brown fox jumps over a lazy dog” in Times Medium. It also prints the entire character set, from ASCII 32 to 127.
Note: You can also use the glc library, which sits atop of OpenGL, for fonts and strings. The library is not specific to GLX and provides other functions in addition to glXUseXFont(). |
Example 3-4. Font and Text Handling
#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 (); } |