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 /* $XFree86: xc/programs/glxgears/glxgears.c,v 1.3tsi Exp $ */
22 
23 /*
24  * This is a port of the infamous "gears" demo to straight GLX (i.e. no GLUT)
25  * Port by Brian Paul  23 March 2001
26  *
27  * Exact timing added by Behdad Esfahbod to achieve a fixed speed regardless
28  * of frame rate.  November 2003
29  *
30  * Printer support added by Roland Mainz <roland.mainz@nrubsig.org>. April 2004
31  *
32  * This version modified by Ian Smith, 30 Sept 2007, to make ubgears.
33  * ubgears is cusoimised for use in the UnixBench benchmarking suite.
34  * Some redundant stuff is gone, and the -time option is added.
35  * Mainly it's forked so we don't use the host's version, which could change
36  * from platform to platform.
37  *
38  * Command line options:
39  *    -display         Set X11 display for output.
40  *    -info            Print additional GLX information.
41  *    -time <t>        Run for <t> seconds and produce a performance report.
42  *    -h               Print this help page.
43  *    -v               Verbose output.
44  *
45  */
46 
47 
48 #include <X11/Xlib.h>
49 #include <X11/Xutil.h>
50 #include <X11/keysym.h>
51 #include <GL/gl.h>
52 #include <GL/glx.h>
53 #include <sys/time.h>
54 #include <sched.h>
55 #include <math.h>
56 #include <stdlib.h>
57 #include <stdio.h>
58 #include <errno.h>
59 #include <string.h>
60 
61 #ifndef M_PI
62 #define M_PI 3.14159265
63 #endif /* !M_PI */
64 
65 /* Turn a NULL pointer string into an empty string */
66 #define NULLSTR(x) (((x)!=NULL)?(x):(""))
67 #define Log(x) { if(verbose) printf x; }
68 #define Msg(x) { printf x; }
69 
70 /* Globla vars */
71 /* program name (from argv[0]) */
72 static const char *ProgramName;
73 
74 /* verbose output what the program is doing */
75 static Bool  verbose = False;
76 
77 /* time in microseconds to run for; -1 means forever. */
78 static int   runTime = -1;
79 
80 /* Time at which start_time(void) was called. */
81 static struct timeval clockStart;
82 
83 /* XXX this probably isn't very portable */
84 
85 /* return current time (in seconds) */
86 static void
start_time(void)87 start_time(void)
88 {
89    (void) gettimeofday(&clockStart, 0);
90 }
91 
92 /*
93  * return time (in microseconds) since start_time(void) was called.
94  *
95  * The older version of this function randomly returned negative results.
96  * This version won't, up to 2000 seconds and some.
97  */
98 static long
current_time(void)99 current_time(void)
100 {
101    struct timeval tv;
102    long secs, micros;
103 
104    (void) gettimeofday(&tv, 0);
105 
106    secs = tv.tv_sec - clockStart.tv_sec;
107    micros = tv.tv_usec - clockStart.tv_usec;
108    if (micros < 0) {
109        --secs;
110        micros += 1000000;
111    }
112    return secs * 1000000 + micros;
113 }
114 
115 static
usage(void)116 void usage(void)
117 {
118    fprintf (stderr, "usage:  %s [options]\n", ProgramName);
119    fprintf (stderr, "-display\tSet X11 display for output.\n");
120    fprintf (stderr, "-info\t\tPrint additional GLX information.\n");
121    fprintf (stderr, "-time t\t\tRun for t seconds and report performance.\n");
122    fprintf (stderr, "-h\t\tPrint this help page.\n");
123    fprintf (stderr, "-v\t\tVerbose output.\n");
124    fprintf (stderr, "\n");
125    exit(EXIT_FAILURE);
126 }
127 
128 
129 static GLfloat view_rotx = 20.0, view_roty = 30.0, view_rotz = 0.0;
130 static GLint gear1, gear2, gear3;
131 static GLfloat angle = 0.0;
132 static GLint speed = 60;
133 static GLboolean printInfo = GL_FALSE;
134 
135 /*
136  *
137  *  Draw a gear wheel.  You'll probably want to call this function when
138  *  building a display list since we do a lot of trig here.
139  *
140  *  Input:  inner_radius - radius of hole at center
141  *          outer_radius - radius at center of teeth
142  *          width - width of gear
143  *          teeth - number of teeth
144  *          tooth_depth - depth of tooth
145  */
146 static void
gear(GLfloat inner_radius,GLfloat outer_radius,GLfloat width,GLint teeth,GLfloat tooth_depth)147 gear(GLfloat inner_radius, GLfloat outer_radius, GLfloat width,
148      GLint teeth, GLfloat tooth_depth)
149 {
150    GLint i;
151    GLfloat r0, r1, r2, maxr2, minr2;
152    GLfloat angle, da;
153    GLfloat u, v, len;
154 
155    r0 = inner_radius;
156    r1 = outer_radius - tooth_depth / 2.0;
157    maxr2 = r2 = outer_radius + tooth_depth / 2.0;
158    minr2 = r2;
159 
160    da = 2.0 * M_PI / teeth / 4.0;
161 
162    glShadeModel(GL_FLAT);
163 
164    glNormal3f(0.0, 0.0, 1.0);
165 
166    /* draw front face */
167    glBegin(GL_QUAD_STRIP);
168    for (i = 0; i <= teeth; i++) {
169       angle = i * 2.0 * M_PI / teeth;
170       glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
171       glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
172       if (i < teeth) {
173          glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
174          glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
175                     width * 0.5);
176       }
177    }
178    glEnd();
179 
180    /* draw front sides of teeth */
181    glBegin(GL_QUADS);
182    for (i = 0; i < teeth; i++) {
183       angle = i * 2.0 * M_PI / teeth;
184 
185       glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
186       glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5);
187       glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
188                  width * 0.5);
189       glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
190                  width * 0.5);
191       r2 = minr2;
192    }
193    r2 = maxr2;
194    glEnd();
195 
196    glNormal3f(0.0, 0.0, -1.0);
197 
198    /* draw back face */
199    glBegin(GL_QUAD_STRIP);
200    for (i = 0; i <= teeth; i++) {
201       angle = i * 2.0 * M_PI / teeth;
202       glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
203       glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
204       if (i < teeth) {
205          glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
206                     -width * 0.5);
207          glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
208       }
209    }
210    glEnd();
211 
212    /* draw back sides of teeth */
213    glBegin(GL_QUADS);
214    da = 2.0 * M_PI / teeth / 4.0;
215    for (i = 0; i < teeth; i++) {
216       angle = i * 2.0 * M_PI / teeth;
217 
218       glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
219                  -width * 0.5);
220       glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
221                  -width * 0.5);
222       glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5);
223       glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
224       r2 = minr2;
225    }
226    r2 = maxr2;
227    glEnd();
228 
229    /* draw outward faces of teeth */
230    glBegin(GL_QUAD_STRIP);
231    for (i = 0; i < teeth; i++) {
232       angle = i * 2.0 * M_PI / teeth;
233 
234       glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
235       glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
236       u = r2 * cos(angle + da) - r1 * cos(angle);
237       v = r2 * sin(angle + da) - r1 * sin(angle);
238       len = sqrt(u * u + v * v);
239       u /= len;
240       v /= len;
241       glNormal3f(v, -u, 0.0);
242       glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5);
243       glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5);
244       glNormal3f(cos(angle + 1.5 * da), sin(angle + 1.5 * da), 0.0);
245       glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
246                  width * 0.5);
247       glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
248                  -width * 0.5);
249       u = r1 * cos(angle + 3 * da) - r2 * cos(angle + 2 * da);
250       v = r1 * sin(angle + 3 * da) - r2 * sin(angle + 2 * da);
251       glNormal3f(v, -u, 0.0);
252       glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
253                  width * 0.5);
254       glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
255                  -width * 0.5);
256       glNormal3f(cos(angle + 3.5 * da), sin(angle + 3.5 * da), 0.0);
257       r2 = minr2;
258    }
259    r2 = maxr2;
260 
261    glVertex3f(r1 * cos(0), r1 * sin(0), width * 0.5);
262    glVertex3f(r1 * cos(0), r1 * sin(0), -width * 0.5);
263 
264    glEnd();
265 
266    glShadeModel(GL_SMOOTH);
267 
268    /* draw inside radius cylinder */
269    glBegin(GL_QUAD_STRIP);
270    for (i = 0; i <= teeth; i++) {
271       angle = i * 2.0 * M_PI / teeth;
272       glNormal3f(-cos(angle), -sin(angle), 0.0);
273       glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
274       glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
275    }
276    glEnd();
277 }
278 
279 
280 static void
draw(void)281 draw(void)
282 {
283    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
284 
285    glPushMatrix();
286    glRotatef(view_rotx, 1.0, 0.0, 0.0);
287    glRotatef(view_roty, 0.0, 1.0, 0.0);
288    glRotatef(view_rotz, 0.0, 0.0, 1.0);
289 
290    glPushMatrix();
291    glTranslatef(-3.0, -2.0, 0.0);
292    glRotatef(angle, 0.0, 0.0, 1.0);
293    glCallList(gear1);
294    glPopMatrix();
295 
296    glPushMatrix();
297    glTranslatef(3.1, -2.0, 0.0);
298    glRotatef(-2.0 * angle - 9.0, 0.0, 0.0, 1.0);
299    glCallList(gear2);
300    glPopMatrix();
301 
302    glPushMatrix();
303    glTranslatef(-3.1, 4.2, 0.0);
304    glRotatef(-2.0 * angle - 25.0, 0.0, 0.0, 1.0);
305    glCallList(gear3);
306    glPopMatrix();
307 
308    glPopMatrix();
309 }
310 
311 
312 /* new window size or exposure */
313 static void
reshape(int width,int height)314 reshape(int width, int height)
315 {
316    GLfloat h = (GLfloat) height / (GLfloat) width;
317 
318    glViewport(0, 0, (GLint) width, (GLint) height);
319    glMatrixMode(GL_PROJECTION);
320    glLoadIdentity();
321    /* fit width and height */
322    if (h >= 1.0)
323      glFrustum(-1.0, 1.0, -h, h, 5.0, 60.0);
324    else
325      glFrustum(-1.0/h, 1.0/h, -1.0, 1.0, 5.0, 60.0);
326    glMatrixMode(GL_MODELVIEW);
327    glLoadIdentity();
328    glTranslatef(0.0, 0.0, -40.0);
329 }
330 
331 
332 static void
init(void)333 init(void)
334 {
335    static GLfloat pos[4] = { 5.0, 5.0, 10.0, 0.0 };
336    static GLfloat red[4] = { 0.8, 0.1, 0.0, 1.0 };
337    static GLfloat green[4] = { 0.0, 0.8, 0.2, 1.0 };
338    static GLfloat blue[4] = { 0.2, 0.2, 1.0, 1.0 };
339 
340    glLightfv(GL_LIGHT0, GL_POSITION, pos);
341    glEnable(GL_CULL_FACE);
342    glEnable(GL_LIGHTING);
343    glEnable(GL_LIGHT0);
344    glEnable(GL_DEPTH_TEST);
345 
346    /* make the gears */
347    gear1 = glGenLists(1);
348    glNewList(gear1, GL_COMPILE);
349    glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, red);
350    gear(1.0, 4.0, 1.0, 20, 0.7);
351    glEndList();
352 
353    gear2 = glGenLists(1);
354    glNewList(gear2, GL_COMPILE);
355    glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, green);
356    gear(0.5, 2.0, 2.0, 10, 0.7);
357    glEndList();
358 
359    gear3 = glGenLists(1);
360    glNewList(gear3, GL_COMPILE);
361    glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, blue);
362    gear(1.3, 2.0, 0.5, 10, 0.7);
363    glEndList();
364 
365    glEnable(GL_NORMALIZE);
366 }
367 
368 
369 /*
370  * Create an RGB, double-buffered window.
371  * Return the window and context handles.
372  */
373 static void
make_window(Display * dpy,Screen * scr,const char * name,int x,int y,int width,int height,Window * winRet,GLXContext * ctxRet)374 make_window( Display *dpy, Screen *scr,
375              const char *name,
376              int x, int y, int width, int height,
377              Window *winRet, GLXContext *ctxRet)
378 {
379    int attrib[] = { GLX_RGBA,
380                    GLX_RED_SIZE, 1,
381                    GLX_GREEN_SIZE, 1,
382                    GLX_BLUE_SIZE, 1,
383                    GLX_DOUBLEBUFFER,
384                    GLX_DEPTH_SIZE, 1,
385                    None };
386    int scrnum;
387    XSetWindowAttributes attr;
388    unsigned long mask;
389    Window root;
390    Window win;
391    GLXContext ctx;
392    XVisualInfo *visinfo;
393    GLint max[2] = { 0, 0 };
394 
395    scrnum = XScreenNumberOfScreen(scr);
396    root   = XRootWindow(dpy, scrnum);
397 
398    visinfo = glXChooseVisual( dpy, scrnum, attrib );
399    if (!visinfo) {
400       fprintf(stderr, "%s: Error: couldn't get an RGB, Double-buffered visual.\n", ProgramName);
401       exit(EXIT_FAILURE);
402    }
403 
404    /* window attributes */
405    attr.background_pixel = 0;
406    attr.border_pixel = 0;
407    attr.colormap = XCreateColormap( dpy, root, visinfo->visual, AllocNone);
408    attr.event_mask = StructureNotifyMask | ExposureMask | KeyPressMask;
409    mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask;
410 
411    win = XCreateWindow( dpy, root, x, y, width, height,
412                        0, visinfo->depth, InputOutput,
413                        visinfo->visual, mask, &attr );
414 
415    /* set hints and properties */
416    {
417       XSizeHints sizehints;
418       sizehints.x = x;
419       sizehints.y = y;
420       sizehints.width  = width;
421       sizehints.height = height;
422       sizehints.flags = USSize | USPosition;
423       XSetNormalHints(dpy, win, &sizehints);
424       XSetStandardProperties(dpy, win, name, name,
425                               None, (char **)NULL, 0, &sizehints);
426    }
427 
428    ctx = glXCreateContext( dpy, visinfo, NULL, True );
429    if (!ctx) {
430       fprintf(stderr, "%s: Error: glXCreateContext failed.\n", ProgramName);
431       exit(EXIT_FAILURE);
432    }
433 
434    XFree(visinfo);
435 
436    XMapWindow(dpy, win);
437    glXMakeCurrent(dpy, win, ctx);
438 
439    /* Check for maximum size supported by the GL rasterizer */
440    glGetIntegerv(GL_MAX_VIEWPORT_DIMS, max);
441    if (printInfo)
442       printf("GL_MAX_VIEWPORT_DIMS=%d/%d\n", (int)max[0], (int)max[1]);
443    if (width > max[0] || height > max[1]) {
444       fprintf(stderr, "%s: Error: Requested window size (%d/%d) larger than "
445               "maximum supported by GL engine (%d/%d).\n",
446               ProgramName, width, height, (int)max[0], (int)max[1]);
447       exit(EXIT_FAILURE);
448    }
449 
450    *winRet = win;
451    *ctxRet = ctx;
452 }
453 
454 static void
event_loop(Display * dpy,Window win)455 event_loop(Display *dpy, Window win)
456 {
457    while (1) {
458       /* Process interactive events */
459       while (XPending(dpy) > 0) {
460          XEvent event;
461          XNextEvent(dpy, &event);
462          switch (event.type) {
463          case Expose:
464             Log(("Event: Expose\n"));
465             /* we'll redraw below */
466             break;
467          case ConfigureNotify:
468             Log(("Event: ConfigureNotify\n"));
469             reshape(event.xconfigure.width, event.xconfigure.height);
470             break;
471          }
472       }
473 
474       {
475          /* Time at which we started measuring. */
476          static long startTime = 0;
477 
478          /* Time of the previous frame. */
479          static long lastFrame = 0;
480 
481          /* Time of the previous FPS report. */
482          static long lastFps = 0;
483 
484          /* Number of frames we've done. */
485          static int frames = 0;
486 
487          /* Number of frames we've done in the measured run. */
488          static long runFrames = 0;
489 
490          long t = current_time();
491          long useconds;
492 
493          if (!lastFrame)
494             lastFrame = t;
495          if (!lastFps)
496             lastFps = t;
497 
498          /* How many microseconds since the previous frame? */
499          useconds = t - lastFrame;
500          if (!useconds) /* assume 100FPS if we don't have timer */
501             useconds = 10000;
502 
503          /* Calculate how far the gears need to move and redraw. */
504          angle = angle + ((double)speed * useconds) / 1000000.0;
505          if (angle > 360.0)
506             angle = angle - 360.0; /* don't lose precision! */
507          draw();
508          glXSwapBuffers(dpy, win);
509 
510          /* Done this frame. */
511          lastFrame = t;
512          frames++;
513 
514          /* Every 5 seconds, print the FPS. */
515          if (t - lastFps >= 5000000L) {
516             GLfloat seconds = (t - lastFps) / 1000000.0;
517             GLfloat fps = frames / seconds;
518 
519             printf("%d frames in %3.1f seconds = %6.3f FPS\n", frames, seconds,
520                    fps);
521             lastFps = t;
522             frames = 0;
523 
524             /*
525              * Set the start time now -- ie. after one report.  This
526              * gives us pump-priming time before we start for real.
527              */
528             if (runTime > 0 && startTime == 0) {
529                 printf("Start timing!\n");
530                 startTime = t;
531             }
532          }
533 
534          if (startTime > 0)
535              ++runFrames;
536 
537          /* If our run time is done, finish. */
538          if (runTime > 0 && startTime > 0 && t - startTime > runTime) {
539              double time = (double) (t - startTime) / 1000000.0;
540              fprintf(stderr, "COUNT|%ld|1|fps\n", runFrames);
541              fprintf(stderr, "TIME|%.1f\n", time);
542              exit(0);
543          }
544 
545          /* Need to give cpu away in order to get precise timing next cycle,
546           * otherwise, gettimeofday would return almost the same value. */
547          sched_yield();
548       }
549    }
550 }
551 
552 
553 int
main(int argc,char * argv[])554 main(int argc, char *argv[])
555 {
556    Bool           use_threadsafe_api = False;
557    Display       *dpy;
558    Window         win;
559    Screen        *screen;
560    GLXContext     ctx;
561    char          *dpyName            = NULL;
562    int            i;
563    XRectangle     winrect;
564 
565    ProgramName = argv[0];
566 
567    for (i = 1; i < argc; i++) {
568       const char *arg = argv[i];
569       int         len = strlen(arg);
570 
571       if (strcmp(argv[i], "-display") == 0) {
572          if (++i >= argc)
573             usage();
574          dpyName = argv[i];
575       }
576       else if (strcmp(argv[i], "-info") == 0) {
577          printInfo = GL_TRUE;
578       }
579       else if (strcmp(argv[i], "-time") == 0) {
580          if (++i >= argc)
581             usage();
582          runTime = atoi(argv[i]) * 1000000;
583       }
584       else if (!strncmp("-v", arg, len)) {
585          verbose   = True;
586          printInfo = GL_TRUE;
587       }
588       else if( !strncmp("-debug_use_threadsafe_api", arg, len) )
589       {
590          use_threadsafe_api = True;
591       }
592       else if (!strcmp(argv[i], "-h")) {
593          usage();
594       }
595       else
596       {
597         fprintf(stderr, "%s: Unsupported option '%s'.\n", ProgramName, argv[i]);
598         usage();
599       }
600    }
601 
602    /* Init X threading API on demand (for debugging) */
603    if( use_threadsafe_api )
604    {
605       if( !XInitThreads() )
606       {
607          fprintf(stderr, "%s: XInitThreads() failure.\n", ProgramName);
608          exit(EXIT_FAILURE);
609       }
610    }
611 
612    dpy = XOpenDisplay(dpyName);
613    if (!dpy) {
614       fprintf(stderr, "%s: Error: couldn't open display '%s'\n", ProgramName, dpyName);
615       return EXIT_FAILURE;
616    }
617 
618    screen = XDefaultScreenOfDisplay(dpy);
619 
620    winrect.x      = 0;
621    winrect.y      = 0;
622    winrect.width  = 300;
623    winrect.height = 300;
624 
625    Log(("Window x=%d, y=%d, width=%d, height=%d\n",
626        (int)winrect.x, (int)winrect.y, (int)winrect.width, (int)winrect.height));
627 
628    make_window(dpy, screen, "ubgears", winrect.x, winrect.y, winrect.width, winrect.height, &win, &ctx);
629    reshape(winrect.width, winrect.height);
630 
631    if (printInfo) {
632       printf("GL_RENDERER   = %s\n", (char *) glGetString(GL_RENDERER));
633       printf("GL_VERSION    = %s\n", (char *) glGetString(GL_VERSION));
634       printf("GL_VENDOR     = %s\n", (char *) glGetString(GL_VENDOR));
635       printf("GL_EXTENSIONS = %s\n", (char *) glGetString(GL_EXTENSIONS));
636    }
637 
638    init();
639 
640    start_time();
641    event_loop(dpy, win);
642 
643    glXDestroyContext(dpy, ctx);
644 
645    XDestroyWindow(dpy, win);
646    XCloseDisplay(dpy);
647 
648    return EXIT_SUCCESS;
649 }
650 
651