1/*
2 * Copyright (C) 2000  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/*
24 * This program tests GLX thread safety.
25 * Command line options:
26 *  -p                       Open a display connection for each thread
27 *  -l                       Enable application-side locking
28 *  -n <num threads>         Number of threads to create (default is 2)
29 *  -display <display name>  Specify X display (default is $DISPLAY)
30 *  -t                       Use texture mapping
31 *
32 * Brian Paul  20 July 2000
33 */
34
35
36/*
37 * Notes:
38 * - Each thread gets its own GLX context.
39 *
40 * - The GLX contexts share texture objects.
41 *
42 * - When 't' is pressed to update the texture image, the window/thread which
43 *   has input focus is signalled to change the texture.  The other threads
44 *   should see the updated texture the next time they call glBindTexture.
45 */
46
47
48#include <assert.h>
49#include <GL/gl.h>
50#include <GL/glx.h>
51#include <math.h>
52#include <stdio.h>
53#include <stdlib.h>
54#include <string.h>
55#include <unistd.h>
56#include <pthread.h>
57
58
59/*
60 * Each window/thread/context:
61 */
62struct winthread {
63   Display *Dpy;
64   int Index;
65   pthread_t Thread;
66   Window Win;
67   GLXContext Context;
68   float Angle;
69   int WinWidth, WinHeight;
70   GLboolean NewSize;
71   GLboolean Initialized;
72   GLboolean MakeNewTexture;
73};
74
75
76#define MAX_WINTHREADS 100
77static struct winthread WinThreads[MAX_WINTHREADS];
78static int NumWinThreads = 0;
79static volatile GLboolean ExitFlag = GL_FALSE;
80
81static GLboolean MultiDisplays = 0;
82static GLboolean Locking = 0;
83static GLboolean Texture = GL_FALSE;
84static GLuint TexObj = 12;
85static GLboolean Animate = GL_TRUE;
86
87static pthread_mutex_t Mutex;
88static pthread_cond_t CondVar;
89static pthread_mutex_t CondMutex;
90
91
92static void
93Error(const char *msg)
94{
95   fprintf(stderr, "Error: %s\n", msg);
96   exit(1);
97}
98
99
100static void
101signal_redraw(void)
102{
103   pthread_mutex_lock(&CondMutex);
104   pthread_cond_broadcast(&CondVar);
105   pthread_mutex_unlock(&CondMutex);
106}
107
108
109static void
110MakeNewTexture(struct winthread *wt)
111{
112#define TEX_SIZE 128
113   static float step = 0.0;
114   GLfloat image[TEX_SIZE][TEX_SIZE][4];
115   GLint width;
116   int i, j;
117
118   for (j = 0; j < TEX_SIZE; j++) {
119      for (i = 0; i < TEX_SIZE; i++) {
120         float dt = 5.0 * (j - 0.5 * TEX_SIZE) / TEX_SIZE;
121         float ds = 5.0 * (i - 0.5 * TEX_SIZE) / TEX_SIZE;
122         float r = dt * dt + ds * ds + step;
123         image[j][i][0] =
124         image[j][i][1] =
125         image[j][i][2] = 0.75 + 0.25 * cos(r);
126         image[j][i][3] = 1.0;
127      }
128   }
129
130   step += 0.5;
131
132   glBindTexture(GL_TEXTURE_2D, TexObj);
133
134   glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
135   if (width) {
136      assert(width == TEX_SIZE);
137      /* sub-tex replace */
138      glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, TEX_SIZE, TEX_SIZE,
139                   GL_RGBA, GL_FLOAT, image);
140   }
141   else {
142      /* create new */
143      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
144      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
145
146      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TEX_SIZE, TEX_SIZE, 0,
147                   GL_RGBA, GL_FLOAT, image);
148   }
149}
150
151
152
153/* draw a colored cube */
154static void
155draw_object(void)
156{
157   glPushMatrix();
158   glScalef(0.75, 0.75, 0.75);
159
160   glColor3f(1, 0, 0);
161
162   if (Texture) {
163      glBindTexture(GL_TEXTURE_2D, TexObj);
164      glEnable(GL_TEXTURE_2D);
165   }
166   else {
167      glDisable(GL_TEXTURE_2D);
168   }
169
170   glBegin(GL_QUADS);
171
172   /* -X */
173   glColor3f(0, 1, 1);
174   glTexCoord2f(0, 0);  glVertex3f(-1, -1, -1);
175   glTexCoord2f(1, 0);  glVertex3f(-1,  1, -1);
176   glTexCoord2f(1, 1);  glVertex3f(-1,  1,  1);
177   glTexCoord2f(0, 1);  glVertex3f(-1, -1,  1);
178
179   /* +X */
180   glColor3f(1, 0, 0);
181   glTexCoord2f(0, 0);  glVertex3f(1, -1, -1);
182   glTexCoord2f(1, 0);  glVertex3f(1,  1, -1);
183   glTexCoord2f(1, 1);  glVertex3f(1,  1,  1);
184   glTexCoord2f(0, 1);  glVertex3f(1, -1,  1);
185
186   /* -Y */
187   glColor3f(1, 0, 1);
188   glTexCoord2f(0, 0);  glVertex3f(-1, -1, -1);
189   glTexCoord2f(1, 0);  glVertex3f( 1, -1, -1);
190   glTexCoord2f(1, 1);  glVertex3f( 1, -1,  1);
191   glTexCoord2f(0, 1);  glVertex3f(-1, -1,  1);
192
193   /* +Y */
194   glColor3f(0, 1, 0);
195   glTexCoord2f(0, 0);  glVertex3f(-1, 1, -1);
196   glTexCoord2f(1, 0);  glVertex3f( 1, 1, -1);
197   glTexCoord2f(1, 1);  glVertex3f( 1, 1,  1);
198   glTexCoord2f(0, 1);  glVertex3f(-1, 1,  1);
199
200   /* -Z */
201   glColor3f(1, 1, 0);
202   glTexCoord2f(0, 0);  glVertex3f(-1, -1, -1);
203   glTexCoord2f(1, 0);  glVertex3f( 1, -1, -1);
204   glTexCoord2f(1, 1);  glVertex3f( 1,  1, -1);
205   glTexCoord2f(0, 1);  glVertex3f(-1,  1, -1);
206
207   /* +Y */
208   glColor3f(0, 0, 1);
209   glTexCoord2f(0, 0);  glVertex3f(-1, -1, 1);
210   glTexCoord2f(1, 0);  glVertex3f( 1, -1, 1);
211   glTexCoord2f(1, 1);  glVertex3f( 1,  1, 1);
212   glTexCoord2f(0, 1);  glVertex3f(-1,  1, 1);
213
214   glEnd();
215
216   glPopMatrix();
217}
218
219
220/* signal resize of given window */
221static void
222resize(struct winthread *wt, int w, int h)
223{
224   wt->NewSize = GL_TRUE;
225   wt->WinWidth = w;
226   wt->WinHeight = h;
227   if (!Animate)
228      signal_redraw();
229}
230
231
232/*
233 * We have an instance of this for each thread.
234 */
235static void
236draw_loop(struct winthread *wt)
237{
238   while (!ExitFlag) {
239
240      if (Locking)
241         pthread_mutex_lock(&Mutex);
242
243      glXMakeCurrent(wt->Dpy, wt->Win, wt->Context);
244      if (!wt->Initialized) {
245         printf("glthreads: %d: GL_RENDERER = %s\n", wt->Index,
246                (char *) glGetString(GL_RENDERER));
247         if (Texture /*&& wt->Index == 0*/) {
248            MakeNewTexture(wt);
249         }
250         wt->Initialized = GL_TRUE;
251      }
252
253      if (Locking)
254         pthread_mutex_unlock(&Mutex);
255
256      glEnable(GL_DEPTH_TEST);
257
258      if (wt->NewSize) {
259         GLfloat w = (float) wt->WinWidth / (float) wt->WinHeight;
260         glViewport(0, 0, wt->WinWidth, wt->WinHeight);
261         glMatrixMode(GL_PROJECTION);
262         glLoadIdentity();
263         glFrustum(-w, w, -1.0, 1.0, 1.5, 10);
264         glMatrixMode(GL_MODELVIEW);
265         glLoadIdentity();
266         glTranslatef(0, 0, -2.5);
267         wt->NewSize = GL_FALSE;
268      }
269
270      if (wt->MakeNewTexture) {
271         MakeNewTexture(wt);
272         wt->MakeNewTexture = GL_FALSE;
273      }
274
275      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
276
277      glPushMatrix();
278         glRotatef(wt->Angle, 0, 1, 0);
279         glRotatef(wt->Angle, 1, 0, 0);
280         glScalef(0.7, 0.7, 0.7);
281         draw_object();
282      glPopMatrix();
283
284      if (Locking)
285         pthread_mutex_lock(&Mutex);
286
287      glXSwapBuffers(wt->Dpy, wt->Win);
288
289      if (Locking)
290         pthread_mutex_unlock(&Mutex);
291
292      if (Animate) {
293         usleep(5000);
294      }
295      else {
296         /* wait for signal to draw */
297         pthread_mutex_lock(&CondMutex);
298         pthread_cond_wait(&CondVar, &CondMutex);
299         pthread_mutex_unlock(&CondMutex);
300      }
301      wt->Angle += 1.0;
302   }
303}
304
305
306static void
307keypress(XEvent *event, struct winthread *wt)
308{
309   char buf[100];
310   KeySym keySym;
311   XComposeStatus stat;
312
313   XLookupString(&event->xkey, buf, sizeof(buf), &keySym, &stat);
314
315   switch (keySym) {
316   case XK_Escape:
317      /* tell all threads to exit */
318      if (!Animate) {
319         signal_redraw();
320      }
321      ExitFlag = GL_TRUE;
322      /*printf("exit draw_loop %d\n", wt->Index);*/
323      return;
324   case XK_t:
325   case XK_T:
326      if (Texture) {
327         wt->MakeNewTexture = GL_TRUE;
328         if (!Animate)
329            signal_redraw();
330      }
331      break;
332   case XK_a:
333   case XK_A:
334      Animate = !Animate;
335      if (Animate)  /* yes, prev Animate state! */
336         signal_redraw();
337      break;
338   case XK_s:
339   case XK_S:
340      if (!Animate)
341         signal_redraw();
342      break;
343   default:
344      ; /* nop */
345   }
346}
347
348
349/*
350 * The main process thread runs this loop.
351 * Single display connection for all threads.
352 */
353static void
354event_loop(Display *dpy)
355{
356   XEvent event;
357   int i;
358
359   assert(!MultiDisplays);
360
361   while (!ExitFlag) {
362
363      if (Locking) {
364         while (1) {
365            int k;
366            pthread_mutex_lock(&Mutex);
367            k = XPending(dpy);
368            if (k) {
369               XNextEvent(dpy, &event);
370               pthread_mutex_unlock(&Mutex);
371               break;
372            }
373            pthread_mutex_unlock(&Mutex);
374            usleep(5000);
375         }
376      }
377      else {
378         XNextEvent(dpy, &event);
379      }
380
381      switch (event.type) {
382         case ConfigureNotify:
383            /* Find winthread for this event's window */
384            for (i = 0; i < NumWinThreads; i++) {
385               struct winthread *wt = &WinThreads[i];
386               if (event.xconfigure.window == wt->Win) {
387                  resize(wt, event.xconfigure.width,
388                         event.xconfigure.height);
389                  break;
390               }
391            }
392            break;
393         case KeyPress:
394            for (i = 0; i < NumWinThreads; i++) {
395               struct winthread *wt = &WinThreads[i];
396               if (event.xkey.window == wt->Win) {
397                  keypress(&event, wt);
398                  break;
399               }
400            }
401            break;
402         default:
403            /*no-op*/ ;
404      }
405   }
406}
407
408
409/*
410 * Separate display connection for each thread.
411 */
412static void
413event_loop_multi(void)
414{
415   XEvent event;
416   int w = 0;
417
418   assert(MultiDisplays);
419
420   while (!ExitFlag) {
421      struct winthread *wt = &WinThreads[w];
422      if (XPending(wt->Dpy)) {
423         XNextEvent(wt->Dpy, &event);
424         switch (event.type) {
425         case ConfigureNotify:
426            resize(wt, event.xconfigure.width, event.xconfigure.height);
427            break;
428         case KeyPress:
429            keypress(&event, wt);
430            break;
431         default:
432            ; /* nop */
433         }
434      }
435      w = (w + 1) % NumWinThreads;
436      usleep(5000);
437   }
438}
439
440
441
442/*
443 * we'll call this once for each thread, before the threads are created.
444 */
445static void
446create_window(struct winthread *wt, GLXContext shareCtx)
447{
448   Window win;
449   GLXContext ctx;
450   int attrib[] = { GLX_RGBA,
451		    GLX_RED_SIZE, 1,
452		    GLX_GREEN_SIZE, 1,
453		    GLX_BLUE_SIZE, 1,
454                    GLX_DEPTH_SIZE, 1,
455		    GLX_DOUBLEBUFFER,
456		    None };
457   int scrnum;
458   XSetWindowAttributes attr;
459   unsigned long mask;
460   Window root;
461   XVisualInfo *visinfo;
462   int width = 160, height = 160;
463   int xpos = (wt->Index % 8) * (width + 10);
464   int ypos = (wt->Index / 8) * (width + 20);
465
466   scrnum = DefaultScreen(wt->Dpy);
467   root = RootWindow(wt->Dpy, scrnum);
468
469   visinfo = glXChooseVisual(wt->Dpy, scrnum, attrib);
470   if (!visinfo) {
471      Error("Unable to find RGB, Z, double-buffered visual");
472   }
473
474   /* window attributes */
475   attr.background_pixel = 0;
476   attr.border_pixel = 0;
477   attr.colormap = XCreateColormap(wt->Dpy, root, visinfo->visual, AllocNone);
478   attr.event_mask = StructureNotifyMask | ExposureMask | KeyPressMask;
479   mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask;
480
481   win = XCreateWindow(wt->Dpy, root, xpos, ypos, width, height,
482		        0, visinfo->depth, InputOutput,
483		        visinfo->visual, mask, &attr);
484   if (!win) {
485      Error("Couldn't create window");
486   }
487
488   {
489      XSizeHints sizehints;
490      sizehints.x = xpos;
491      sizehints.y = ypos;
492      sizehints.width  = width;
493      sizehints.height = height;
494      sizehints.flags = USSize | USPosition;
495      XSetNormalHints(wt->Dpy, win, &sizehints);
496      XSetStandardProperties(wt->Dpy, win, "glthreads", "glthreads",
497                              None, (char **)NULL, 0, &sizehints);
498   }
499
500
501   ctx = glXCreateContext(wt->Dpy, visinfo, shareCtx, True);
502   if (!ctx) {
503      Error("Couldn't create GLX context");
504   }
505
506   XMapWindow(wt->Dpy, win);
507   XSync(wt->Dpy, 0);
508
509   /* save the info for this window/context */
510   wt->Win = win;
511   wt->Context = ctx;
512   wt->Angle = 0.0;
513   wt->WinWidth = width;
514   wt->WinHeight = height;
515   wt->NewSize = GL_TRUE;
516}
517
518
519/*
520 * Called by pthread_create()
521 */
522static void *
523thread_function(void *p)
524{
525   struct winthread *wt = (struct winthread *) p;
526   draw_loop(wt);
527   return NULL;
528}
529
530
531/*
532 * called before exit to wait for all threads to finish
533 */
534static void
535clean_up(void)
536{
537   int i;
538
539   /* wait for threads to finish */
540   for (i = 0; i < NumWinThreads; i++) {
541      pthread_join(WinThreads[i].Thread, NULL);
542   }
543
544   for (i = 0; i < NumWinThreads; i++) {
545      glXDestroyContext(WinThreads[i].Dpy, WinThreads[i].Context);
546      XDestroyWindow(WinThreads[i].Dpy, WinThreads[i].Win);
547   }
548}
549
550
551static void
552usage(void)
553{
554   printf("glthreads: test of GL thread safety (any key = exit)\n");
555   printf("Usage:\n");
556   printf("  glthreads [options]\n");
557   printf("Options:\n");
558   printf("   -display DISPLAYNAME  Specify display string\n");
559   printf("   -n NUMTHREADS  Number of threads to create\n");
560   printf("   -p  Use a separate display connection for each thread\n");
561   printf("   -l  Use application-side locking\n");
562   printf("   -t  Enable texturing\n");
563   printf("Keyboard:\n");
564   printf("   Esc  Exit\n");
565   printf("   t    Change texture image (requires -t option)\n");
566   printf("   a    Toggle animation\n");
567   printf("   s    Step rotation (when not animating)\n");
568}
569
570
571int
572main(int argc, char *argv[])
573{
574   char *displayName = NULL;
575   int numThreads = 2;
576   Display *dpy = NULL;
577   int i;
578   Status threadStat;
579
580   if (argc == 1) {
581      usage();
582   }
583   else {
584      int i;
585      for (i = 1; i < argc; i++) {
586         if (strcmp(argv[i], "-display") == 0 && i + 1 < argc) {
587            displayName = argv[i + 1];
588            i++;
589         }
590         else if (strcmp(argv[i], "-p") == 0) {
591            MultiDisplays = 1;
592         }
593         else if (strcmp(argv[i], "-l") == 0) {
594            Locking = 1;
595         }
596         else if (strcmp(argv[i], "-t") == 0) {
597            Texture = 1;
598         }
599         else if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) {
600            numThreads = atoi(argv[i + 1]);
601            if (numThreads < 1)
602               numThreads = 1;
603            else if (numThreads > MAX_WINTHREADS)
604               numThreads = MAX_WINTHREADS;
605            i++;
606         }
607         else {
608            usage();
609            exit(1);
610         }
611      }
612   }
613
614   if (Locking)
615      printf("glthreads: Using explicit locks around Xlib calls.\n");
616   else
617      printf("glthreads: No explict locking.\n");
618
619   if (MultiDisplays)
620      printf("glthreads: Per-thread display connections.\n");
621   else
622      printf("glthreads: Single display connection.\n");
623
624   /*
625    * VERY IMPORTANT: call XInitThreads() before any other Xlib functions.
626    */
627   if (!MultiDisplays) {
628      if (!Locking) {
629         threadStat = XInitThreads();
630      if (threadStat) {
631         printf("XInitThreads() returned %d (success)\n", (int) threadStat);
632      }
633      else {
634         printf("XInitThreads() returned 0 (failure- this program may fail)\n");
635      }
636      }
637
638      dpy = XOpenDisplay(displayName);
639      if (!dpy) {
640         fprintf(stderr, "Unable to open display %s\n", XDisplayName(displayName));
641         return -1;
642      }
643   }
644
645   pthread_mutex_init(&Mutex, NULL);
646   pthread_mutex_init(&CondMutex, NULL);
647   pthread_cond_init(&CondVar, NULL);
648
649   printf("glthreads: creating windows\n");
650
651   NumWinThreads = numThreads;
652
653   /* Create the GLX windows and contexts */
654   for (i = 0; i < numThreads; i++) {
655      GLXContext share;
656
657      if (MultiDisplays) {
658         WinThreads[i].Dpy = XOpenDisplay(displayName);
659         assert(WinThreads[i].Dpy);
660      }
661      else {
662         WinThreads[i].Dpy = dpy;
663      }
664      WinThreads[i].Index = i;
665      WinThreads[i].Initialized = GL_FALSE;
666
667      share = (Texture && i > 0) ? WinThreads[0].Context : 0;
668
669      create_window(&WinThreads[i], share);
670   }
671
672   printf("glthreads: creating threads\n");
673
674   /* Create the threads */
675   for (i = 0; i < numThreads; i++) {
676      pthread_create(&WinThreads[i].Thread, NULL, thread_function,
677                     (void*) &WinThreads[i]);
678      printf("glthreads: Created thread %p\n", (void *) WinThreads[i].Thread);
679   }
680
681   if (MultiDisplays)
682      event_loop_multi();
683   else
684      event_loop(dpy);
685
686   clean_up();
687
688   if (MultiDisplays) {
689      for (i = 0; i < numThreads; i++) {
690         XCloseDisplay(WinThreads[i].Dpy);
691      }
692   }
693   else {
694      XCloseDisplay(dpy);
695   }
696
697   return 0;
698}
699