This chapter first presents background information that you will find useful when working with OpenGL and the X Window System. Following the background information is a simple example program that displays OpenGL code in an X window. This chapter uses the following topics:
To effectively integrate your OpenGL program with the X Window System, you need to understand the basic concepts described in the following sections:
Note: If you are unfamiliar with the X Window System, you are urged to learn about it using some of the material listed under “Background Reading”. |
The X Window System is the only window system provided for Silicon Graphics systems running IRIX or Linux.
X is a network-transparent window system: an application need not be running on the same system on which you view its display. In the X client/server model, you can run programs on the local workstation or remotely on other workstations connected by a network. The X server handles input and output and informs client applications when various events occur. A special client, the window manager, places windows on the screen, handles icons, and manages titles and other window decorations.
When you run an OpenGL program in an X environment, window manipulation and event handling are performed by X functions. Rendering can be done with both X and OpenGL. In general, X is for the user interface and OpenGL is used for rendering 3D scenes or for imaging.
There are two different X servers provided depending on the operating system and type of graphics supported:
For traditional IRIX graphics systems such as VPro, InfinitePerformance, and InfiniteReality, Silicon Graphics uses its own X server, called Xsgi.
For IRIX Oynx4 systems and all Linux systems, Silicon Graphics uses an X server from the open source XFree86 project. This server contains newer X extensions such as RENDER but does not support all of the extensions of the Xsgi server.
While both Xsgi and XFree86 are based on the X Consortium X11R6 source code base, Xsgi includes some enhancements that not all servers have: support for visuals with different colormaps, overlay windows, the Display PostScript extension, the Shape extension, the X Input extension, the Shared Memory extension, the SGI video control extensions, and simultaneous displays on multiple graphics monitors. Specifically for working with OpenGL programs, Silicon Graphics offers the GLX extension described in the next section.
To see what extensions to the X Window System are available on your current system, execute xdpyinfo and check the extensions listed below the number of extensions line.
The GLX extension, which integrates OpenGL and X, is used by X servers that support OpenGL. The Xsgi and XFree86 servers shipped with Silicon Graphics systems all support GLX. GLX is both an API and an X extension protocol for supporting OpenGL. GLX routines provide basic interaction between X and OpenGL. Use them, for example, to create a rendering context and bind it to a window.
To compile a program that uses the GLX extension, include the GLX header file (/usr/include/GL/glx.h), which includes relevant X header files and the standard OpenGL header files. If desired, include also the GLU utility library header file (/usr/include/GL/glu.h).
Table 2-1 provides an overview of the headers and libraries you need to include.
Table 2-1. Headers and Link Lines for OpenGL and Associated Libraries
Library | Header | Link Line |
---|---|---|
GL/gl.h | -lGL | |
GLU | GL/glu.h | -lGLU |
GLX | GL/glx.h | -lGL (includes GLX and OpenGL) |
X11 | X11/xlib.h | -lX11 |
To help you understand how to use your OpenGL program inside the X Window System environment, this section describes the following concepts you will encounter throughout this guide:
A standard X visual specifies how the server should map a given pixel value to a color to be displayed on the screen. Different windows on the screen can have different visuals.
Currently, GLX allows RGB rendering to TrueColor and DirectColor visuals and color index rendering to StaticColor or PseudoColor visuals. See Table 4-1 for information about the visuals and their supported OpenGL rendering modes. Framebuffer configurations, or FBConfigs, allow additional combinations. For details, see the section “Using Visuals and Framebuffer Configurations” in Chapter 4.
GLX overloads X visuals to include both the standard X definition of a visual and information specific to OpenGL about the configuration of the framebuffer and ancillary buffers that might be associated with a drawable. Only those overloaded visuals support both OpenGL and X rendering. GLX, therefore, requires that an X server support a high-minimum baseline of OpenGL functionality.
When you need visual information, do the following:
Use xdpyinfo to display all the X visuals your system supports.
Use glxinfo or findvis to find visuals that can be used with OpenGL.
The findvis command (only available on SGI IRIX systems) can actually look for available visuals with certain attributes. See the xdpyinfo, glxinfo, and findvis man pages for more information.
Not all X visuals support OpenGL rendering, but all X servers capable of OpenGL rendering have at least two OpenGL capable visuals. The exact number and type vary among different hardware systems. A Silicon Graphics system typically supports many more than the two required OpenGL capable visuals. An RGBA visual is required for any hardware system that supports OpenGL; a color index visual is required only if the hardware requires color index. To determine the OpenGL configuration of a visual, you must use a GLX function.
Visuals are discussed in some detail in “Using Visuals and Framebuffer Configurations” in Chapter 4. Table 4-1 illustrates which X visuals support which type of OpenGL rendering and whether the colormaps for those visuals are writable or not.
As a rule, a drawable is something into which X can draw, either a window or a pixmap. An exception is a pixel buffer ( pbuffer), which is a GLX drawable but cannot be used for X rendering. A GLX drawable is something into which both X and OpenGL can draw, either an OpenGL capable window or a GLX pixmap. (A GLX pixmap is a handle to an X pixmap that is allocated in a special way; see Figure 4-2.) Different ways of creating a GLX drawable are discussed in “Drawing-Area Widget Setup and Creation” in Chapter 3, “Creating a Colormap and a Window” in Chapter 3, and “Using Pixmaps” in Chapter 4.
Pbuffers were promoted from the SGIX_pbuffer extension to GLX 1.1 into a standard part of GLX 1.3, which is supported on all current Silicon Graphics visualization systems. So, the SGIX_pbuffer extension is no longer described in detail in this document.
A rendering context ( GLXContext) is an OpenGL data structure that contains the current OpenGL rendering state, an instance of an OpenGL state machine. (For more information, see the section “OpenGL as a State Machine” in Chapter 1, “Introduction to OpenGL,” of the OpenGL Programming Guide.) Think of a context as a complete description of how to draw what the drawing commands specify.
Only one rendering context can be bound to at most one window or pixmap in a given thread. If a context is bound, it is considered the current context.
OpenGL routines do not specify a drawable or rendering context as parameters. Instead, they implicitly affect the current bound drawable using the current rendering context of the calling thread.
Resources, in X, are data structures maintained by the server rather than by client programs. Colormaps (as well as windows, pixmaps, and fonts) are implemented as resources.
Rather than keeping information about a window in the client program and sending an entire window data structure from client to server, for instance, window data is stored in the server and given a unique integer ID called an XID. To manipulate or query the window data, the client sends the window's ID number; the server can then perform any requested operation on that window. This reduces network traffic.
Because pixmaps and windows are resources, they are part of the X server and can be shared by different processes (or threads). OpenGL contexts are also resources. In standard OpenGL, they can be shared by threads in the same or a different process through the use of FBConfigs. For details, see the section “Using Visuals and Framebuffer Configurations” in Chapter 4.
A colormap maps pixel values from the framebuffer to intensities on the screen. Each pixel value indexes into the colormap to produce intensities of red, green, and blue for display. Depending on hardware limitations, one or more colormaps may be installed at one time so that windows associated with those maps display with the correct colors. If there is only one colormap, two windows that load colormaps with different values look correct only when they have their particular colormap installed. The X window manager takes care of colormap installation and tries to make sure that the X client with input focus has its colormaps installed. On all systems, the colormap is a limited resource.
Every X window needs a colormap. If you are using the OpenGL drawing-area widget to render in RGB mode into a TrueColor visual, you may not need to worry about the colormap. In other cases, you may need to assign one. For additional information, see “Using Colormaps” in Chapter 4. Colormaps are also discussed in detail in O'Reilly, Volume One.
This section first describes programming with widgets and with the Xt (X Toolkit) library, then briefly mentions some other toolkits that facilitate integrating OpenGL with the X Window System.
A widget is a piece of a user interface. Under IRIS IM, buttons, menus, scroll bars, and drawing windows are all widgets.
It usually makes sense to use one of the standard widget sets. A widget set provides a collection of user interface elements. A widget set may contain, for example, a simple window with scrollbars, a simple dialog with buttons, and so on. A standard widget set allows you to easily provide a common look and feel for your applications. The two most common widget sets are OSF/Motif and the Athena widget set from MIT.
If you develop on IRIX, Silicon Graphics strongly encourages using IRIS IM, the Silicon Graphics port of OSF/Motif, for conformance with Silicon Graphics user interface style and integration with the IRIX Interactive Desktop. If you use IRIS IM, your application follows the same conventions as other applications on the desktop and becomes easier to learn and to use. If you develop for cross-platform environments or only for Linux environments, use those features of OSF/Motif that are not specific to SGI or use other toolkits such as GTK or Qt.
The examples in this guide use IRIS IM. Using IRIS IM makes it easier to deal with difficult issues such as text management and cut and paste. IRIS IM makes writing complex applications with many user interface components relatively simple. This simplicity does not come free; an application that has minimal user interactions incurs a performance penalty over the same application written in Xlib. For an introduction to Xlib, see “Xlib Library” in Chapter 1.
Widgets are built using Xt, the X Toolkit Intrinsics, a library of routines for creating and using widgets. Xt is a “meta” toolkit used to build toolkits like Motif or IRIS IM; you can, in effect, use it to extend the existing widgets in your widget sets. Xt uses a callback-driven programming model. It provides tools for common tasks like input handling and animation and frees you from having to handle a lot of the details of Xlib programming.
Note that in most (but not all) cases, using Xlib is necessary only for colormap manipulation, fonts, and 2D rendering. Otherwise, Xt and IRIS IM are enough, though you may pay a certain performance penalty for using widgets instead of programming directly in Xlib.
Standard Xt is discussed in detail in O'Reilly, Volume Four. Standard Motif widgets are discussed in more detail in O'Reilly, Volume Six. See “Background Reading” for full bibliographic information and for pointers to additional documents about Motif and IRIS IM. The book on OpenGL and X (Kilgard 1996) is particularly helpful for OpenGL developers.
Silicon Graphics makes several other tools and toolkits available that can greatly facilitate designing your IRIS IM interface. For more information, see “Open Inventor” in Chapter 1, “IRIS ViewKit” in Chapter 1, and “Porting Applications between IRIX and Linux” in Chapter 1.
To help you get started, this section presents the simplest possible example program that illustrates how to integrate an OpenGL program with IRIS IM. The program itself is followed by a brief explanation of the steps involved and a more detailed exploration of the steps to follow during integration and setup of your own program.
Window creation and event handling, either using Motif widgets or using the Xlib library directly, are discussed in Chapter 3, “OpenGL and X: Examples”.
The program in Example 2-1 (motif/simplest.c) performs setup, creates a window using a drawing-area widget, connects the window with a rendering context, and performs some simple OpenGL rendering (see Figure 2-1).
Example 2-1. Simple IRIS IM Program
/* * simplest - simple single buffered RGBA motif program. */ #include <stdlib.h> #include <stdio.h> #include <Xm/Frame.h> #include <X11/GLw/GLwMDrawA.h> #include <X11/keysym.h> #include <X11/Xutil.h> #include <GL/glx.h> static int attribs[] = { GLX_RGBA, None}; static String fallbackResources[] = { "*useSchemes: all", “*sgimode:True”, "*glxwidget*width: 300", "*glxwidget*height: 300", "*frame*shadowType: SHADOW_IN", NULL}; /*Clear the window and draw 3 rectangles*/ 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(); } /*Process input events*/ 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; } } /*Process window resize events*/ * calling glXWaitX makes sure that all x operations like * * XConfigureWindow to resize the window happen befor the * * OpenGL glViewport call.*/ 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); } /*Process window expose events*/ static void expose(Widget w, XtPointer client_data, XtPointer call) { draw_scene(); } main(int argc, char *argv[]) { Display *dpy; XtAppContext app; XVisualInfo *visinfo; GLXContext glxcontext; Widget toplevel, frame, glxwidget; toplevel = XtOpenApplication(&app, "simplest", 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); XtRealizeWidget(toplevel); glxcontext = glXCreateContext(dpy, visinfo, 0, GL_TRUE); GLwDrawingAreaMakeCurrent(glxwidget, glxcontext); XtAppMainLoop(app); } |
As the example program illustrates, integrating OpenGL drawing routines with a simple IRIS IM program involves only a few steps. Except for window creation and event handling, these steps are actually independent of whether the program uses Xt and Motif or Xlib.
The rest of this chapter looks at each step. Each step is described in a separate section:
Note that event handling, which is different depending on whether you use Xlib or Motif, is described in “Input Handling With Widgets and Xt” in Chapter 3 and, for Xlib programming, “Xlib Event Handling” in Chapter 3.
Before making any GLX (or OpenGL) calls, a program must open a display (required) and should find out whether the X server supports GLX (optional).
To open a display, use XOpenDisplay() if you are programming with Xlib, or XtOpenApplication() if you are working with widgets as in Example 2-1 above. XtOpenApplication() actually opens the display and performs some additional setup:
Initializing Xt
Opening an X server connection
Creating an X context (not a GLX context) for the application
Creating an application shell widget
Processing command-line options
Registering fallback resources
It is recommend (but not required) that you find out whether the X server supports GLX by calling glXQueryExtension().
Bool glXQueryExtension ( Display *dpy, int *errorBase, int *eventBase ) |
In most cases, NULL is appropriate for both errorBase and eventBase. See the glXQueryExtension man page for more information.
Note: This call is not required (and therefore not part of motif/simplest.c), because glXChooseVisual() simply fails if GLX is not supported. It is included here because it is recommended for the sake of portability. |
If glXQueryExtension() succeeds, use glXQueryVersion() to find which version of GLX is being used; an older version of the extension may not be able to do everything your version can do.The following pseudo code demonstrates checking for the version number:
glXQueryVersion(dpy, &major, &minor); if (((major == 1) && (minor == 0)){ /*assume GLX 1.0, avoid GLX 1.1 functionality*/ } else{ /*can use GLX 1.1 functionality*/ } } |
GLX 1.3 is supported on all current Silicon Graphics platforms under IRIX 6.5 and Linux. In addition to providing a few new functions and a mechanism for using extensions (introduced in GLX 1.1), GLX 1.3 promoted the SGIX_fbconfig, SGIX_pbuffer, and SGIX_make_current_read GLX extensions to become standard parts of the core 1.3 API.
A visual determines how pixel values are mapped to the screen. The display mode of your OpenGL program (RGBA or color index) determines which X visuals are suitable. To find a visual with the attributes you want, call glXChooseVisual() with the desired parameters. The following is the function's format:
XVisualInfo* glXChooseVisual(Display *dpy, int screen, int *attribList) |
The first two parameters specify the display and screen. The display was earlier opened with XtOpenApplication() or XOpenDisplay(). Typically, you specify the default screen that is returned by the DefaultScreen() macro.
The third parameter is a list of the attributes you want your visual to have, specified as an array of integers with the special value None as the final element in the array. Attributes can specify the following:
Whether to use RGBA or color-index mode (depending on whether GLX_RGBA is True or False)
Whether to use double-buffering or not (depending on the value of GLX_DOUBLEBUFFER)
How deep the depth buffer should be (depending on the value of GLX_DEPTH_SIZE)
In Example 2-1, the only attribute specified is an RGB display:
static int attribs[] = { GLX_RGBA, None}; |
The visual returned by glXChooseVisual() is always a visual that supports OpenGL. It is guaranteed to have Boolean attributes matching those specified and integer attributes with values at least as large as those specified. For detailed information, see the glXChooseVisual man page.
Note: Be aware that Xlib provides these three different but related visual data types. glXChooseVisual() actually returns an XVisualInfo*, which is a different entity from a visual* or a visual ID. XCreateWindow(), on the other hand, requires a visual*, not an XVisualInfo*. |
The framebuffer capabilities and other attributes of a window are determined statically by the visual used to create it. For example, to change a window from single-buffer to double-buffer, you have to switch to a different window created with a different visual.
Note: In general, ask for one bit of red, green, and blue to get maximum color resolution. Zero matches to the smallest available color resolution. |
Instead of calling glXChooseVisual(), you can also choose a visual as follows:
Ask the X server for a list of all visuals using XGetVisualInfo() and then call glXGetConfig() to query the attributes of the visuals. Be sure to use a visual for which the attribute GLX_USE_GL is True.
If you have decided to use IRIS IM, call XtCreateManagedWidget(), provide GLwDrawingAreaWidget as the parent, and let the widget choose the visual for you.
GLX 1.3 allows you to create and choose a glXFBConfig construct, which packages GLX drawable information, for use instead of a visual.
Creating a rendering context is the application's responsibility. Even if you choose to use IRIS IM, the widget does no context management. Therefore, before you can draw anything, you must create a rendering context for OpenGL using glXCreateContext(), which has the following function format:
GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, GLXContext shareList, Bool direct) |
The following describes the arguments:
dpy | The display you have already opened. | |
vis | The visual you have chosen with glXChooseVisual(). | |
sharedList | A context for sharing display lists or NULL to not share display lists. | |
direct | Direct or indirect rendering. For best performance, always request direct rendering. The OpenGL implementation automatically switches to indirect rendering when direct rendering is not possible (for example, when rendering remotely). See “Direct and Indirect Rendering” in Chapter 4. |
After picking a visual and creating a context, you need to create a drawable (window or pixmap) that uses the chosen visual. How you create the drawable depends on whether you use Xlib or Motif calls and is described, with program examples, in “Drawing-Area Widget Setup and Creation” in Chapter 3 and “Creating a Colormap and a Window” in Chapter 3.
If you are working with Xlib, bind the context to the window by calling glXMakeCurrent(). Example 3-2 is a complete Xlib program and illustrates how the function is used.
If you are working with widgets and have an OpenGL context and a window, bind them together with GLwDrawingAreaMakeCurrent(). This IRIS IM function is a front end to glXMakeCurrent(); it allows you to bind the context to the window without having to know the drawable ID and display.
If GLwDrawingAreaMakeCurrent() is successful, subsequent OpenGL calls use the new context to draw on the given drawable. The call fails if the context and the drawable are mismatched—that is, if they were created with different visuals.
Note: Do not make OpenGL calls until the context and window have been bound (made current). |
For each thread of execution, only one context can be bound to a single window or pixmap.
Note: GLX 1.3 allows you to attach separate read and write drawables to a GLX context. For details, see section “SGI_make_current_read—The Make Current Read Extension” in Chapter 6. |
A window can become visible only if it is mapped and all its parent windows are mapped. Note that mapping the window is not directly related to binding it to an OpenGL rendering context, but both need to happen if you want to display an OpenGL application.
Mapping the window or realizing the widget is not synchronous with the call that performs the action. When a window is mapped, the window manager makes it visible if no other actions are specified to happen before. For example, some window managers display just an outline of the window instead of the window itself, letting the user position the window. When the user clicks, the window becomes visible.
If a window is mapped but is not yet visible, you may already have set OpenGL state; for example, you may load textures or set colors, but rendering to the window is discarded (this includes rendering to a back buffer if you are doing double-buffering). You need to get an Expose event—if using Xlib—or the expose() callback before the window is guaranteed to be visible on the screen. The init() callback does not guarantee that the window is visible, only that it exists.
How you map the window on the screen depends on whether you have chosen to create an X window from scratch or to use a widget:
Table 2-2 summarizes the steps that are needed to integrate an OpenGL program with the X Window System. While some functions differ in IRIS IM and Xlib, note that the GLX functions are usually common.
Table 2-2. Integrating OpenGL and X
Step | Using IRIS IM | Using Xlib |
---|---|---|
| XtOpenApplication | XOpenDisplay |
Making sure GLX is supported (optional) | glXQueryExtension glXQueryVersion | glXQueryExtension glXQueryVersion |
| glXChooseVisual | glXChooseVisual |
“Creating a Rendering Context”
| glXCreateContext | glXCreateContext |
(see Chapter 3, “OpenGL and X: Examples” ) | XtVaCreateManagedWidget, with glwMDrawingAreaWidgetClass | XCreateColormap XCreateWindow |
“Binding the Context to the Window”
| GLwDrawingAreaMakeCurrent | glXMakeCurrent |
| XtRealizeWidget | XMapWindow |
Additional example programs are provided in Chapter 3, “OpenGL and X: Examples”.
This section lists compiler options for individual libraries then lists groups or libraries typically used together.
This sections lists link lines and the libraries that will be linked.
–lGL | ||
–lX11 | Xlib, X client library for X11 protocol generation. | |
–lXext | The X Extension library provides infrastructure for X client-side libraries (like OpenGL). | |
–lGLU | OpenGL utility library. | |
–lXmu | Miscellaneous utilities library (includes colormap utilities). | |
–lXt | X toolkit library, infrastructure for widgets. | |
–lXm | Motif widget set library. | |
–lGLw | OpenGL widgets, Motif and core OpenGL drawing-area widgets. | |
–lXi | X input extension library for using extra input devices. | |
–limage | RGB file image reading and writing routines.The image library is only supported under IRIX. Open source alternatives like libjpeg and libpnm provide image I/O functions and are better alternatives when writing code that must also run on Linux and other platforms. | |
–lm | Math library. Needed if your OpenGL program uses trigonometric or other special math routines. |