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