/* ** A simple OpenGL stereo application. Uses the GLw Motif DrawingArea ** widget with a popup menu. Includes a simple API for controlling ** stereo. ** ** cc -o glwstereo glwstereo.c -lGLw -lXm -lXt -lGL -lXext -lX11 -lm */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /********************************************************************/ #include static struct stereoStateRec { Bool useSGIStereo; Display *currentDisplay; Window currentWindow; GLXContext currentContext; GLenum currentDrawBuffer; int currentStereoBuffer; Bool enabled; char *stereoCommand; char *restoreCommand; } stereo; /* call instead of glDrawBuffer */ void stereoDrawBuffer(GLenum mode) { if (stereo.useSGIStereo) { stereo.currentDrawBuffer = mode; switch (mode) { case GL_FRONT: case GL_BACK: case GL_FRONT_AND_BACK: /* ** Simultaneous drawing to both left and right ** buffers isn't really possible if we don't have a ** stereo capable visual. For now just fall through and use the left buffer. */ case GL_LEFT: case GL_FRONT_LEFT: case GL_BACK_LEFT: stereo.currentStereoBuffer = STEREO_BUFFER_LEFT; break; case GL_RIGHT: case GL_FRONT_RIGHT: stereo.currentStereoBuffer = STEREO_BUFFER_RIGHT; mode = GL_FRONT; break; case GL_BACK_RIGHT: stereo.currentStereoBuffer = STEREO_BUFFER_RIGHT; mode = GL_BACK; break; default: break; } if (stereo.currentDisplay && stereo.currentWindow) { glXWaitGL(); /* sync with GL command stream before calling X */ XSGISetStereoBuffer(stereo.currentDisplay, stereo.currentWindow, stereo.currentStereoBuffer); glXWaitX(); /* sync with X command stream before calling GL */ } } glDrawBuffer(mode); } /* call instead of glClear */ void stereoClear(GLbitfield mask) { if (stereo.useSGIStereo) { GLenum drawBuffer = stereo.currentDrawBuffer; switch (drawBuffer) { case GL_FRONT: stereoDrawBuffer(GL_FRONT_RIGHT); glClear(mask); stereoDrawBuffer(drawBuffer); break; case GL_BACK: stereoDrawBuffer(GL_BACK_RIGHT); glClear(mask); stereoDrawBuffer(drawBuffer); break; case GL_FRONT_AND_BACK: stereoDrawBuffer(GL_RIGHT); glClear(mask); stereoDrawBuffer(drawBuffer); break; case GL_LEFT: case GL_FRONT_LEFT: case GL_BACK_LEFT: case GL_RIGHT: case GL_FRONT_RIGHT: case GL_BACK_RIGHT: default: break; } } glClear(mask); } /* call after glXMakeCurrent */ void stereoMakeCurrent(Display *dpy, Window win, GLXContext ctx) { if (stereo.useSGIStereo) { if (dpy && (dpy != stereo.currentDisplay)) { int event, error; /* Make sure new Display supports SGIStereo */ if (XSGIStereoQueryExtension(dpy, &event, &error) == False) { dpy = NULL; } } if (dpy && win && (win != stereo.currentWindow)) { /* Make sure new Window supports SGIStereo */ if (XSGIQueryStereoMode(dpy, win) == X_STEREO_UNSUPPORTED) { win = None; } } if (ctx && (ctx != stereo.currentContext)) { GLint drawBuffer; glGetIntegerv(GL_DRAW_BUFFER, &drawBuffer); stereoDrawBuffer((GLenum) drawBuffer); } stereo.currentDisplay = dpy; stereo.currentWindow = win; stereo.currentContext = ctx; } } /* call to turn on stereo viewing */ void stereoEnable(void) { if (!stereo.enabled && stereo.stereoCommand) { system(stereo.stereoCommand); } stereo.enabled = True; } /* call to turn off stereo viewing */ void stereoDisable(void) { if (stereo.enabled && stereo.restoreCommand) { system(stereo.restoreCommand); } stereo.enabled = False; } /* call before using stereo */ void stereoInit(GLboolean usingStereoVisual, char *stereoCmd, char *restoreCmd) { stereo.useSGIStereo = !usingStereoVisual; stereo.currentDisplay = NULL; stereo.currentWindow = None; stereo.currentContext = NULL; stereo.currentDrawBuffer = GL_NONE; stereo.currentStereoBuffer = STEREO_BUFFER_NONE; stereo.enabled = False; if (stereo.stereoCommand) { free(stereo.stereoCommand); } stereo.stereoCommand = stereoCmd ? strdup(stereoCmd) : NULL; if (stereo.restoreCommand) { free(stereo.restoreCommand); } stereo.restoreCommand = restoreCmd ? strdup(restoreCmd) : NULL; } /* call when done using stereo */ void stereoDone(void) { stereoDisable(); stereoInit(True, NULL, NULL); } /************************************************************************/ #define APP_CLASS "GLWApp" String fallbackResources[] = { /* ** Widget resources */ "*sgiMode: True", "*useSchemes: all", "*title: Simple OpenGL Stereo Demo", "*form.width: 300", "*form.height: 300", /* ** Non-Widget (application) resources */ "*stereoCommand: /usr/gfx/setmon -n 640x512_120s", "*restoreCommand: /usr/gfx/setmon -n 72HZ", "*SGIStereoCommand: /usr/gfx/setmon -n STR_BOT", "*SGIRestoreCommand: /usr/gfx/setmon -n 60HZ", NULL, }; struct appResourceRec { _XtString stereoCommand; _XtString restoreCommand; _XtString SGIStereoCommand; _XtString SGIRestoreCommand; } appResources; XtResource appResourceList[] = { { "stereoCommand", XtCString, XtRString, sizeof (_XtString), XtOffsetOf(struct appResourceRec, stereoCommand), XtRImmediate, (XtPointer) NULL }, { "restoreCommand", XtCString, XtRString, sizeof (_XtString), XtOffsetOf(struct appResourceRec, restoreCommand), XtRImmediate, (XtPointer) NULL }, { "SGIStereoCommand", XtCString, XtRString, sizeof (_XtString), XtOffsetOf(struct appResourceRec, SGIStereoCommand), XtRImmediate, (XtPointer) NULL }, { "SGIRestoreCommand", XtCString, XtRString, sizeof (_XtString), XtOffsetOf(struct appResourceRec, SGIRestoreCommand), XtRImmediate, (XtPointer) NULL }, }; XtAppContext appContext; XtWorkProcId appWorkProcId; Widget topLevel, form, glw, popup; GLXContext ctx; GLboolean animationEnabled = GL_FALSE; GLboolean stereoEnabled = GL_FALSE; double lastTime = 0.0; double rotationRate = 360.0 / 8.0; double rotation = 0.0; static void drawCylinder(void) { int numSides = 20; double radius = 0.7; double stepSize = 2.0*M_PI / numSides; int i; glBegin(GL_TRIANGLE_STRIP); for (i=0; i<=numSides; ++i) { double a = i * stepSize; GLfloat x = cos(a); GLfloat y = sin(a); glNormal3f(x, y, 0.0); glVertex3f(x * radius, y * radius, 0.7); glNormal3f(x, y, 0.0); glVertex3f(x * radius, y * radius, -0.7); } glEnd(); } static void initialize(void) { GLfloat lightAmb[4] = { 0.2, 0.2, 0.2, 1.0 }; GLfloat lightPos[4] = { 1.5, 1.5, 1.5, 1.0 }; GLfloat matAmbDiff[4] = { 0.30, 0.40, 0.80, 1.0 }; glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(0, 0, -2); glEnable(GL_DEPTH_TEST); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glLightfv(GL_LIGHT0, GL_AMBIENT, lightAmb); glLightfv(GL_LIGHT0, GL_POSITION, lightPos); glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE); glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, matAmbDiff); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } void stereoFrustum(GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat near, GLfloat far, GLfloat eyeDist, GLfloat eyeOffset) { GLfloat eyeShift = (eyeDist - near) * (eyeOffset / eyeDist); glFrustum(left+eyeShift, right+eyeShift, bottom, top, near, far); glTranslatef(-eyeShift, 0.0, 0.0); } static void redraw(void) { GLfloat eyeDist = 2.0; GLfloat eyeOffset = 0.05; glPushMatrix(); glRotated(rotation, 0, 1, 0); if (stereoEnabled) { /* ** Draw right-eye view. */ glMatrixMode(GL_PROJECTION); glLoadIdentity(); stereoFrustum(-1, 1, -1, 1, 1, 3, eyeDist, eyeOffset); glMatrixMode(GL_MODELVIEW); stereoDrawBuffer(GL_BACK_RIGHT); stereoClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); drawCylinder(); } else { eyeOffset = 0.0; } /* ** Draw left-eye view. */ glMatrixMode(GL_PROJECTION); glLoadIdentity(); stereoFrustum(-1, 1, -1, 1, 1, 3, eyeDist, -eyeOffset); glMatrixMode(GL_MODELVIEW); stereoDrawBuffer(GL_BACK_LEFT); stereoClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); drawCylinder(); glPopMatrix(); GLwDrawingAreaSwapBuffers(glw); } double getTime(void) { struct timeval time; gettimeofday(&time); return ((double) time.tv_sec + (double) time.tv_usec * 1E-6); } static Boolean animateWP(XtPointer clientData) { double currentTime = getTime(); double elapsed = currentTime - lastTime; lastTime = currentTime; rotation += rotationRate * ((elapsed <= 1.0) ? elapsed : 1.0); if (rotation >= 360) rotation -= 360; redraw(); return False; } static void popupCB(Widget w, XtPointer clientData, XtPointer callData) { int button = (int) clientData; Bool needsRedraw = False; switch (button) { case 0: glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); needsRedraw = True; break; case 1: glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); needsRedraw = True; break; case 2: animationEnabled = !animationEnabled; if (animationEnabled) { lastTime = getTime(); appWorkProcId = XtAppAddWorkProc(appContext, animateWP, NULL); } else { XtRemoveWorkProc(appWorkProcId); } break; case 3: stereoEnabled = !stereoEnabled; if (stereoEnabled) { stereoEnable(); } else { stereoDisable(); } needsRedraw = True; break; case 4: stereoDone(); exit(EXIT_SUCCESS); break; default: break; } if (needsRedraw) { redraw(); } } static void popupEH(Widget w, XtPointer clientData, XEvent *event, Boolean *cont) { Widget popup = *((Widget *) clientData); if ((event->type == ButtonPress) && (event->xbutton.button == Button3)) { XmMenuPosition(popup, &event->xbutton); XtManageChild(popup); } } static void exposeCB(Widget w, XtPointer clientData, XtPointer callData) { redraw(); } static void resizeCB(Widget w, XtPointer clientData, XtPointer callData) { GLwDrawingAreaCallbackStruct *glwcallData = (GLwDrawingAreaCallbackStruct *) callData; glViewport(0, 0, glwcallData->width, glwcallData->height); redraw(); } int main(int argc, char **argv) { int stereoAttrs[] = { GLX_STEREO, GLX_RGBA, GLX_DOUBLEBUFFER, GLX_RED_SIZE, 1, GLX_DEPTH_SIZE, 1, None, }; int visualAttrs[] = { GLX_RGBA, GLX_DOUBLEBUFFER, GLX_RED_SIZE, 1, GLX_DEPTH_SIZE, 1, None, }; Display *dpy; int scrn; XVisualInfo *vis; Arg args[10]; int n; topLevel = XtOpenApplication(&appContext, APP_CLASS, NULL, 0, &argc, argv, fallbackResources, applicationShellWidgetClass, NULL, 0); XtGetApplicationResources(topLevel, &appResources, appResourceList, XtNumber(appResourceList), NULL, 0); dpy = XtDisplay(topLevel); scrn = XScreenNumberOfScreen(XtScreen(topLevel)); if ((vis = glXChooseVisual(dpy, scrn, stereoAttrs)) != NULL) { /* initialize for use with a stereo capable visual */ stereoInit(True, appResources.stereoCommand, appResources.restoreCommand); } else if ((vis = glXChooseVisual(dpy, scrn, visualAttrs)) != NULL) { /* initialize for use without a stereo capable visual */ stereoInit(False, appResources.SGIStereoCommand, appResources.SGIRestoreCommand); /* Force the topLevel widget to maintain a 2:1 aspect ratio */ n = 0; XtSetArg(args[n], XmNminAspectX, 2); n++; XtSetArg(args[n], XmNminAspectY, 1); n++; XtSetArg(args[n], XmNmaxAspectX, 2); n++; XtSetArg(args[n], XmNmaxAspectY, 1); n++; XtSetValues(topLevel, args, n); } else { fprintf(stderr, "can't find appropriate visual\n"); exit(EXIT_FAILURE); } if ((ctx = glXCreateContext(dpy, vis, 0, True)) == NULL) { fprintf(stderr, "can't create rendering context\n"); exit(EXIT_FAILURE); } form = XtCreateManagedWidget("form", xmFormWidgetClass, topLevel, NULL, 0); n = 0; XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; XtSetArg(args[n], GLwNvisualInfo, vis); n++; glw = XtCreateManagedWidget("glw", glwMDrawingAreaWidgetClass, form, args, n); XtAddCallback(glw, GLwNexposeCallback, exposeCB, 0); XtAddCallback(glw, GLwNresizeCallback, resizeCB, 0); { XmButtonType buttonTypes[] = { XmPUSHBUTTON, XmPUSHBUTTON, XmSEPARATOR, XmPUSHBUTTON, XmSEPARATOR, XmPUSHBUTTON, XmSEPARATOR, XmPUSHBUTTON, }; XmString buttonLabels[XtNumber(buttonTypes)]; buttonLabels[0] = XmStringCreateLocalized("draw filled"); buttonLabels[1] = XmStringCreateLocalized("draw lines"); buttonLabels[2] = NULL; buttonLabels[3] = XmStringCreateLocalized("toggle animation"); buttonLabels[4] = NULL; buttonLabels[5] = XmStringCreateLocalized("toggle stereo mode"); buttonLabels[6] = NULL; buttonLabels[7] = XmStringCreateLocalized("quit"); n = 0; XtSetArg(args[n], XmNbuttonCount, XtNumber(buttonTypes)); n++; XtSetArg(args[n], XmNbuttonType, buttonTypes); n++; XtSetArg(args[n], XmNbuttons, buttonLabels); n++; XtSetArg(args[n], XmNsimpleCallback, popupCB); n++; popup = XmCreateSimplePopupMenu(form, "popup", args, n); XtAddEventHandler(form, ButtonPressMask, False, popupEH, &popup); XmStringFree(buttonLabels[0]); XmStringFree(buttonLabels[1]); XmStringFree(buttonLabels[3]); XmStringFree(buttonLabels[5]); XmStringFree(buttonLabels[7]); } XtRealizeWidget(topLevel); GLwDrawingAreaMakeCurrent(glw, ctx); stereoMakeCurrent(dpy, XtWindow(glw), ctx); initialize(); XtAppMainLoop(appContext); }