Merge branch '7.8'
[mesa.git] / progs / egl / opengl / xeglthreads.c
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/gl.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 */
68 struct 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
85 static struct winthread WinThreads[MAX_WINTHREADS];
86 static int NumWinThreads = 0;
87 static volatile GLboolean ExitFlag = GL_FALSE;
88
89 static GLboolean MultiDisplays = 0;
90 static GLboolean Locking = 0;
91 static GLboolean Texture = GL_FALSE;
92 static GLuint TexObj = 12;
93 static GLboolean Animate = GL_TRUE;
94
95 static pthread_mutex_t Mutex;
96 static pthread_cond_t CondVar;
97 static pthread_mutex_t CondMutex;
98
99
100 static void
101 Error(const char *msg)
102 {
103 fprintf(stderr, "Error: %s\n", msg);
104 exit(1);
105 }
106
107
108 static void
109 signal_redraw(void)
110 {
111 pthread_mutex_lock(&CondMutex);
112 pthread_cond_broadcast(&CondVar);
113 pthread_mutex_unlock(&CondMutex);
114 }
115
116
117 static void
118 MakeNewTexture(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 */
162 static void
163 draw_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 */
229 static void
230 resize(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 */
243 static void
244 draw_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
321 static void
322 keypress(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 */
368 static void
369 event_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 */
427 static void
428 event_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 */
460 static void
461 create_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 */
563 static void *
564 thread_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 */
575 static void
576 clean_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
592 static void
593 usage(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
612 int
613 main(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
767 int
768 main(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 */