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