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