by Kamran Husain
IN THIS CHAPTER
This chapter is really two chapters in one. I will discuss two topics here:
After you read this chapter, you will have an idea of how to draw to an X drawable, and use Motif and PEX together. This chapter is not a tutorial on PEX, nor will you become the world's expert on writing additions to X servers. This chapter will introduce you to techniques that you can use to add features to the X server. You will also learn where to look for more information about PEX and X servers.
The X Window system is primarily a two-dimensional graphical system. Due to the lack of standards in the three-dimensional (3-D) area, there hasn't been an evolution of good 3-D developmental tool libraries. However, PEX is supposed to alleviate this problem by providing a consistent set of toolkit calls, which enables a user to support 3-D software with little effort.
PEX originally stood for PHIGS Extensions to X. PHIGS stands for Programmer's Hierarchical Interactive Graphics System. PEX has been adopted by the Common Open Software Environment (COSE) for X11 releases that are later than 2.2.
However, PEX is simply historical at this point, because the last version of the PEX protocol (Version 6.0) is not designed specifically for PHIGS at all. PEX is now designed to support 3-D application programs. PEX is an extension to the Core X Protocol to provide 3-D graphics support within the X Window environment. Included in the X11R5 distribution is code for the Sample Implementation of the extensions to the X Window server, which implements the functionality defined by the PEX Protocol Extensions.
In order to access the PEX functional extensions to the X server, one must use an application that generates PEX Protocol. The application can either generate the Protocol bytestream itself, or use something called an Application Protocol Interface (API). One such API provided with the X11R5 distribution is the PHIGS 3-D graphics standard. This is a port of the PHIGS C language binding onto an internal layer, which generates the PEX Protocol enabling this particular PHIGS implementation to work within the X Window environment. Other alternate APIs are available via anonymous FTP from export.lcs.mit.edu.
When discussing PEX, it is important not to confuse the protocol with the API. The API is the conceptual model of 3-D graphics that the application developer sees when developing a client program. The PEX protocol is generated by the API, and is interpreted by the X server to perform graphics requests on behalf of the client program.
One API provided with the R5 PEX-SI is a PHIGS/PHIGS-PLUS API. The PHIGS/PHIGS-PLUS standards are specified in two parts. First, a functional description explains each operation conceptually, in a language-independent manner. Second, language bindings are used to bind the particular PHIGS functions to the semantics of the language. The PEX-SI comes with an application programmer interface that conforms to the latest revision of the PHIGS/PHIGS-PLUS C language binding.
If your version of R5 is patched through patch number 22, you have a second MIT-supplied API called PEXlib. PEXlib is to the PEX protocol what Xlib is to the core X protocol. PEXlib provides an interface that is as close as possible to a one-to-one correspondence between functions and protocol requests. It is intended to be a systems programming interface (so, people developing graphics toolkits and graphics systems will implement their system on top of PEXlib). It is proposed that the PHIGS API be ported to PEXlib when PEXlib is finalized. This change would not affect programs written to the existing PHIGS API.
However, because PEXlib is intimately tied to the protocol, it is expected that there will be changes between the current PEXlib (which supports Version 5.0 and 5.1 of the PEX protocol) and the PEXlib that supports the next major version of the PEX protocol, Version 6.0. Naturally, every attempt will be made to make the changes to the API minimal. The nature of the changes from 5.1 to 6.0 are not such that every primitive will be affected; rather the changes deal with the sticky problems of subsets, multibuffering, and other issues of global rendering semantics.
Find out whether you have PEX available on your X server first. By default, PEX support is not built into the servers you received for Linux. The xdpyinfo command displays all the extensions supported by a server.
Output from the PEX command should contain strings of the following form:
number of extensions: 7 XTestExtension1 SHAPE MIT-SHM X3D-PEX (<-- This is the line you are looking for) Multi-buffering MIT-SUNDRY-NONSTANDARD
If one of the extensions listed is X3D-PEX, your server supports PEX. If you do not see this line, you must build the server yourself.
To build a server that only includes the drivers you need, use the LinkKit instead of compiling the complete X system. Using the LinkKit package is much easier. The LinkKit package can be found in /usr/X386/lib/Server.
The LinkKit package contains a file called site.def for you to edit. The site.def file contains the site-specific information for your system. Edit the site.def file to define which servers you want to build, and the drivers and font renderers (programs that generate fonts for display) you want to include.
Let's examine the site.def file in a bit of detail. See Listing 60.1 for the site.def file that I used to create a PEX server for my machine.
XCOMM $XFree86: mit/server/ddx/x386/LinkKit/site.def.LK,v 2.11 1994/04/10 05:49:56 dawes Exp $ /* Configuration file for Server Link Kit */ #ifdef BeforeVendorCF /* * Change these definitions if you need to override the defaults: */ /* * HasGcc: defaults: * SVR3,4: YES * Mach, 386bsd: YES */ /* #define HasGcc NO */ /* * HasGcc2: (should also set HasGcc) * defaults: * SVR3,4: YES * Mach: YES * 386bsd: NO */ /* #define HasGcc2 NO */ /* * If the link kit you are using was built with gcc2, and you are using * a different compiler: * 1. Install libgcc.a in a directory searched by your `ld' * 2. Set NeedLibGcc to YES */ #define NeedLibGcc NO /* * Uncomment this if you want to link with the Gnu malloc library */ /* #define GnuMalloc YES */ /* * GnuMallocLib: link-time flags to include the Gnu malloc library. * this is only used when GnuMalloc is set to YES. * defaults: * 386bsd: -lgnumalloc * others: -lgmalloc */ /* #define GnuMallocLib -L/usr/local/gnu -lmalloc */ /* * Server configuration parameters */ #define FontRenderers Speedo Type1 #define X386Vga2Drivers et4000 et3000 pvga1 gvga tvga8900 ncr \ compaq oak generic #define X386Vga16Drivers et4000 tvga8900 generic #define X386Vga256Drivers et4000 et3000 pvga1 gvga ati tvga8900 cirrus \ ncr compaq oak #define X386Hga2Drivers /**/ /* To enable the hga2 driver, replace the above line with the following */ /* #define X386Hga2Drivers hga6845 */ /* * To include the generic banked monochrome driver in the monochrome server, * uncomment this with one of the following low level drivers * hgc1280 [Hyundai HGC-1280 1280x1024] * sigma [Sigma L-View] * visa [???] * apollo [???] * ... * (list is subject to grow) */ /* #define X386Bdm2Drivers hgc1280 sigma visa apollo */ /* #define XF86S3Drivers mmio_928 s3_generic */ /* * Set which servers to build. Change the YES to NO for servers you don't * want to build. */ /* The SVGA color server */ #define XF86SVGAServer YES /* The 16-color VGA server */ #define XF86VGA16Server NO /* The VGA mono server */ #define XF86MonoServer NO /* The S3 server */ #define XF86S3Server NO /* The IBM 8514/A server */ #define XF86I8514Server NO /* The Mach8 server */ #define XF86Mach8Server NO /* The Mach32 server */ #define XF86Mach32Server NO /* Set the default server (ie the one that gets the sym-link to "X") */ /* #define XFree86DefaultServer XF86_S3 */ /* * If you want PEX (and this link kit was built with PEX support), uncomment * the following */ /* #define BuildPexExt YES */ #define BuildPexExt YES #endif /* BeforeVendorCF */ #ifdef AfterVendorCF /* If you are using a different ProjectRoot, set it here */ /* #ifdef ProjectRoot #undef ProjectRoot #endif #define ProjectRoot /usr/X11R5 */ #endif /* AfterVendorCF */
Note the following items about this site.def file:
For Linux, NeedLibGcc is set to NO.
The servers are available to you via LinkKit are shown in Table 60.1. To create any of these servers, you have to set value of the corresponding variable to YES:
Table 60.1. Server types in site.def.
Server Type | Variable to Set |
256-color server | XF86SVGAServer |
256-color server | XF86SVGAServer |
16-color server | XF86VGA16Server |
Monochrome server | XF86MonoServer |
S3 server | XF86S3Server |
Mach8 server | XF86Mach8Server |
Mach32 server | XF86Mach32Server |
IBM 8514/A server | XF86I8514Server |
In the sample site.def in Listing 60.1, I have set only the 256-color SVGA server to be built. All other servers will not be built.
The Drivers variables define the video drivers that you want to include in a server. The order of drivers determines the order in which the server probes the video card to determine which driver to use. The generic driver should be the last one included in the monochrome and 16-color servers because its probe always succeeds.
The generic_s3 driver should be the last one included in the S3 servers for similar reasons.
After you have edited the site.def file, you must create the Makefile.
To build the Makefile, run this command:
# ./mkmf
Then, run make to link the servers that you have configured in the site.def file. This command takes a while. After this command is done, run the make install command to install the new servers:
# make install
Now start X, run the window manager, and in an xterm use the xdpyinfo command to see whether the PEX extensions are there. It is possible to see which drivers are included in the server by running the X server with the -showconfig flag.
If you are including a driver that it not part of the standard distribution, make a directory in drivers/vga256 (drivers/vga2 if it is for the monochrome server; drivers/vga16 if it is for the 16-color server; or drivers/bdm2 if it is for the bdm2 monochrome server's bdm2 screen). Copy either the source or the .o file, and a suitable Imakefile, into that directory. The name of the directory should be the same as the name of the driver. If you are adding an additional font renderer, put the library in ./renderers. Look at the VGADriver.Doc file for more details.
You have PEX files on the CD-ROM at the back of this book.
There are several examples of PEX code available on the Internet. One sample library can be found at the site export.lcs.mit.edu in the /R5contrib/R5contrib-fixes directory as the file PEX.examples.tar.Z.
Let's look at an example of a simple PEX program that prints the line Howdy World. (Hello World is a bit overused.) The following is the listing for printing this line:
#include "phigs/phigs.h" #include "X11/Xlib.h" #include "X11/Xatom.h" #include "strings.h" char windowName[] = "PEX in Linux"; char HelloStr[] = "Howdy World"; main() { Pconnid_x_drawable connid; Display *display; int screen; Ppoint text_pt; popen_phigs(NULL,0); /* open a conn. to PHIGS server */ /* ** Set the error file name to NULL ** Set default memory size to zero */ connid.display = display = XOpenDisplay( NULL ); screen = DefaultScreen( display ); connid.drawable_id = XCreateSimpleWindow( display, RootWindow( display, screen ), 0, 0, 600, 600, 4, WhitePixel( display, screen ), BlackPixel( display, screen ) ); XChangeProperty( display, connid.drawable_id, XA_WM_NAME, XA_STRING, 8, PropModeReplace, (unsigned char *) windowName , strlen(windowName) ); XMapWindow( display, connid.drawable_id ); popen_ws( 1, &connid, phigs_ws_type_x_drawable ); popen_struct( 1 ); text_pt.x = 0.2; text_pt.y = 0.5; pset_char_ht( 0.05 ); ptext( &text_pt, HelloStr, strlen(HelloStr)): pclose_struct( ); ppost_struct( 1, 1, 1.0 ); printf("Hit return to exit"); getchar(); pclose_ws( 1 ); pclose_phigs( );
}
The first executable line in this file opens a connection to the PEX server with a call to popen_phigs(NULL,0). The NULL parameter sets the error filename to nothing, and the 0 sets the default memory size to 0.
The next lines set the display, screen, and window IDs for this application. The window ID is set to point to the root window for the application with a call to the XCreateSimpleWindow function. The display and screen IDs are set to the defaults. Note that you are using low-level X Window system function calls to create the root window. This example tells you that it's possible to access all the low-level X Window functions, in addition to the PEX functions.
The XChangeProperty function call sets the window name to the one desired. The display and window ID (connid.drawable_id) are set to that of the root window of the application.
The XMapWindow function maps your display to the current window ID.
The popen_ws() call opens the workspace for the display for you to be able to draw on. You then set the test position for the Howdy World string. Then, open a structure for writing with PEX primitives to set the text_pt structure with the text. It is necessary to call the pclose_struct() function when done with the drawing area buffer. Lastly, post the structure to ppost_struct.
Wait until the user gives the keystroke you want, and then end the application. In this case, you must call two functions before ending the application. One is a call to pclose_ws( 1 ) for closing the workspace; the other is a call to pclose_phigs( ) for shutting down the PEX server.
In the previous example, the drawable was used to create the drawing patterns on a drawing area widget. PEXlib by itself is intended to be an interface into the lower-level Xlib. PEXlib gives you the capability to do 3-D mappings, shading, and so on, along with other geometric transformations.
This section covers the basic act of combining PEXlib with Motif. This way, you can get a drawable area under Motif and be able to use PEX functions on it. In addition to this, you still have the Motif framework to add your own menus to it. Here are the steps to combine Motif and PEXlib:
For initializing the toolkit, you must use a long method instead of XtAppInitialize. This long method enables you to select the best visual you can get for PEX. The following code segment will suffice:
XtToolkitInitialize(); app_context = XtCreateApplicationContext(); cp_argc = argc; argv_sz = argc * sizeof(char *); cp_argv = (char **)XtMalloc(argv_sz); memcpy(cp_argv, argv, argv_sz); /* copy the pointers */ display = XtOpenDisplay(app_context, argv[0], argv[0], "pEX", /* Class Name */ NULL, 0, /* no resource options */ &argc, argv); if (display == (Display *) NULL) abort("Unable to open display");
You are making a copy of argc and argv to preserve their values, because the call to XtOpenDisplay() mangles these original values. It's necessary to check whether the display pointer is set to a valid value when you return from XtOpenDisplay() because the display may not have been opened for various reasons, and you do not want to work a NULL pointer for the display.
Initialize the PEXlib functions with a call to the function PEXInitialize(). The syntax for this call is as follows:
#include <X11/PEX5/PEXlib.h> int PEXInitialize(Display *dp, PEXExtensionInfo *info_ptr, int message_length, char *msg);
The PEXExtensionInfo pointer should point to a structure to which you want this function to return information about the PEX server. The message array should be about 80 characters long, and contain the text for any returned error messages. The function returns 0 if no errors occurred; otherwise, it returns one of the following values:
Also, note that I used PEX5 as the location of the include files. When PEX 6.0 comes along, you may have to change this PEX5 reference to PEX6 in all the code you have to date. A bummer indeed, but necessary for an upgrade.
The returned PEXExtensionInfo structure is of the following form:
typedef struct { unsigned_short major_version; unsigned_short minor_version; unsigned long release; unsigned long subset_info; char *vendor_name; int major_opcode; int first_event; int first_error; } PEXExtensionInfo;
The major version is usually 5 or 6, and the minor version either 0 or 1. The vendor name and release are vendor-specific. The subset information contains information about the features in your PEX server, and can have the following values:
PEXImmediateMode | Enables drawing primitives that are sent directly to the display. |
PEXWorkstationOnly | Enables workstation resources. |
PEXStructureMode | Enables drawing primitives to be stored in a structure before being sent to the display. |
PEXCompeleteImplementation | Enables all of these functions. |
Now create the window with the colormap and the best visual for PEX. The way to do this is as follows:
XStandardColormap colormap; Colormap PEX_colormap; /* open display as before */ screen = DefaultScreen(display); visual = DefaultVisual(display,screen); depth = DefaultDepth(display,screen); status = GetStdColormap(display, screen, visual, depth, &colormap); if (status == True) PEX_colormap = colormap; blue = AllocNamedColor(display, PEX_colormap, "Blue", 0L); white = AllocNamedColor(display, PEX_colormap, "White", 0L); n = 0; XtSetArg(args[n],XmNvisual, visual); n++; XtSetArg(args[n],XmNdepth, depth); n++; XtSetArg(args[n],XmNcolormap, colormap); n++; XtSetArg(args[n],XmNallowResize, True); n++; XtSetArg(args[n],XmNmapWhenManaged, False); n++; XtSetArg(args[n],XmNbackground, blue); n++; XtSetArg(args[n],XmNforeground, white); n++; XtSetArg(args[n],XmNargc, cp_argc); n++; XtSetArg(args[n],XmNargv, cp_argv); n++; XtSetArg(args[n],XmNheight, 400); n++; XtSetArg(args[n],XmNwidth, 400); n++; toplevel = XtAppCreateShell(NULL, "peX", applicationShellWidgetClass, display, args, n); mainWindow = XmCreateMainWindow(toplevel, "mainWin", NULL, 0); drawMe = XmCreateDrawingArea(mainWindow, "pexdraw", args, n);
The application requires the toplevel shell. For this shell, you have to manually set its visual, screen, display, and colormap. Note that the resize resource is set to True and mapWhenManaged is set to False. You have to set these values via the XtArgs args array at creation time for this to work. The toplevel shell is where you create the main window to place your Motif widgets. After you have created the main window with the XmCreateMainWindow function call, you create the drawing area called drawMe on top of this window.
Next, you add callbacks to the drawing area widget to allow for redrawing. Add the callbacks to the drawing area for the following types of events: resize, expose, and input.
The complete code for a very simple application is shown in Listing 60.2.
#include <Xm/Xm.h> #include <Xm/DrawingA.h> #include <Xm/MainW.h> #include <Xm/RowColumn.h> #include <X11/PEX5/PEXlib.h> #include <stdio.h> int bailout(char *str) { printf ("\n %s", str); exit(1); } int pex_set_line_color(Display *dpy, PEXRenderer p, float r, float g, float b) { PEXColor pc; pc.rgb.red = r; pc.rgb.green = g; pc.rgb.blue = b; PEXSetLineColor(dpy, p, PEXOCRender, PEXColorTypeRGB, &pc); } void doSamplePEX( Display *dpy, Window win, PEXRenderer ren) { PEXCoord coords[10]; PEXBeginRendering(dpy, win, ren); PEXSetLineWidth(dpy, ren, PEXOCRender, 8.0); pex_set_line_color(dpy, ren, 0.5, 0.5, 1.0); coords[0].x = 0.3; coords[0].y = 0.3; coords[0].z = 0.0; coords[1].x = 0.3; coords[1].y = 0.6; coords[1].z = 0.0; coords[2].x = 0.6; coords[2].y = 0.6; coords[2].z = 0.0; coords[3].x = 0.6; coords[3].y = 0.3; coords[3].z = 0.0; PEXPolyline(dpy, ren, PEXOCRender, 4, coords); pex_set_line_color(dpy, ren, 1.5, 0.5, 1.5); coords[0].x = 0.3; coords[0].y = 0.3; coords[0].z = 0.5; coords[1].x = 0.3; coords[1].y = 0.6; coords[1].z = 0.5; coords[2].x = 0.6; coords[2].y = 0.6; coords[2].z = 0.5; coords[3].x = 0.6; coords[3].y = 0.3; coords[3].z = 0.5; PEXPolyline(dpy, ren, PEXOCRender, 4, coords); PEXEndRendering(dpy, win, ren); XFlush(dpy); /* important */ } void quitBtn( Widget w, void *p, void *pp) { exit(0); } void drawBtn( Widget w, XmDrawingAreaCallbackStruct *sp, XtPointer *client_data) { Dimension wd, ht; PEXRenderer *rp; if (sp == NULL) return; switch(sp->reason) { case XmCR_EXPOSE: if (sp->event->xexpose.count == 0) { rp = (PEXRenderer *)client_data; doSamplePEX(XtDisplay(w), XtWindow(w), *rp); } break; case XmCR_INPUT: break; } } int pexInit(Display *dpy, PEXExtensionInfo **pexparms) { int err; char errorMsg[PEXErrorStringLength+1]; PEXExtensionInfo *pex_info; err = PEXInitialize(dpy, pexparms, PEXErrorStringLength, errorMsg); if (err) return False; pex_info = (PEXExtensionInfo *)(*pexparms); if( (pex_info->subset_info & PEXImmediateMode) || ((pex_info->subset_info & 0xffff) == PEXCompleteImplementation)) { return True; } return False; } int main(int argc, char *argv[]) { Widget parent; XtAppContext app_context; int cp_size; int cp_argc; int cp_argv; int status; int screen; int depth; int fore; int bkg; int n; Arg wars[20]; Visual *visual; Colormap colormap; XStandardColormap std_cmp; PEXRendererAttributes pex_attr; Display *dpy; PEXExtensionInfo *pexParms; PEXRenderer ren; Widget mainw; Widget filemenu; Widget menubar; Widget exitBtn; Widget drawme; XtToolkitInitialize(); app_context = XtCreateApplicationContext(); cp_argc = argc; cp_size = argc * (sizeof(char *)); cp_argv = (char **)XtMalloc(cp_size); memcpy(cp_argv, argv, cp_size); dpy = XtOpenDisplay(app_context, NULL, NULL, "pexSample", NULL, 0, &argc, argv); if (dpy == (Display *) NULL) bailout("Cannot open display"); status = pexInit(dpy,&pexParms); if (status == False) bailout("Cannot use PEX"); screen = DefaultScreen(dpy); visual = DefaultVisual(dpy, screen); depth = DefaultDepth(dpy,screen); status = GetStdColormap(dpy, screen, visual, depth, &std_cmp); colormap = std_cmp.colormap; bkg = BlackPixel(dpy,screen); fore = WhitePixel(dpy,screen); n = 0; XtSetArg(wars[n], XmNvisual, visual); n++; XtSetArg(wars[n], XmNdepth, depth); n++; XtSetArg(wars[n], XmNcolormap, colormap); n++; XtSetArg(wars[n], XmNbackground, bkg); n++; XtSetArg(wars[n], XmNborderColor, fore); n++; XtSetArg(wars[n], XmNargc, cp_argc); n++; XtSetArg(wars[n], XmNargv,cp_argv); n++; XtSetArg(wars[n], XmNallowResize, True); n++; XtSetArg(wars[n], XmNmappedWhenManaged, False); n++; XtSetArg(wars[n], XmNwidth, 300); n++; XtSetArg(wars[n], XmNheight, 300); n++; parent = XtAppCreateShell(NULL,"pexSample", applicationShellWidgetClass, dpy, wars, n); n = 0; mainw = XmCreateMainWindow(parent, "mainwindow", wars, n); n = 0; XtSetArg(wars[n], XmNresizePolicy, XmRESIZE_ANY); n++; XtSetArg(wars[n], XmNbackground, bkg); n++; XtSetArg(wars[n], XmNborderColor, fore); n++; drawme = XmCreateDrawingArea(mainw, "da", wars, n); XtAddCallback(drawme, XmNexposeCallback, (XtCallbackProc)drawBtn, (XtPointer) &ren); XtAddCallback(drawme, XmNinputCallback, (XtCallbackProc)drawBtn, (XtPointer) &ren); XtAddCallback(drawme, XmNresizeCallback, (XtCallbackProc)drawBtn, (XtPointer) &ren); XtManageChild(drawme); XtManageChild(mainw); XtRealizeWidget(parent); pex_set_color_approx(dpy,XtWindow(drawme), &std_cmp, &pex_attr); XtMapWidget(parent); ren = PEXCreateRenderer(XtDisplay(mainw), XtWindow(drawme), PEXRAColorApproxTable, &pex_attr); if (ren == 0) { printf("\n Bad renderer \n"); exit (1); } XtAppMainLoop(app_context); return(0); }
A few points to note about Listing 60.2 are that the immediate mode was used for rendering on the screen. PEX-SI provides no support for double buffering. This is a serious bug because lack of double buffering hinders performance.
Even worse, the PEX-SI API assumes that the client desires an XClearArea on the window before each frame is drawn. This causes unnecessary flickering while the screen is cleared and redrawn on all primitive drawing calls. What should have been done was to provide an end-of-render procedure hook, with the default hook installed to do a clear area function call.
Individual vendors (because of market pressure) have provided their own solutions to the double buffering problem. (Most PHIGS workstations do double buffering. If you do immediate mode you get single buffering along with the PEX-SI's XClearArea call.)
A final word about adding too many features in an X server. The more you add to the X server, the more memory it chews up in your system. Unless you absolutely require PEX (or other) support, do not add it to your X server, especially if RAM is 8MB or less. The PEX support for Linux added about 420KB on my system, which is not a lot, but it does start adding up. Also, the overhead of PEX applications tend to make my 8MB, 486/33 somewhat slow when running PEX demos, which leads me to believe that PEX on Linux with less than 16MB is not worth the hassle. If you are serious about PEX on Linux, get a faster machine and put gobs of memory on it. The performance improved very dramatically on a 486/66 with 32MB of RAM.
If you would like more information and examples of source code for PEX, check out these FTP sites:
This chapter has been a whirlwind tour of PEX. The topic of PEX could be a book in itself. In fact, there are several texts available that go into excruciating detail about PEX. You should have learned the following information from this chapter: