glxgears: Support GLX_EXT_swap_control for querying the current swap interval.
[mesa.git] / progs / xdemos / glxgears.c
index a82a0b9bc711bfac984acc28929c473bd0cba0d5..92c75caa5e63d1de160a6b2f32d2814c89eea48d 100644 (file)
@@ -23,9 +23,7 @@
  * This is a port of the infamous "gears" demo to straight GLX (i.e. no GLUT)
  * Port by Brian Paul  23 March 2001
  *
- * Command line options:
- *    -info      print GL implementation information
- *
+ * See usage() below for command line options.
  */
 
 
 #include <X11/keysym.h>
 #include <GL/gl.h>
 #include <GL/glx.h>
+#include <GL/glxext.h>
+
+#ifndef GLX_MESA_swap_control
+#define GLX_MESA_swap_control 1
+typedef int (*PFNGLXGETSWAPINTERVALMESAPROC)(void);
+#endif
 
 
 #define BENCHMARK
@@ -49,7 +53,7 @@
 #include <unistd.h>
 
 /* return current time (in seconds) */
-static int
+static double
 current_time(void)
 {
    struct timeval tv;
@@ -59,16 +63,23 @@ current_time(void)
    struct timezone tz;
    (void) gettimeofday(&tv, &tz);
 #endif
-   return (int) tv.tv_sec;
+   return (double) tv.tv_sec + tv.tv_usec / 1000000.0;
 }
 
 #else /*BENCHMARK*/
 
 /* dummy */
-static int
+static double
 current_time(void)
 {
-   return 0;
+   /* update this function for other platforms! */
+   static double t = 0.0;
+   static int warn = 1;
+   if (warn) {
+      fprintf(stderr, "Warning: current_time() not implemented!!\n");
+      warn = 0;
+   }
+   return t += 1.0;
 }
 
 #endif /*BENCHMARK*/
@@ -80,10 +91,22 @@ current_time(void)
 #endif
 
 
+/** Event handler results: */
+#define NOP 0
+#define EXIT 1
+#define DRAW 2
+
 static GLfloat view_rotx = 20.0, view_roty = 30.0, view_rotz = 0.0;
 static GLint gear1, gear2, gear3;
 static GLfloat angle = 0.0;
 
