1/*
2 * Copyright (C) 1999-2001  Brian Paul   All Rights Reserved.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included
12 * in all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
17 * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
18 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 */
21
22/*
23 * This is a port of the infamous "gears" demo to straight GLX (i.e. no GLUT)
24 * Port by Brian Paul  23 March 2001
25 *
26 * See usage() below for command line options.
27 */
28
29
30#include <math.h>
31#include <stdlib.h>
32#include <stdio.h>
33#include <string.h>
34#include <X11/Xlib.h>
35#include <X11/keysym.h>
36#include <GL/gl.h>
37#include <GL/glx.h>
38#include <GL/glxext.h>
39
40#ifndef GLX_MESA_swap_control
41#define GLX_MESA_swap_control 1
42typedef int (*PFNGLXGETSWAPINTERVALMESAPROC)(void);
43#endif
44
45
46#define BENCHMARK
47
48#ifdef BENCHMARK
49
50/* XXX this probably isn't very portable */
51
52#include <sys/time.h>
53#include <unistd.h>
54
55/* return current time (in seconds) */
56static double
57current_time(void)
58{
59   struct timeval tv;
60#ifdef __VMS
61   (void) gettimeofday(&tv, NULL );
62#else
63   struct timezone tz;
64   (void) gettimeofday(&tv, &tz);
65#endif
66   return (double) tv.tv_sec + tv.tv_usec / 1000000.0;
67}
68
69#else /*BENCHMARK*/
70
71/* dummy */
72static double
73current_time(void)
74{
75   /* update this function for other platforms! */
76   static double t = 0.0;
77   static int warn = 1;
78   if (warn) {
79      fprintf(stderr, "Warning: current_time() not implemented!!\n");
80      warn = 0;
81   }
82   return t += 1.0;
83}
84
85#endif /*BENCHMARK*/
86
87
88
89#ifndef M_PI
90#define M_PI 3.14159265
91#endif
92
93
94/** Event handler results: */
95#define NOP 0
96#define EXIT 1
97#define DRAW 2
98
99static GLfloat view_rotx = 20.0, view_roty = 30.0, view_rotz = 0.0;
100static GLint gear1, gear2, gear3;
101static GLfloat angle = 0.0;
102
103static GLboolean fullscreen = GL_FALSE;	/* Create a single fullscreen window */
104static GLboolean stereo = GL_FALSE;	/* Enable stereo.  */
105static GLint samples = 0;               /* Choose visual with at least N samples. */
106static GLboolean animate = GL_TRUE;	/* Animation */
107static GLfloat eyesep = 5.0;		/* Eye separation. */
108static GLfloat fix_point = 40.0;	/* Fixation point distance.  */
109static GLfloat left, right, asp;	/* Stereo frustum params.  */
110
111
112/*
113 *
114 *  Draw a gear wheel.  You'll probably want to call this function when
115 *  building a display list since we do a lot of trig here.
116 *
117 *  Input:  inner_radius - radius of hole at center
118 *          outer_radius - radius at center of teeth
119 *          width - width of gear
120 *          teeth - number of teeth
121 *          tooth_depth - depth of tooth
122 */
123static void
124gear(GLfloat inner_radius, GLfloat outer_radius, GLfloat width,
125     GLint teeth, GLfloat tooth_depth)
126{
127   GLint i;
128   GLfloat r0, r1, r2;
129   GLfloat angle, da;
130   GLfloat u, v, len;
131
132   r0 = inner_radius;
133   r1 = outer_radius - tooth_depth / 2.0;
134   r2 = outer_radius + tooth_depth / 2.0;
135
136   da = 2.0 * M_PI / teeth / 4.0;
137
138   glShadeModel(GL_FLAT);
139
140   glNormal3f(0.0, 0.0, 1.0);
141
142   /* draw front face */
143   glBegin(GL_QUAD_STRIP);
144   for (i = 0; i <= teeth; i++) {
145      angle = i * 2.0 * M_PI / teeth;
146      glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
147      glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
148      if (i < teeth) {
149	 glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
150	 glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
151		    width * 0.5);
152      }
153   }
154   glEnd();
155
156   /* draw front sides of teeth */
157   glBegin(GL_QUADS);
158   da = 2.0 * M_PI / teeth / 4.0;
159   for (i = 0; i < teeth; i++) {
160      angle = i * 2.0 * M_PI / teeth;
161
162      glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
163      glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5);
164      glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
165		 width * 0.5);
166      glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
167		 width * 0.5);
168   }
169   glEnd();
170
171   glNormal3f(0.0, 0.0, -1.0);
172
173   /* draw back face */
174   glBegin(GL_QUAD_STRIP);
175   for (i = 0; i <= teeth; i++) {
176      angle = i * 2.0 * M_PI / teeth;
177      glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
178      glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
179      if (i < teeth) {
180	 glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
181		    -width * 0.5);
182	 glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
183      }
184   }
185   glEnd();
186
187   /* draw back sides of teeth */
188   glBegin(GL_QUADS);
189   da = 2.0 * M_PI / teeth / 4.0;
190   for (i = 0; i < teeth; i++) {
191      angle = i * 2.0 * M_PI / teeth;
192
193      glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
194		 -width * 0.5);
195      glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
196		 -width * 0.5);
197      glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5);
198      glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
199   }
200   glEnd();
201
202   /* draw outward faces of teeth */
203   glBegin(GL_QUAD_STRIP);
204   for (i = 0; i < teeth; i++) {
205      angle = i * 2.0 * M_PI / teeth;
206
207      glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
208      glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
209      u = r2 * cos(angle + da) - r1 * cos(angle);
210      v = r2 * sin(angle + da) - r1 * sin(angle);
211      len = sqrt(u * u + v * v);
212      u /= len;
213      v /= len;
214      glNormal3f(v, -u, 0.0);
215      glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5);
216      glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5);
217      glNormal3f(cos(angle), sin(angle), 0.0);
218      glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
219		 width * 0.5);
220      glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
221		 -width * 0.5);
222      u = r1 * cos(angle + 3 * da) - r2 * cos(angle + 2 * da);
223      v = r1 * sin(angle + 3 * da) - r2 * sin(angle + 2 * da);
224      glNormal3f(v, -u, 0.0);
225      glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
226		 width * 0.5);
227      glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
228		 -width * 0.5);
229      glNormal3f(cos(angle), sin(angle), 0.0);
230   }
231
232   glVertex3f(r1 * cos(0), r1 * sin(0), width * 0.5);
233   glVertex3f(r1 * cos(0), r1 * sin(0), -width * 0.5);
234
235   glEnd();
236
237   glShadeModel(GL_SMOOTH);
238
239   /* draw inside radius cylinder */
240   glBegin(GL_QUAD_STRIP);
241   for (i = 0; i <= teeth; i++) {
242      angle = i * 2.0 * M_PI / teeth;
243      glNormal3f(-cos(angle), -sin(angle), 0.0);
244      glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
245      glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
246   }
247   glEnd();
248}
249
250
251static void
252draw(void)
253{
254   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
255
256   glPushMatrix();
257   glRotatef(view_rotx, 1.0, 0.0, 0.0);
258   glRotatef(view_roty, 0.0, 1.0, 0.0);
259   glRotatef(view_rotz, 0.0, 0.0, 1.0);
260
261   glPushMatrix();
262   glTranslatef(-3.0, -2.0, 0.0);
263   glRotatef(angle, 0.0, 0.0, 1.0);
264   glCallList(gear1);
265   glPopMatrix();
266
267   glPushMatrix();
268   glTranslatef(3.1, -2.0, 0.0);
269   glRotatef(-2.0 * angle - 9.0, 0.0, 0.0, 1.0);
270   glCallList(gear2);
271   glPopMatrix();
272
273   glPushMatrix();
274   glTranslatef(-3.1, 4.2, 0.0);
275   glRotatef(-2.0 * angle - 25.0, 0.0, 0.0, 1.0);
276   glCallList(gear3);
277   glPopMatrix();
278
279   glPopMatrix();
280}
281
282
283static void
284draw_gears(void)
285{
286   if (stereo) {
287      /* First left eye.  */
288      glDrawBuffer(GL_BACK_LEFT);
289
290      glMatrixMode(GL_PROJECTION);
291      glLoadIdentity();
292      glFrustum(left, right, -asp, asp, 5.0, 60.0);
293
294      glMatrixMode(GL_MODELVIEW);
295
296      glPushMatrix();
297      glTranslated(+0.5 * eyesep, 0.0, 0.0);
298      draw();
299      glPopMatrix();
300
301      /* Then right eye.  */
302      glDrawBuffer(GL_BACK_RIGHT);
303
304      glMatrixMode(GL_PROJECTION);
305      glLoadIdentity();
306      glFrustum(-right, -left, -asp, asp, 5.0, 60.0);
307
308      glMatrixMode(GL_MODELVIEW);
309
310      glPushMatrix();
311      glTranslated(-0.5 * eyesep, 0.0, 0.0);
312      draw();
313      glPopMatrix();
314   }
315   else {
316      draw();
317   }
318}
319
320
321/** Draw single frame, do SwapBuffers, compute FPS */
322static void
323draw_frame(Display *dpy, Window win)
324{
325   static int frames = 0;
326   static double tRot0 = -1.0, tRate0 = -1.0;
327   double dt, t = current_time();
328
329   if (tRot0 < 0.0)
330      tRot0 = t;
331   dt = t - tRot0;
332   tRot0 = t;
333
334   if (animate) {
335      /* advance rotation for next frame */
336      angle += 70.0 * dt;  /* 70 degrees per second */
337      if (angle > 3600.0)
338         angle -= 3600.0;
339   }
340
341   draw_gears();
342   glXSwapBuffers(dpy, win);
343
344   frames++;
345
346   if (tRate0 < 0.0)
347      tRate0 = t;
348   if (t - tRate0 >= 5.0) {
349      GLfloat seconds = t - tRate0;
350      GLfloat fps = frames / seconds;
351      printf("%d frames in %3.1f seconds = %6.3f FPS\n", frames, seconds,
352             fps);
353      fflush(stdout);
354      tRate0 = t;
355      frames = 0;
356   }
357}
358
359
360/* new window size or exposure */
361static void
362reshape(int width, int height)
363{
364   glViewport(0, 0, (GLint) width, (GLint) height);
365
366   if (stereo) {
367      GLfloat w;
368
369      asp = (GLfloat) height / (GLfloat) width;
370      w = fix_point * (1.0 / 5.0);
371
372      left = -5.0 * ((w - 0.5 * eyesep) / fix_point);
373      right = 5.0 * ((w + 0.5 * eyesep) / fix_point);
374   }
375   else {
376      GLfloat h = (GLfloat) height / (GLfloat) width;
377
378      glMatrixMode(GL_PROJECTION);
379      glLoadIdentity();
380      glFrustum(-1.0, 1.0, -h, h, 5.0, 60.0);
381   }
382
383   glMatrixMode(GL_MODELVIEW);
384   glLoadIdentity();
385   glTranslatef(0.0, 0.0, -40.0);
386}
387
388
389
390static void
391init(void)
392{
393   static GLfloat pos[4] = { 5.0, 5.0, 10.0, 0.0 };
394   static GLfloat red[4] = { 0.8, 0.1, 0.0, 1.0 };
395   static GLfloat green[4] = { 0.0, 0.8, 0.2, 1.0 };
396   static GLfloat blue[4] = { 0.2, 0.2, 1.0, 1.0 };
397
398   glLightfv(GL_LIGHT0, GL_POSITION, pos);
399   glEnable(GL_CULL_FACE);
400   glEnable(GL_LIGHTING);
401   glEnable(GL_LIGHT0);
402   glEnable(GL_DEPTH_TEST);
403
404   /* make the gears */
405   gear1 = glGenLists(1);
406   glNewList(gear1, GL_COMPILE);
407   glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, red);
408   gear(1.0, 4.0, 1.0, 20, 0.7);
409   glEndList();
410
411   gear2 = glGenLists(1);
412   glNewList(gear2, GL_COMPILE);
413   glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, green);
414   gear(0.5, 2.0, 2.0, 10, 0.7);
415   glEndList();
416
417   gear3 = glGenLists(1);
418   glNewList(gear3, GL_COMPILE);
419   glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, blue);
420   gear(1.3, 2.0, 0.5, 10, 0.7);
421   glEndList();
422
423   glEnable(GL_NORMALIZE);
424}
425
426
427/**
428 * Remove window border/decorations.
429 */
430static void
431no_border( Display *dpy, Window w)
432{
433   static const unsigned MWM_HINTS_DECORATIONS = (1 << 1);
434   static const int PROP_MOTIF_WM_HINTS_ELEMENTS = 5;
435
436   typedef struct
437   {
438      unsigned long       flags;
439      unsigned long       functions;
440      unsigned long       decorations;
441      long                inputMode;
442      unsigned long       status;
443   } PropMotifWmHints;
444
445   PropMotifWmHints motif_hints;
446   Atom prop, proptype;
447   unsigned long flags = 0;
448
449   /* setup the property */
450   motif_hints.flags = MWM_HINTS_DECORATIONS;
451   motif_hints.decorations = flags;
452
453   /* get the atom for the property */
454   prop = XInternAtom( dpy, "_MOTIF_WM_HINTS", True );
455   if (!prop) {
456      /* something went wrong! */
457      return;
458   }
459
460   /* not sure this is correct, seems to work, XA_WM_HINTS didn't work */
461   proptype = prop;
462
463   XChangeProperty( dpy, w,                         /* display, window */
464                    prop, proptype,                 /* property, type */
465                    32,                             /* format: 32-bit datums */
466                    PropModeReplace,                /* mode */
467                    (unsigned char *) &motif_hints, /* data */
468                    PROP_MOTIF_WM_HINTS_ELEMENTS    /* nelements */
469                  );
470}
471
472
473/*
474 * Create an RGB, double-buffered window.
475 * Return the window and context handles.
476 */
477static void
478make_window( Display *dpy, const char *name,
479             int x, int y, int width, int height,
480             Window *winRet, GLXContext *ctxRet, VisualID *visRet)
481{
482   int attribs[64];
483   int i = 0;
484
485   int scrnum;
486   XSetWindowAttributes attr;
487   unsigned long mask;
488   Window root;
489   Window win;
490   GLXContext ctx;
491   XVisualInfo *visinfo;
492
493   /* Singleton attributes. */
494   attribs[i++] = GLX_RGBA;
495   attribs[i++] = GLX_DOUBLEBUFFER;
496   if (stereo)
497      attribs[i++] = GLX_STEREO;
498
499   /* Key/value attributes. */
500   attribs[i++] = GLX_RED_SIZE;
501   attribs[i++] = 1;
502   attribs[i++] = GLX_GREEN_SIZE;
503   attribs[i++] = 1;
504   attribs[i++] = GLX_BLUE_SIZE;
505   attribs[i++] = 1;
506   attribs[i++] = GLX_DEPTH_SIZE;
507   attribs[i++] = 1;
508   if (samples > 0) {
509      attribs[i++] = GLX_SAMPLE_BUFFERS;
510      attribs[i++] = 1;
511      attribs[i++] = GLX_SAMPLES;
512      attribs[i++] = samples;
513   }
514
515   attribs[i++] = None;
516
517   scrnum = DefaultScreen( dpy );
518   root = RootWindow( dpy, scrnum );
519
520   visinfo = glXChooseVisual(dpy, scrnum, attribs);
521   if (!visinfo) {
522      printf("Error: couldn't get an RGB, Double-buffered");
523      if (stereo)
524         printf(", Stereo");
525      if (samples > 0)
526         printf(", Multisample");
527      printf(" visual\n");
528      exit(1);
529   }
530
531   /* window attributes */
532   attr.background_pixel = 0;
533   attr.border_pixel = 0;
534   attr.colormap = XCreateColormap( dpy, root, visinfo->visual, AllocNone);
535   attr.event_mask = StructureNotifyMask | ExposureMask | KeyPressMask;
536   /* XXX this is a bad way to get a borderless window! */
537   mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask;
538
539   win = XCreateWindow( dpy, root, x, y, width, height,
540		        0, visinfo->depth, InputOutput,
541		        visinfo->visual, mask, &attr );
542
543   if (fullscreen)
544      no_border(dpy, win);
545
546   /* set hints and properties */
547   {
548      XSizeHints sizehints;
549      sizehints.x = x;
550      sizehints.y = y;
551      sizehints.width  = width;
552      sizehints.height = height;
553      sizehints.flags = USSize | USPosition;
554      XSetNormalHints(dpy, win, &sizehints);
555      XSetStandardProperties(dpy, win, name, name,
556                              None, (char **)NULL, 0, &sizehints);
557   }
558
559   ctx = glXCreateContext( dpy, visinfo, NULL, True );
560   if (!ctx) {
561      printf("Error: glXCreateContext failed\n");
562      exit(1);
563   }
564
565   *winRet = win;
566   *ctxRet = ctx;
567   *visRet = visinfo->visualid;
568
569   XFree(visinfo);
570}
571
572
573/**
574 * Determine whether or not a GLX extension is supported.
575 */
576static int
577is_glx_extension_supported(Display *dpy, const char *query)
578{
579   const int scrnum = DefaultScreen(dpy);
580   const char *glx_extensions = NULL;
581   const size_t len = strlen(query);
582   const char *ptr;
583
584   if (glx_extensions == NULL) {
585      glx_extensions = glXQueryExtensionsString(dpy, scrnum);
586   }
587
588   ptr = strstr(glx_extensions, query);
589   return ((ptr != NULL) && ((ptr[len] == ' ') || (ptr[len] == '\0')));
590}
591
592
593/**
594 * Attempt to determine whether or not the display is synched to vblank.
595 */
596static void
597query_vsync(Display *dpy, GLXDrawable drawable)
598{
599   int interval = 0;
600
601#if defined(GLX_EXT_swap_control)
602   if (is_glx_extension_supported(dpy, "GLX_EXT_swap_control")) {
603       unsigned int tmp = -1;
604       glXQueryDrawable(dpy, drawable, GLX_SWAP_INTERVAL_EXT, &tmp);
605       interval = tmp;
606   } else
607#endif
608   if (is_glx_extension_supported(dpy, "GLX_MESA_swap_control")) {
609      PFNGLXGETSWAPINTERVALMESAPROC pglXGetSwapIntervalMESA =
610          (PFNGLXGETSWAPINTERVALMESAPROC)
611          glXGetProcAddressARB((const GLubyte *) "glXGetSwapIntervalMESA");
612
613      interval = (*pglXGetSwapIntervalMESA)();
614   } else if (is_glx_extension_supported(dpy, "GLX_SGI_swap_control")) {
615      /* The default swap interval with this extension is 1.  Assume that it
616       * is set to the default.
617       *
618       * Many Mesa-based drivers default to 0, but all of these drivers also
619       * export GLX_MESA_swap_control.  In that case, this branch will never
620       * be taken, and the correct result should be reported.
621       */
622      interval = 1;
623   }
624
625
626   if (interval > 0) {
627      printf("Running synchronized to the vertical refresh.  The framerate should be\n");
628      if (interval == 1) {
629         printf("approximately the same as the monitor refresh rate.\n");
630      } else if (interval > 1) {
631         printf("approximately 1/%d the monitor refresh rate.\n",
632                interval);
633      }
634   }
635}
636
637/**
638 * Handle one X event.
639 * \return NOP, EXIT or DRAW
640 */
641static int
642handle_event(Display *dpy, Window win, XEvent *event)
643{
644   (void) dpy;
645   (void) win;
646
647   switch (event->type) {
648   case Expose:
649      return DRAW;
650   case ConfigureNotify:
651      reshape(event->xconfigure.width, event->xconfigure.height);
652      break;
653   case KeyPress:
654      {
655         char buffer[10];
656         int code;
657         code = XLookupKeysym(&event->xkey, 0);
658         if (code == XK_Left) {
659            view_roty += 5.0;
660         }
661         else if (code == XK_Right) {
662            view_roty -= 5.0;
663         }
664         else if (code == XK_Up) {
665            view_rotx += 5.0;
666         }
667         else if (code == XK_Down) {
668            view_rotx -= 5.0;
669         }
670         else {
671            XLookupString(&event->xkey, buffer, sizeof(buffer),
672                          NULL, NULL);
673            if (buffer[0] == 27) {
674               /* escape */
675               return EXIT;
676            }
677            else if (buffer[0] == 'a' || buffer[0] == 'A') {
678               animate = !animate;
679            }
680         }
681         return DRAW;
682      }
683   }
684   return NOP;
685}
686
687
688static void
689event_loop(Display *dpy, Window win)
690{
691   while (1) {
692      int op;
693      while (!animate || XPending(dpy) > 0) {
694         XEvent event;
695         XNextEvent(dpy, &event);
696         op = handle_event(dpy, win, &event);
697         if (op == EXIT)
698            return;
699         else if (op == DRAW)
700            break;
701      }
702
703      draw_frame(dpy, win);
704   }
705}
706
707
708static void
709usage(void)
710{
711   printf("Usage:\n");
712   printf("  -display <displayname>  set the display to run on\n");
713   printf("  -stereo                 run in stereo mode\n");
714   printf("  -samples N              run in multisample mode with at least N samples\n");
715   printf("  -fullscreen             run in fullscreen mode\n");
716   printf("  -info                   display OpenGL renderer info\n");
717   printf("  -geometry WxH+X+Y       window geometry\n");
718}
719
720
721int
722main(int argc, char *argv[])
723{
724   unsigned int winWidth = 300, winHeight = 300;
725   int x = 0, y = 0;
726   Display *dpy;
727   Window win;
728   GLXContext ctx;
729   char *dpyName = NULL;
730   GLboolean printInfo = GL_FALSE;
731   VisualID visId;
732   int i;
733
734   for (i = 1; i < argc; i++) {
735      if (strcmp(argv[i], "-display") == 0) {
736         dpyName = argv[i+1];
737         i++;
738      }
739      else if (strcmp(argv[i], "-info") == 0) {
740         printInfo = GL_TRUE;
741      }
742      else if (strcmp(argv[i], "-stereo") == 0) {
743         stereo = GL_TRUE;
744      }
745      else if (i < argc-1 && strcmp(argv[i], "-samples") == 0) {
746         samples = strtod(argv[i+1], NULL );
747         ++i;
748      }
749      else if (strcmp(argv[i], "-fullscreen") == 0) {
750         fullscreen = GL_TRUE;
751      }
752      else if (i < argc-1 && strcmp(argv[i], "-geometry") == 0) {
753         XParseGeometry(argv[i+1], &x, &y, &winWidth, &winHeight);
754         i++;
755      }
756      else {
757         usage();
758         return -1;
759      }
760   }
761
762   dpy = XOpenDisplay(dpyName);
763   if (!dpy) {
764      printf("Error: couldn't open display %s\n",
765	     dpyName ? dpyName : getenv("DISPLAY"));
766      return -1;
767   }
768
769   if (fullscreen) {
770      int scrnum = DefaultScreen(dpy);
771
772      x = 0; y = 0;
773      winWidth = DisplayWidth(dpy, scrnum);
774      winHeight = DisplayHeight(dpy, scrnum);
775   }
776
777   make_window(dpy, "glxgears", x, y, winWidth, winHeight, &win, &ctx, &visId);
778   XMapWindow(dpy, win);
779   glXMakeCurrent(dpy, win, ctx);
780   query_vsync(dpy, win);
781
782   if (printInfo) {
783      printf("GL_RENDERER   = %s\n", (char *) glGetString(GL_RENDERER));
784      printf("GL_VERSION    = %s\n", (char *) glGetString(GL_VERSION));
785      printf("GL_VENDOR     = %s\n", (char *) glGetString(GL_VENDOR));
786      printf("GL_EXTENSIONS = %s\n", (char *) glGetString(GL_EXTENSIONS));
787      printf("VisualID %d, 0x%x\n", (int) visId, (int) visId);
788   }
789
790   init();
791
792   /* Set initial projection/viewing transformation.
793    * We can't be sure we'll get a ConfigureNotify event when the window
794    * first appears.
795    */
796   reshape(winWidth, winHeight);
797
798   event_loop(dpy, win);
799
800   glDeleteLists(gear1, 1);
801   glDeleteLists(gear2, 1);
802   glDeleteLists(gear3, 1);
803   glXMakeCurrent(dpy, None, NULL);
804   glXDestroyContext(dpy, ctx);
805   XDestroyWindow(dpy, win);
806   XCloseDisplay(dpy);
807
808   return 0;
809}
810