/* pfMotif Wade Olsen Silicon Graphics Computer Systems 1993 The purpose of this program to provide an example of how to use IRIS Performer in a Motif application. Using the multiple process model available in Performer can greatly improve performance of 3D applications. Unfortunately, this same processing model makes life more difficult for someone who wants to have a nice user interface to their application. Why is that, you might ask? User interface structure and event handling is almost always best handled in the application process, not in the draw process. But the draw process must have exclusive ownership of its own GL context and typically does so by opening its own window. Structuring the application this way usually means the draw process gets the user interface events and must some how send then to the draw process. Another solution is for the application process to open an invisible (input only) window over the draw processes window to capture events. The approach produces the headache of managing what happens when the user interface is moved, resized, or iconified. These issues are best left to window managers and toolkits. Another solution to this problem is outlined in the example below. This program follows these steps: 1. Read a Multigen flight file specified on the command line. 2. Build the user interface, including a GLX widget, and "realize" the UI. 3. Enter the Xt event loop. 4. When the GLX widget is "realize" a GINIT callback is produced. In the callback routine (see redraw() below) the application removes its exclusive hold on the GLX widget. Then, Performer is told to initialize the draw process. 5. In the draw process, Performer calls the openGLXconnection() callback to "create" the GL window to draw in. Instead, the callback just links to the existing GLX widget created by the application process. This nice thing about this approach is the draw processes window is still a child of the widgets in the application process. Thus, event handling and window management is greatly simplified. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pfsgi.h" static XtAppContext app_context;// An Xt thingy // This structure is where application // resources will be read from. typedef struct { int mp_mode; // Performer multiprocessing mode. } AppData, *AppDataPtr; // These are the resources the // application is interested in. static XtResource resources[] = { { "mp_mode", "Mp_mode", XtRInt, sizeof(int), XtOffset(AppDataPtr, mp_mode), XtRImmediate, (caddr_t)-1 }, }; // The command line options. static XrmOptionDescRec options[] = { { "-mp", "*mp_mode", XrmoptionSepArg, NULL }, }; static float diameter; // Diameter of the model we're looking at. static float distance; // Distance from the model to the eye. static pfVec3 view_center; // The point the viewer looks at. static float view_azim; // Viewer rotations. static float view_incl; // static pfPipe * thePipe; // The performer pipe object static pfChannel * theChannel; // The performer channel in the pipe. static int mouse_x_pos; // Current mouse position. static int mouse_y_pos; static int * win_x_size; // Current GLX window size (Stored in static int * win_y_size; // shared memory for draw process to read). // This structure, placed in shared // memory, is read by the draw process // to attach to the GLX widget. static struct glx_info_struct { char * display_name; Window xWindow; } * glx_info; // Also placed in shared memory, this // structure is used to enable/disable // drawing modes. enum { MODE_WIREFRAME, MODE_TEXTURE, NUM_MODES }; static struct draw_mode_struct { int modes[NUM_MODES]; int read; // Counters to indicate changes. int write; } * draw_modes; // This list describes the GLX widget // to be created. GLX_NOCONFIG means // "give me the biggest." static GLXconfig regularGlxConfig [] = { GLX_NORMAL, GLX_BUFSIZE, GLX_NOCONFIG, GLX_NORMAL, GLX_ZSIZE, GLX_NOCONFIG, GLX_NORMAL, GLX_DOUBLE, TRUE, GLX_NORMAL, GLX_RGB, TRUE, GLX_NORMAL, GLX_WINDOW, GLX_NONE, 0, 0, 0, }; // An Xt callback function called when a "draw mode" button is // toggled. static void modeCB(Widget, int mode, XmToggleButtonCallbackStruct * cb) { draw_modes->modes[mode] = cb->set; draw_modes->write++; } // This function is called from the Performer draw process to connect // to the GLX widget created in the application process. It gets the // window id out of a structure (glx_info) in sharred memory. static void openGLXconnection() { Display * display = XOpenDisplay(glx_info->display_name); Window glx_window = glx_info->xWindow; XWindowAttributes attributes; XGetWindowAttributes(display, glx_window, & attributes); int screenNo = XScreenNumberOfScreen(attributes.screen); // Use the same configuration here // that was used in creating the // widget. GLXconfig * config; config = GLXgetconfig(display, screenNo, regularGlxConfig); if (config == 0) { fprintf(stderr, "No visual found to match request in GLXgetconfig\n"); exit(1); } // Find the window entry and set it to // have the same window id of the // GLXwidget's window. for (int i = 0; config[i].buffer; i++) if (config[i].buffer == GLX_NORMAL && config[i].mode == GLX_WINDOW) { config[i].arg = (int)glx_window; } // Connect the GL context to the // GLXwidget created by the // application process. if (GLXlink(display, config)) { fprintf(stderr, "Error in GLXlink\n"); exit(1); } // Make it the current window. GLXwinset(display, glx_window); zbuffer(TRUE); } // This function is called by Performer each frame to draw the scene. static void drawChannelCallback(pfChannel * chan, void *) { static int init = 1; if (init) { init = 0; pfApplyLModel(pfNewLModel(0)); pfPushIdentMatrix(); pfLightOn(pfNewLight(0)); // Make a simple light that moves // with the viewer. pfPopMatrix(); } pfClearChan(chan); int w = draw_modes->write; // See if draw modes have been changed. if (w != draw_modes->read) { if (draw_modes->modes[MODE_WIREFRAME]) pfEnable(PFEN_WIREFRAME); else pfDisable(PFEN_WIREFRAME); if (draw_modes->modes[MODE_TEXTURE]) pfEnable(PFEN_TEXTURE); else pfDisable(PFEN_TEXTURE); draw_modes->read = w; } pfDraw(); } // This Xt callback is called whenever Xt has no other events to // process. It just tells performer to draw another frame. static int draw_workproc() { pfSync(); pfFrame(); return FALSE; // don't remove this callback } // This callback fuction is called if something happens to the // GLXwidget, like an expose or resize event. The first time it is // called with a GINIT event. Performer is told to open a pipe in the // draw process by using the openGLXconnection callback. Info needed // to make the GLX link is placed in shared memory in the glx_info // structure. static void redrawCB(Widget w, void *, GlxDrawCallbackStruct * cb) { // Remember the size of the window. if (cb->reason == GlxCR_RESIZE || cb->reason == GlxCR_GINIT) { *win_x_size = cb->width; *win_y_size = cb->height; } // This happens once after the widget // is first realized. if (cb->reason == GlxCR_GINIT) { Display * display = XtDisplay(w); Window xWindow = XtWindow(w); // Place info in shared memory so draw // process can attached to GLXwidget. glx_info->display_name = 0; glx_info->xWindow = xWindow; // Release exclusive hold on GLXwidget. GLXunlink(display, xWindow); // Performer will now call // openGLXconnection in the draw // process. pfInitPipe(thePipe, (void (*)(pfPipe*))openGLXconnection); } } // This Xt callback is called when the application shell widget is // stowed or unstowed. When unstowed or opened for the first time, // a work process callback is set. This work process is called // whenever Xt is not processing other events. When the shell widget // is stowed, the work process is unset so the application does not // chew up CPU time while iconified. static void mapCB(Widget, void *, XEvent * event) { static XtWorkProcId work_id; static pid_t draw_pid; // draw process ID. // When the window is open displayed, // Xt calls draw_workproc() // when no events are pending. if (event->type == MapNotify) { work_id = XtAppAddWorkProc(app_context, (XtWorkProc)draw_workproc, 0); // Resume the stopped draw process // (see below). if (draw_pid > 0) kill(draw_pid, SIGCONT); // Get the draw processes ID. If // there is no draw process, don't // worry about suspending it. else if (draw_pid == 0) { long mode = pfGetMultiprocess(); if (mode & PFMP_FORK_DRAW) draw_pid = pfGetPID(0, PFPROC_DRAW); else draw_pid = -1; } } // Nothing is done when the // application is iconified. if (event->type == UnmapNotify) { XtRemoveWorkProc(work_id); // If there is a separate draw // process, it must be temporarily // stopped, otherwise it busy-waits. if (draw_pid > 0) kill(draw_pid, SIGSTOP); } } // This Xt callback is called when mouse buttons are pressed in the // GLXwidget. static void buttonCB(Widget, void *, XEvent * event) { mouse_x_pos = event->xbutton.x; mouse_y_pos = event->xbutton.y; } // This function recomputes the channels viewing parameters. static void adjust_view() { pfVec3 view_pos, view_dir; float azim_sin, azim_cos; float incl_sin, incl_cos; pfSinCos(view_azim, &azim_sin, &azim_cos); pfSinCos(view_incl, &incl_sin, &incl_cos); view_pos[0] = view_center[0] + distance * azim_sin * incl_cos; view_pos[1] = view_center[1] + distance * (-azim_cos) * incl_cos; view_pos[2] = view_center[2] + distance * (-incl_sin); view_dir[0] = view_azim; view_dir[1] = view_incl; view_dir[2] = 0; pfChanView(theChannel, view_pos, view_dir); } // This Xt callback is called when mouse buttons are pressed and the // mouse is dragged in the GLXwidget. It recomputes the viewing // direction and distance from the object. static void motionCB(Widget, void *, XEvent * event) { int new_x = event->xmotion.x; int new_y = event->xmotion.y; float dx = (float)(new_x - mouse_x_pos); float dy = (float)(new_y - mouse_y_pos); mouse_x_pos = new_x; mouse_y_pos = new_y; // Move the viewer nearer or farther // away. if (Button1Mask & event->xmotion.state) { distance += -(fabs(distance) + 0.001) * (dy / 100.0); } // Rotate the viewer around the // object. if (Button2Mask & event->xmotion.state) { view_azim -= dx / 2.0; view_incl -= dy / 2.0; } // Translate the viewer. if (Button3Mask & event->xmotion.state) { float fov_x, fov_y; pfGetChanFOV(theChannel, &fov_x, &fov_y); float azim_sin, azim_cos; float incl_sin, incl_cos; pfSinCos(view_azim, &azim_sin, &azim_cos); pfSinCos(view_incl, &incl_sin, &incl_cos); float x_fudge, y_fudge, junk; pfSinCos(fov_x, &x_fudge, &junk); pfSinCos(fov_y, &y_fudge, &junk); x_fudge /= *win_x_size; y_fudge /= *win_y_size; view_center[0] -= (azim_cos * dx * x_fudge + azim_sin * (-incl_sin) * dy * y_fudge) * distance; view_center[1] -= (azim_sin * dx * x_fudge + azim_cos * incl_sin * dy * y_fudge) * distance; view_center[2] -= (-incl_cos) * dy * distance * y_fudge; } adjust_view(); } void main(int argc, char ** argv) { AppData appData; Arg args[20]; int n = 0; // Use schemes to avoid the Motif // baby-blue look. static String app_defaults[] = { "*useSchemes: all", NULL, }; Widget toplevel; toplevel = XtVaAppInitialize(& app_context, "PfMotif", options, XtNumber(options), &argc, argv, app_defaults, NULL); XtAddEventHandler(toplevel, StructureNotifyMask, FALSE, (XtEventHandler)mapCB, 0); XtGetApplicationResources(toplevel, &appData, resources, XtNumber(resources), NULL, 0); if (argc != 2) { fprintf(stderr, "Usage: %s [-mp ] geometry_file\n", argv[0]); exit(1); } pfInit(); pfNotifyLevel(PFNFY_WARN); void * arena = pfGetSharedArena(); // Place window size in shared memory // so the draw process has access to // it. win_x_size = (int *)pfMalloc(sizeof(int), arena); win_y_size = (int *)pfMalloc(sizeof(int), arena); // Communicate these draw modes through // shared memory as well. glx_info = (glx_info_struct *)pfMalloc(sizeof(glx_info_struct), arena); draw_modes = (draw_mode_struct *)pfMalloc(sizeof(draw_mode_struct), arena); draw_modes->modes[MODE_TEXTURE] = 1; draw_modes->modes[MODE_WIREFRAME] = 0; draw_modes->read = 0; draw_modes->write = 0; // Performer forks draw and cull // processes here (depending on // mp_mode). if (appData.mp_mode >= 0) pfMultiprocess(appData.mp_mode); pfConfig(); // Load some model to spin around. char * path = dirname(strdup(argv[1])); pfFilePath(path); pfNode * data = LoadFile(argv[1], NULL); if (data == 0) { fprintf(stderr, "%s: %s is not readable\n", argv[0], argv[1]); exit(1); } Widget form = XtCreateManagedWidget("form", xmFormWidgetClass, toplevel, NULL, 0); n = 0; XtSetArg(args[n], XmNorientation, XmVERTICAL); n++; Widget rowcol = XtCreateManagedWidget("commands", xmRowColumnWidgetClass, form, args, n); n = 0; XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; XtSetValues(rowcol, args, n); // Create the GLXwidget for GL // rendering. n = 0; XtSetArg(args[n], GlxNglxConfig, regularGlxConfig); n++; XtSetArg(args[n], XtNwidth, 646); n++; XtSetArg(args[n], XtNheight, 486); n++; XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++; XtSetArg(args[n], XmNleftWidget, rowcol); n++; XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; Widget GLXwidget = XtCreateManagedWidget("draw_area", glxMDrawWidgetClass, form, args, n); XtAddCallback(GLXwidget, GlxNginitCallback, (XtCallbackProc)redrawCB, 0); XtAddCallback(GLXwidget, GlxNexposeCallback, (XtCallbackProc)redrawCB, 0); XtAddCallback(GLXwidget, GlxNresizeCallback, (XtCallbackProc)redrawCB, 0); XtAddEventHandler(GLXwidget, ButtonMotionMask, FALSE, (XtEventHandler)motionCB, 0); XtAddEventHandler(GLXwidget, ButtonPressMask, FALSE, (XtEventHandler)buttonCB, 0); Widget modeWidget; n = 0; XtSetArg(args[n], XmNset, TRUE); n++; modeWidget = XtCreateManagedWidget("Texture", xmToggleButtonWidgetClass, rowcol, args, n); XtAddCallback(modeWidget, XmNvalueChangedCallback, (XtCallbackProc)modeCB, (XtPointer)MODE_TEXTURE); modeWidget = XtCreateManagedWidget("Wireframe", xmToggleButtonWidgetClass, rowcol, NULL, 0); XtAddCallback(modeWidget, XmNvalueChangedCallback, (XtCallbackProc)modeCB, (XtPointer)MODE_WIREFRAME); XtRealizeWidget(toplevel); thePipe = pfGetPipe(0); theChannel = pfNewChan(thePipe); pfScene * scene = pfNewScene(); pfChanScene(theChannel, scene); pfChanDrawFunc(theChannel, drawChannelCallback); pfSphere bound; pfGetNodeBSphere(data, & bound); diameter = 2 * bound.radius; pfCopyVec3(view_center, bound.center); distance = diameter / (2 * fsin(45.0 / 2 * M_PI / 180)); pfChanNearFar(theChannel, diameter / 10.0, diameter * 100); pfAddChild(scene, data); adjust_view(); XtAppMainLoop(app_context); } // Performer calls these GL routines every frame. They are very slow // because they require a round trip to the X server. These are much // faster replacements. extern "C" void getorigin(long * x, long * y) { *x = 0; *y = 0; } extern "C" void getsize(long * x, long * y) { *x = *win_x_size; *y = *win_y_size; }