+static GLboolean fullscreen = GL_FALSE;        /* Create a single fullscreen window */
+static GLboolean stereo = GL_FALSE;    /* Enable stereo.  */
+static GLboolean animate = GL_TRUE;    /* Animation */
+static GLfloat eyesep = 5.0;           /* Eye separation. */
+static GLfloat fix_point = 40.0;       /* Fixation point distance.  */
+static GLfloat left, right, asp;       /* Stereo frustum params.  */
+
 
 /*
  *
@@ -256,20 +279,110 @@ draw(void)
 }
 
 
+static void
+draw_gears(void)
+{
+   if (stereo) {
+      /* First left eye.  */
+      glDrawBuffer(GL_BACK_LEFT);
+
+      glMatrixMode(GL_PROJECTION);
+      glLoadIdentity();
+      glFrustum(left, right, -asp, asp, 5.0, 60.0);
+
+      glMatrixMode(GL_MODELVIEW);
+
+      glPushMatrix();
+      glTranslated(+0.5 * eyesep, 0.0, 0.0);
+      draw();
+      glPopMatrix();
+
+      /* Then right eye.  */
+      glDrawBuffer(GL_BACK_RIGHT);
+
+      glMatrixMode(GL_PROJECTION);
+      glLoadIdentity();
+      glFrustum(-right, -left, -asp, asp, 5.0, 60.0);
+
+      glMatrixMode(GL_MODELVIEW);
+
+      glPushMatrix();
+      glTranslated(-0.5 * eyesep, 0.0, 0.0);
+      draw();
+      glPopMatrix();
+   }
+   else {
+      draw();
+   }
+}
+
+
+/** Draw single frame, do SwapBuffers, compute FPS */
+static void
+draw_frame(Display *dpy, Window win)
+{
+   static int frames = 0;
+   static double tRot0 = -1.0, tRate0 = -1.0;
+   double dt, t = current_time();
+
+   if (tRot0 < 0.0)
+      tRot0 = t;
+   dt = t - tRot0;
+   tRot0 = t;
+
+   if (animate) {
+      /* advance rotation for next frame */
+      angle += 70.0 * dt;  /* 70 degrees per second */
+      if (angle > 3600.0)
+         angle -= 3600.0;
+   }
+
+   draw_gears();
+   glXSwapBuffers(dpy, win);
+
+   frames++;
+   
+   if (tRate0 < 0.0)
+      tRate0 = t;
+   if (t - tRate0 >= 5.0) {
+      GLfloat seconds = t - tRate0;
+      GLfloat fps = frames / seconds;
+      printf("%d frames in %3.1f seconds = %6.3f FPS\n", frames, seconds,
+             fps);
+      tRate0 = t;
+      frames = 0;
+   }
+}
+
+
 /* new window size or exposure */
 static void
 reshape(int width, int height)
 {
-   GLfloat h = (GLfloat) height / (GLfloat) width;
-
    glViewport(0, 0, (GLint) width, (GLint) height);
-   glMatrixMode(GL_PROJECTION);
-   glLoadIdentity();
-   glFrustum(-1.0, 1.0, -h, h, 5.0, 60.0);
+
+   if (stereo) {
+      GLfloat w;
+
+      asp = (GLfloat) height / (GLfloat) width;
+      w = fix_point * (1.0 / 5.0);
+
+      left = -5.0 * ((w - 0.5 * eyesep) / fix_point);
+      right = 5.0 * ((w + 0.5 * eyesep) / fix_point);
+   }
+   else {
+      GLfloat h = (GLfloat) height / (GLfloat) width;
+
+      glMatrixMode(GL_PROJECTION);
+      glLoadIdentity();
+      glFrustum(-1.0, 1.0, -h, h, 5.0, 60.0);
+   }
+   
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0, 0.0, -40.0);
 }
+   
 
 
 static void
@@ -309,6 +422,52 @@ init(void)
 }
 
 
