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 * Port to windows done by Michal Krol.
23 */
24
25
26/*
27 * This program tests WGL thread safety.
28 * Command line options:
29 *  -h                       Print usage
30 *  -l                       Enable application-side locking
31 *  -n <num threads>         Number of threads to create (default is 2)
32 *  -t                       Use texture mapping
33 *  -s                       Force single-threaded.
34 *
35 * Brian Paul  20 July 2000
36 */
37
38
39/*
40 * Notes:
41 * - Each thread gets its own WGL context.
42 *
43 * - The WGL contexts share texture objects.
44 *
45 * - When 't' is pressed to update the texture image, the window/thread which
46 *   has input focus is signalled to change the texture.  The other threads
47 *   should see the updated texture the next time they call glBindTexture.
48 */
49
50
51#include <assert.h>
52#include <windows.h>
53#include <GL/gl.h>
54#include <math.h>
55#include <stdio.h>
56#include <stdlib.h>
57#include <string.h>
58
59
60/*
61 * Each window/thread/context:
62 */
63struct winthread {
64   int Index;
65   HANDLE Thread;
66   HWND Win;
67   HDC hDC;
68   HGLRC Context;
69   float Angle;
70   int WinWidth, WinHeight;
71   GLboolean NewSize;
72   HANDLE hEventInitialised;
73   GLboolean Initialized;
74   GLboolean MakeNewTexture;
75   HANDLE hEventRedraw;
76};
77
78
79#define MAX_WINTHREADS 128
80static struct winthread WinThreads[MAX_WINTHREADS];
81static int NumWinThreads = 2;
82static HANDLE ExitEvent = NULL;
83
84static GLboolean Locking = 0;
85static GLboolean Texture = GL_FALSE;
86static GLboolean SingleThreaded = GL_FALSE;
87static GLuint TexObj = 12;
88static GLboolean Animate = GL_TRUE;
89
90static CRITICAL_SECTION Mutex;
91
92
93static void
94Error(const char *msg)
95{
96   fprintf(stderr, "Error: %s\n", msg);
97   exit(1);
98}
99
100
101static void
102signal_redraw(void)
103{
104   int i;
105
106   for (i = 0; i < NumWinThreads; i++) {
107      SetEvent(WinThreads[i].hEventRedraw);
108   }
109}
110
111
112static void
113MakeNewTexture(struct winthread *wt)
114{
115#define TEX_SIZE 128
116   static float step = 0.0f;
117   GLfloat image[TEX_SIZE][TEX_SIZE][4];
118   GLint width;
119   int i, j;
120
121   for (j = 0; j < TEX_SIZE; j++) {
122      for (i = 0; i < TEX_SIZE; i++) {
123         float dt = 5.0f * (j - 0.5f * TEX_SIZE) / TEX_SIZE;
124         float ds = 5.0f * (i - 0.5f * TEX_SIZE) / TEX_SIZE;
125         float r = dt * dt + ds * ds + step;
126         image[j][i][0] =
127         image[j][i][1] =
128         image[j][i][2] = 0.75f + 0.25f * (float) cos(r);
129         image[j][i][3] = 1.0f;
130      }
131   }
132
133   step += 0.5;
134
135   glBindTexture(GL_TEXTURE_2D, TexObj);
136
137   glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
138   if (width) {
139      assert(width == TEX_SIZE);
140      /* sub-tex replace */
141      glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, TEX_SIZE, TEX_SIZE,
142                   GL_RGBA, GL_FLOAT, image);
143   }
144   else {
145      /* create new */
146      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
147      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
148
149      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TEX_SIZE, TEX_SIZE, 0,
150                   GL_RGBA, GL_FLOAT, image);
151   }
152}
153
154
155
156/* draw a colored cube */
157static void
158draw_object(void)
159{
160   glPushMatrix();
161   glScalef(0.75f, 0.75f, 0.75f);
162
163   glColor3f(1, 0, 0);
164
165   if (Texture) {
166      glBindTexture(GL_TEXTURE_2D, TexObj);
167      glEnable(GL_TEXTURE_2D);
168   }
169   else {
170      glDisable(GL_TEXTURE_2D);
171   }
172
173   glBegin(GL_QUADS);
174
175   /* -X */
176   glColor3f(0, 1, 1);
177   glTexCoord2f(0, 0);  glVertex3f(-1, -1, -1);
178   glTexCoord2f(1, 0);  glVertex3f(-1,  1, -1);
179   glTexCoord2f(1, 1);  glVertex3f(-1,  1,  1);
180   glTexCoord2f(0, 1);  glVertex3f(-1, -1,  1);
181
182   /* +X */
183   glColor3f(1, 0, 0);
184   glTexCoord2f(0, 0);  glVertex3f(1, -1, -1);
185   glTexCoord2f(1, 0);  glVertex3f(1,  1, -1);
186   glTexCoord2f(1, 1);  glVertex3f(1,  1,  1);
187   glTexCoord2f(0, 1);  glVertex3f(1, -1,  1);
188
189   /* -Y */
190   glColor3f(1, 0, 1);
191   glTexCoord2f(0, 0);  glVertex3f(-1, -1, -1);
192   glTexCoord2f(1, 0);  glVertex3f( 1, -1, -1);
193   glTexCoord2f(1, 1);  glVertex3f( 1, -1,  1);
194   glTexCoord2f(0, 1);  glVertex3f(-1, -1,  1);
195
196   /* +Y */
197   glColor3f(0, 1, 0);
198   glTexCoord2f(0, 0);  glVertex3f(-1, 1, -1);
199   glTexCoord2f(1, 0);  glVertex3f( 1, 1, -1);
200   glTexCoord2f(1, 1);  glVertex3f( 1, 1,  1);
201   glTexCoord2f(0, 1);  glVertex3f(-1, 1,  1);
202
203   /* -Z */
204   glColor3f(1, 1, 0);
205   glTexCoord2f(0, 0);  glVertex3f(-1, -1, -1);
206   glTexCoord2f(1, 0);  glVertex3f( 1, -1, -1);
207   glTexCoord2f(1, 1);  glVertex3f( 1,  1, -1);
208   glTexCoord2f(0, 1);  glVertex3f(-1,  1, -1);
209
210   /* +Y */
211   glColor3f(0, 0, 1);
212   glTexCoord2f(0, 0);  glVertex3f(-1, -1, 1);
213   glTexCoord2f(1, 0);  glVertex3f( 1, -1, 1);
214   glTexCoord2f(1, 1);  glVertex3f( 1,  1, 1);
215   glTexCoord2f(0, 1);  glVertex3f(-1,  1, 1);
216
217   glEnd();
218
219   glPopMatrix();
220}
221
222
223/* signal resize of given window */
224static void
225resize(struct winthread *wt, int w, int h)
226{
227   wt->NewSize = GL_TRUE;
228   wt->WinWidth = w;
229   wt->WinHeight = h;
230   if (!Animate)
231      SetEvent(wt->hEventRedraw);
232}
233
234
235/*
236 * We have an instance of this for each thread.
237 */
238static void
239draw_loop(struct winthread *wt)
240{
241   while (1) {
242      GLboolean draw = Animate;
243      MSG msg;
244
245      if (Animate) {
246         /* wait 5 ms for signal either to exit or process messages */
247         switch (MsgWaitForMultipleObjects(1, &ExitEvent, FALSE, 5, QS_ALLINPUT)) {
248         case WAIT_OBJECT_0:
249            SendMessage(wt->Win, WM_CLOSE, 0, 0);
250            break;
251         case WAIT_OBJECT_0 + 1:
252            break;
253         }
254      }
255      else {
256         HANDLE events[2];
257
258         events[0] = wt->hEventRedraw;
259         events[1] = ExitEvent;
260
261         /* wait for signal either to draw, exit or process messages */
262         switch (MsgWaitForMultipleObjects(2, events, FALSE, INFINITE, QS_ALLINPUT)) {
263         case WAIT_OBJECT_0:
264            draw = GL_TRUE;
265            break;
266         case WAIT_OBJECT_0 + 1:
267            SendMessage(wt->Win, WM_CLOSE, 0, 0);
268            break;
269         case WAIT_OBJECT_0 + 2:
270            break;
271         }
272      }
273
274      while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
275         if (msg.message == WM_QUIT) {
276            return;
277         }
278         TranslateMessage(&msg);
279         DispatchMessage(&msg);
280      }
281
282      if (!draw)
283         continue;
284
285      if (Locking)
286         EnterCriticalSection(&Mutex);
287
288      wglMakeCurrent(wt->hDC, wt->Context);
289
290      if (!wt->Initialized) {
291         printf("wglthreads: %d: GL_RENDERER = %s\n", wt->Index,
292                (char *) glGetString(GL_RENDERER));
293         if (Texture /*&& wt->Index == 0*/) {
294            MakeNewTexture(wt);
295         }
296         wt->Initialized = GL_TRUE;
297      }
298
299      if (Locking)
300         LeaveCriticalSection(&Mutex);
301
302      glEnable(GL_DEPTH_TEST);
303
304      if (wt->NewSize) {
305         GLfloat w = (float) wt->WinWidth / (float) wt->WinHeight;
306         glViewport(0, 0, wt->WinWidth, wt->WinHeight);
307         glMatrixMode(GL_PROJECTION);
308         glLoadIdentity();
309         glFrustum(-w, w, -1.0, 1.0, 1.5, 10);
310         glMatrixMode(GL_MODELVIEW);
311         glLoadIdentity();
312         glTranslatef(0, 0, -2.5);
313         wt->NewSize = GL_FALSE;
314      }
315
316      if (wt->MakeNewTexture) {
317         MakeNewTexture(wt);
318         wt->MakeNewTexture = GL_FALSE;
319      }
320
321      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
322
323      glPushMatrix();
324         glRotatef(wt->Angle, 0, 1, 0);
325         glRotatef(wt->Angle, 1, 0, 0);
326         glScalef(0.7f, 0.7f, 0.7f);
327         draw_object();
328      glPopMatrix();
329
330      if (Locking)
331         EnterCriticalSection(&Mutex);
332
333      SwapBuffers(wt->hDC);
334
335      if (Locking)
336         LeaveCriticalSection(&Mutex);
337
338      wt->Angle += 1.0;
339   }
340}
341
342
343static void
344keypress(WPARAM keySym, struct winthread *wt)
345{
346   switch (keySym) {
347   case VK_ESCAPE:
348      /* tell all threads to exit */
349      SetEvent(ExitEvent);
350      /*printf("exit draw_loop %d\n", wt->Index);*/
351      return;
352   case 't':
353   case 'T':
354      if (Texture) {
355         wt->MakeNewTexture = GL_TRUE;
356         if (!Animate)
357            signal_redraw();
358      }
359      break;
360   case 'a':
361   case 'A':
362      Animate = !Animate;
363      if (Animate)
364         signal_redraw();
365      break;
366   case 's':
367   case 'S':
368      if (!Animate)
369         signal_redraw();
370      break;
371   default:
372      ; /* nop */
373   }
374}
375
376
377static LRESULT CALLBACK
378WndProc(HWND hWnd,
379        UINT uMsg,
380        WPARAM wParam,
381        LPARAM lParam )
382{
383   struct winthread *wt = (struct winthread *)(INT_PTR)GetWindowLongPtr(hWnd, GWLP_USERDATA);
384
385   switch (uMsg) {
386   case WM_KEYDOWN:
387      keypress(wParam, wt);
388      break;
389   case WM_SIZE:
390      {
391         RECT r;
392         GetClientRect(hWnd, &r);
393         resize(wt, r.right, r.bottom);
394      }
395      break;
396   case WM_DESTROY:
397      PostQuitMessage(0);
398      break;
399   default:
400      return DefWindowProc(hWnd, uMsg, wParam, lParam);
401   }
402
403   return 0;
404}
405
406/*
407 * we'll call this once for each thread, before the threads are created.
408 */
409static void
410create_window(struct winthread *wt, HGLRC shareCtx)
411{
412   WNDCLASS wc = {0};
413   int width = 160, height = 160;
414   int xpos = (wt->Index % 8) * (width + 10);
415   int ypos = (wt->Index / 8) * (width + 20);
416   HWND win;
417   HDC hdc;
418   PIXELFORMATDESCRIPTOR pfd;
419   int visinfo;
420   HGLRC ctx;
421
422   wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
423   wc.hCursor = LoadCursor(NULL, IDC_ARROW);
424   wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
425   wc.lpfnWndProc = WndProc;
426   wc.lpszClassName = "wglthreads";
427   wc.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
428   RegisterClass(&wc);
429
430   win = CreateWindowEx(0,
431                        wc.lpszClassName,
432                        "wglthreads",
433                        WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_TILEDWINDOW,
434                        xpos,
435                        ypos,
436                        width,
437                        height,
438                        NULL,
439                        NULL,
440                        wc.hInstance,
441                        (LPVOID) wt);
442   if (!win) {
443      Error("Couldn't create window");
444   }
445
446   SetWindowLongPtr(win, GWLP_USERDATA, (LONG_PTR)wt);
447
448   hdc = GetDC(win);
449   if (!hdc) {
450      Error("Couldn't obtain HDC");
451   }
452
453   memset(&pfd, 0, sizeof(pfd));
454   pfd.cColorBits = 24;
455   pfd.cDepthBits = 24;
456   pfd.dwFlags = PFD_DOUBLEBUFFER | PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
457   pfd.iLayerType = PFD_MAIN_PLANE;
458   pfd.iPixelType = PFD_TYPE_RGBA;
459   pfd.nSize = sizeof(pfd);
460   pfd.nVersion = 1;
461
462   visinfo = ChoosePixelFormat(hdc, &pfd);
463   if (!visinfo) {
464      Error("Unable to find RGB, Z, double-buffered visual");
465   }
466
467   SetPixelFormat(hdc, visinfo, &pfd);
468   ctx = wglCreateContext(hdc);
469   if (!ctx) {
470      Error("Couldn't create WGL context");
471   }
472
473   if (shareCtx) {
474      if(!wglShareLists(shareCtx, ctx))
475         Error("Couldn't share WGL context lists");
476   }
477
478   /* save the info for this window/context */
479   wt->Win = win;
480   wt->hDC = hdc;
481   wt->Context = ctx;
482   wt->Angle = 0.0;
483   wt->WinWidth = width;
484   wt->WinHeight = height;
485   wt->NewSize = GL_TRUE;
486}
487
488
489/*
490 * Called by pthread_create()
491 */
492static DWORD WINAPI
493ThreadProc(void *p)
494{
495   struct winthread *wt = (struct winthread *) p;
496   HGLRC share;
497
498   /* Wait for the previous thread */
499   if(Texture && wt->Index > 0) {
500      WaitForSingleObject(WinThreads[wt->Index - 1].hEventInitialised, INFINITE);
501      share = WinThreads[0].Context;
502   }
503   else
504      share = 0;
505
506   share = (Texture && wt->Index > 0) ? WinThreads[0].Context : 0;
507   create_window(wt, share);
508   SetEvent(wt->hEventInitialised);
509
510   /* Wait for all threads to initialize otherwise wglShareLists will fail */
511   if(wt->Index < NumWinThreads - 1)
512      WaitForSingleObject(WinThreads[NumWinThreads - 1].hEventInitialised, INFINITE);
513
514   draw_loop(wt);
515   return 0;
516}
517
518
519static void
520usage(void)
521{
522   printf("wglthreads: test of GL thread safety (any key = exit)\n");
523   printf("Usage:\n");
524   printf("  wglthreads [options]\n");
525   printf("Options:\n");
526   printf("   -h  Show this usage screen\n");
527   printf("   -n NUMTHREADS  Number of threads to create\n");
528   printf("   -l  Use application-side locking\n");
529   printf("   -t  Enable texturing\n");
530   printf("   -s  Force single-threaded\n");
531   printf("Keyboard:\n");
532   printf("   Esc  Exit\n");
533   printf("   t    Change texture image (requires -t option)\n");
534   printf("   a    Toggle animation\n");
535   printf("   s    Step rotation (when not animating)\n");
536}
537
538
539int
540main(int argc, char *argv[])
541{
542   int i;
543
544   for (i = 1; i < argc; i++) {
545      if (strcmp(argv[i], "-h") == 0) {
546         usage();
547         exit(0);
548      }
549      else if (strcmp(argv[i], "-l") == 0) {
550         Locking = 1;
551      }
552      else if (strcmp(argv[i], "-t") == 0) {
553         Texture = 1;
554      }
555      else if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) {
556         NumWinThreads = atoi(argv[i + 1]);
557         if (NumWinThreads < 1)
558            NumWinThreads = 1;
559         else if (NumWinThreads > MAX_WINTHREADS)
560            NumWinThreads = MAX_WINTHREADS;
561         i++;
562      }
563      else if (strcmp(argv[i], "-s") == 0) {
564         SingleThreaded = GL_TRUE;
565      }
566      else {
567         usage();
568         exit(1);
569      }
570   }
571
572   if (SingleThreaded)
573      printf("wglthreads: Forcing single-threaded, no other threads will be created.\n");
574
575   if (Locking)
576      printf("wglthreads: Using explicit locks around WGL calls.\n");
577   else
578      printf("wglthreads: No explict locking.\n");
579
580   InitializeCriticalSection(&Mutex);
581   ExitEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
582
583   if (SingleThreaded) {
584      NumWinThreads = 1;
585
586      WinThreads[0].Index = 0;
587      WinThreads[0].hEventInitialised = CreateEvent(NULL, TRUE, FALSE, NULL);
588      WinThreads[0].hEventRedraw = CreateEvent(NULL, FALSE, FALSE, NULL);
589
590      ThreadProc((void*) &WinThreads[0]);
591   }
592   else {
593      HANDLE threads[MAX_WINTHREADS];
594
595      printf("wglthreads: creating threads\n");
596
597      /* Create the events */
598      for (i = 0; i < NumWinThreads; i++) {
599         WinThreads[i].Index = i;
600         WinThreads[i].hEventInitialised = CreateEvent(NULL, TRUE, FALSE, NULL);
601         WinThreads[i].hEventRedraw = CreateEvent(NULL, FALSE, FALSE, NULL);
602      }
603
604      /* Create the threads */
605      for (i = 0; i < NumWinThreads; i++) {
606         DWORD id;
607
608         WinThreads[i].Thread = CreateThread(NULL,
609                                             0,
610                                             ThreadProc,
611                                             (void*) &WinThreads[i],
612                                             0,
613                                             &id);
614         printf("wglthreads: Created thread %p\n", (void *) WinThreads[i].Thread);
615
616         threads[i] = WinThreads[i].Thread;
617      }
618
619      /* Wait for all threads to finish. */
620      WaitForMultipleObjects(NumWinThreads, threads, TRUE, INFINITE);
621   }
622
623   return 0;
624}
625