+/**
+ * Remove window border/decorations.
+ */
+static void
+no_border( Display *dpy, Window w)
+{
+   static const unsigned MWM_HINTS_DECORATIONS = (1 << 1);
+   static const int PROP_MOTIF_WM_HINTS_ELEMENTS = 5;
+
+   typedef struct
+   {
+      unsigned long       flags;
+      unsigned long       functions;
+      unsigned long       decorations;
+      long                inputMode;
+      unsigned long       status;
+   } PropMotifWmHints;
+
+   PropMotifWmHints motif_hints;
+   Atom prop, proptype;
+   unsigned long flags = 0;
+
+   /* setup the property */
+   motif_hints.flags = MWM_HINTS_DECORATIONS;
+   motif_hints.decorations = flags;
+
+   /* get the atom for the property */
+   prop = XInternAtom( dpy, "_MOTIF_WM_HINTS", True );
+   if (!prop) {
+      /* something went wrong! */
+      return;
+   }
+
+   /* not sure this is correct, seems to work, XA_WM_HINTS didn't work */
+   proptype = prop;
+
+   XChangeProperty( dpy, w,                         /* display, window */
+                    prop, proptype,                 /* property, type */
+                    32,                             /* format: 32-bit datums */
+                    PropModeReplace,                /* mode */
+                    (unsigned char *) &motif_hints, /* data */
+                    PROP_MOTIF_WM_HINTS_ELEMENTS    /* nelements */
+                  );
+}
+
+
 /*
  * Create an RGB, double-buffered window.
  * Return the window and context handles.
@@ -318,13 +477,21 @@ make_window( Display *dpy, const char *name,
              int x, int y, int width, int height,
              Window *winRet, GLXContext *ctxRet)
 {
-   int attrib[] = { GLX_RGBA,
-                   GLX_RED_SIZE, 1,
-                   GLX_GREEN_SIZE, 1,
-                   GLX_BLUE_SIZE, 1,
-                   GLX_DOUBLEBUFFER,
-                   GLX_DEPTH_SIZE, 1,
-                   None };
+   int attribs[] = { GLX_RGBA,
+                     GLX_RED_SIZE, 1,
+                     GLX_GREEN_SIZE, 1,
+                     GLX_BLUE_SIZE, 1,
+                     GLX_DOUBLEBUFFER,
+                     GLX_DEPTH_SIZE, 1,
+                     None };
+   int stereoAttribs[] = { GLX_RGBA,
+                           GLX_RED_SIZE, 1,
+                           GLX_GREEN_SIZE, 1,
+                           GLX_BLUE_SIZE, 1,
+                           GLX_DOUBLEBUFFER,
+                           GLX_DEPTH_SIZE, 1,
+                           GLX_STEREO,
+                           None };
    int scrnum;
    XSetWindowAttributes attr;
    unsigned long mask;
@@ -336,9 +503,22 @@ make_window( Display *dpy, const char *name,
    scrnum = DefaultScreen( dpy );
    root = RootWindow( dpy, scrnum );
 
-   visinfo = glXChooseVisual( dpy, scrnum, attrib );
+   if (fullscreen) {
+      x = 0; y = 0;
+      width = DisplayWidth( dpy, scrnum );
+      height = DisplayHeight( dpy, scrnum );
+   }
+
+   if (stereo)
+      visinfo = glXChooseVisual( dpy, scrnum, stereoAttribs );
+   else
+      visinfo = glXChooseVisual( dpy, scrnum, attribs );
    if (!visinfo) {
-      printf("Error: couldn't get an RGB, Double-buffered visual\n");
+      if (stereo) {
+         printf("Error: couldn't get an RGB, "
+                "Double-buffered, Stereo visual\n");
+      } else
+         printf("Error: couldn't get an RGB, Double-buffered visual\n");
       exit(1);
    }
 
@@ -347,12 +527,16 @@ make_window( Display *dpy, const char *name,
    attr.border_pixel = 0;
    attr.colormap = XCreateColormap( dpy, root, visinfo->visual, AllocNone);
    attr.event_mask = StructureNotifyMask | ExposureMask | KeyPressMask;
+   /* XXX this is a bad way to get a borderless window! */
    mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask;
 
-   win = XCreateWindow( dpy, root, 0, 0, width, height,
+   win = XCreateWindow( dpy, root, x, y, width, height,
                        0, visinfo->depth, InputOutput,
                        visinfo->visual, mask, &attr );
 
+   if (fullscreen)
+      no_border(dpy, win);
+
    /* set hints and properties */
    {
       XSizeHints sizehints;
@@ -379,88 +563,162 @@ make_window( Display *dpy, const char *name,
 }
 
 
+/**
+ * Determine whether or not a GLX extension is supported.
+ */
+static int
+is_glx_extension_supported(Display *dpy, const char *query)
+{
+   const int scrnum = DefaultScreen(dpy);
+   const char *glx_extensions = NULL;
+   const size_t len = strlen(query);
+   const char *ptr;
+
+   if (glx_extensions == NULL) {
+      glx_extensions = glXQueryExtensionsString(dpy, scrnum);
+   }
+
+   ptr = strstr(glx_extensions, query);
+   return ((ptr != NULL) && ((ptr[len] == ' ') || (ptr[len] == '\0')));
+}
+
+
+/**
+ * Attempt to determine whether or not the display is synched to vblank.
+ */
 static void
-event_loop(Display *dpy, Window win)
+query_vsync(Display *dpy, GLXDrawable drawable)
 {
-   while (1) {
-      while (XPending(dpy) > 0) {
-         XEvent event;
-         XNextEvent(dpy, &event);
-         switch (event.type) {
-        case Expose:
-            /* we'll redraw below */
-           break;
-        case ConfigureNotify:
-           reshape(event.xconfigure.width, event.xconfigure.height);
-           break;
-         case KeyPress:
-            {
-               char buffer[10];
-               int r, code;
-               code = XLookupKeysym(&event.xkey, 0);
-               if (code == XK_Left) {
-                  view_roty += 5.0;
-               }
-               else if (code == XK_Right) {
-                  view_roty -= 5.0;
-               }
-               else if (code == XK_Up) {
-                  view_rotx += 5.0;
-               }
-               else if (code == XK_Down) {
-                  view_rotx -= 5.0;
-               }
-               else {
-                  r = XLookupString(&event.xkey, buffer, sizeof(buffer),
-                                    NULL, NULL);
-                  if (buffer[0] == 27) {
-                     /* escape */
-                     return;
-                  }
-               }
-            }
-         }
-      }
+   int interval = 0;
+
+#if defined(GLX_EXT_swap_control)
+   if (is_glx_extension_supported(dpy, "GLX_EXT_swap_control")) {
+       unsigned int tmp = -1;
+       glXQueryDrawable(dpy, drawable, GLX_SWAP_INTERVAL_EXT, &tmp);
+       interval = tmp;
+   } else
+#endif
+   if (is_glx_extension_supported(dpy, "GLX_MESA_swap_control")) {
+      PFNGLXGETSWAPINTERVALMESAPROC pglXGetSwapIntervalMESA =
+          (PFNGLXGETSWAPINTERVALMESAPROC)
+          glXGetProcAddressARB((const GLubyte *) "glXGetSwapIntervalMESA");
+
+      interval = (*pglXGetSwapIntervalMESA)();
+   } else if (is_glx_extension_supported(dpy, "GLX_SGI_swap_control")) {
+      /* The default swap interval with this extension is 1.  Assume that it
+       * is set to the default.
+       *
+       * Many Mesa-based drivers default to 0, but all of these drivers also
+       * export GLX_MESA_swap_control.  In that case, this branch will never
+       * be taken, and the correct result should be reported.
+       */
+      interval = 1;
+   }
 
-      /* next frame */
-      angle += 2.0;
-      if (angle > 3600.0)
-       angle -= 3600.0;
 
-      draw();
-      glXSwapBuffers(dpy, win);
+   if (interval > 0) {
+      printf("Running synchronized to the vertical refresh.  The framerate should be\n");
+      if (interval == 1) {
+         printf("approximately the same as the monitor refresh rate.\n");
+      } else if (interval > 1) {
+         printf("approximately 1/%d the monitor refresh rate.\n",
+                interval);
+      }
+   }
+}
 
-      /* calc framerate */
+/**
+ * Handle one X event.
+ * \return NOP, EXIT or DRAW
+ */
+static int
+handle_event(Display *dpy, Window win, XEvent *event)
+{
+   (void) dpy;
+   (void) win;
+
+   switch (event->type) {
+   case Expose:
+      return DRAW;
+   case ConfigureNotify:
+      reshape(event->xconfigure.width, event->xconfigure.height);
+      break;
+   case KeyPress:
       {
-         static int t0 = -1;
-         static int frames = 0;
-         int t = current_time();
-
-         if (t0 < 0)
-            t0 = t;
-
-         frames++;
-
-         if (t - t0 >= 5.0) {
-            GLfloat seconds = t - t0;
-            GLfloat fps = frames / seconds;
-            printf("%d frames in %3.1f seconds = %6.3f FPS\n", frames, seconds,
-                   fps);
-            t0 = t;
-            frames = 0;
+         char buffer[10];
+         int r, code;
+         code = XLookupKeysym(&event->xkey, 0);
+         if (code == XK_Left) {
+            view_roty += 5.0;
          }
+         else if (code == XK_Right) {
+            view_roty -= 5.0;
+         }
+         else if (code == XK_Up) {
+            view_rotx += 5.0;
+         }
+         else if (code == XK_Down) {
+            view_rotx -= 5.0;
+         }
+         else {
+            r = XLookupString(&event->xkey, buffer, sizeof(buffer),
+                              NULL, NULL);
+            if (buffer[0] == 27) {
+               /* escape */
+               return EXIT;
+            }
+            else if (buffer[0] == 'a' || buffer[0] == 'A') {
+               animate = !animate;
+            }
+         }
+         return DRAW;
+      }
+   }
+   return NOP;
+}
+
+
+static void
+event_loop(Display *dpy, Window win)
+{
+   while (1) {
+      int op;
+      while (!animate || XPending(dpy) > 0) {
+         XEvent event;
+         XNextEvent(dpy, &event);
+         op = handle_event(dpy, win, &event);
+         if (op == EXIT)
+            return;
+         else if (op == DRAW)
+            break;
       }
+
+      draw_frame(dpy, win);
    }
 }
 
 
+static void
+usage(void)
+{
+   printf("Usage:\n");
+   printf("  -display <displayname>  set the display to run on\n");
+   printf("  -stereo                 run in stereo mode\n");
+   printf("  -fullscreen             run in fullscreen mode\n");
+   printf("  -info                   display OpenGL renderer info\n");
+   printf("  -geometry WxH+X+Y       window geometry\n");
+}
+
 int
 main(int argc, char *argv[])
 {
+   unsigned int winWidth = 300, winHeight = 300;
+   int x = 0, y = 0;
    Display *dpy;
    Window win;
    GLXContext ctx;
-   char *dpyName = ":0";
+   char *dpyName = NULL;
    GLboolean printInfo = GL_FALSE;
    int i;
 
@@ -472,17 +730,33 @@ main(int argc, char *argv[])
       else if (strcmp(argv[i], "-info") == 0) {
          printInfo = GL_TRUE;
       }
+      else if (strcmp(argv[i], "-stereo") == 0) {
+         stereo = GL_TRUE;
+      }
+      else if (strcmp(argv[i], "-fullscreen") == 0) {
+         fullscreen = GL_TRUE;
+      }
+      else if (i < argc-1 && strcmp(argv[i], "-geometry") == 0) {
+         XParseGeometry(argv[i+1], &x, &y, &winWidth, &winHeight);
+         i++;
+      }
+      else {
+         usage();
+         return -1;
+      }
    }
 
    dpy = XOpenDisplay(dpyName);
    if (!dpy) {
-      printf("Error: couldn't open display %s\n", dpyName);
+      printf("Error: couldn't open display %s\n",
+            dpyName ? dpyName : getenv("DISPLAY"));
       return -1;
    }
 
-   make_window(dpy, "glxgears", 0, 0, 300, 300, &win, &ctx);
+   make_window(dpy, "glxgears", x, y, winWidth, winHeight, &win, &ctx);
    XMapWindow(dpy, win);
    glXMakeCurrent(dpy, win, ctx);
+   query_vsync(dpy, win);
 
    if (printInfo) {
       printf("GL_RENDERER   = %s\n", (char *) glGetString(GL_RENDERER));
@@ -493,8 +767,18 @@ main(int argc, char *argv[])
 
    init();
 
+   /* Set initial projection/viewing transformation.
+    * We can't be sure we'll get a ConfigureNotify event when the window
+    * first appears.
+    */
+   reshape(winWidth, winHeight);
+
    event_loop(dpy, win);
 
+   glDeleteLists(gear1, 1);
+   glDeleteLists(gear2, 1);
+   glDeleteLists(gear3, 1);
+   glXMakeCurrent(dpy, None, NULL);
    glXDestroyContext(dpy, ctx);
    XDestroyWindow(dpy, win);
    XCloseDisplay(dpy);