Merge branch 'mesa_7_5_branch'
[mesa.git] / progs / egl / 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_NONE };
471 EGLConfig config;
472 EGLint num_configs;
473 EGLint vid;
474 int scrnum;
475 XSetWindowAttributes attr;
476 unsigned long mask;
477 Window root;
478 XVisualInfo *visinfo, visTemplate;
479 int num_visuals;
480 int width = 160, height = 160;
481 int xpos = (wt->Index % 8) * (width + 10);
482 int ypos = (wt->Index / 8) * (width + 20);
483
484 scrnum = DefaultScreen(wt->Dpy);
485 root = RootWindow(wt->Dpy, scrnum);
486
487 if (!eglChooseConfig(wt->Display, attribs, &config, 1, &num_configs)) {
488 Error("Unable to choose an EGL config");
489 }
490
491 assert(config);
492 assert(num_configs > 0);
493
494 if (!eglGetConfigAttrib(wt->Display, config, EGL_NATIVE_VISUAL_ID, &vid)) {
495 Error("Unable to get visual id of EGL config\n");
496 }
497
498 visTemplate.visualid = vid;
499 visinfo = XGetVisualInfo(wt->Dpy, VisualIDMask,
500 &visTemplate, &num_visuals);
501 if (!visinfo) {
502 Error("Unable to find RGB, Z, double-buffered visual");
503 }
504
505 /* window attributes */
506 attr.background_pixel = 0;
507 attr.border_pixel = 0;
508 attr.colormap = XCreateColormap(wt->Dpy, root, visinfo->visual, AllocNone);
509 attr.event_mask = StructureNotifyMask | ExposureMask | KeyPressMask;
510 mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask;
511
512 win = XCreateWindow(wt->Dpy, root, xpos, ypos, width, height,
513 0, visinfo->depth, InputOutput,
514 visinfo->visual, mask, &attr);
515 if (!win) {
516 Error("Couldn't create window");
517 }
518
519 XFree(visinfo);
520
521 {
522 XSizeHints sizehints;
523 sizehints.x = xpos;
524 sizehints.y = ypos;
525 sizehints.width = width;
526 sizehints.height = height;
527 sizehints.flags = USSize | USPosition;
528 XSetNormalHints(wt->Dpy, win, &sizehints);
529 XSetStandardProperties(wt->Dpy, win, "xeglthreads", "xeglthreads",
530 None, (char **)NULL, 0, &sizehints);
531 }
532
533 eglBindAPI(EGL_OPENGL_API);
534
535 ctx = eglCreateContext(wt->Display, config, shareCtx, NULL);
536 if (!ctx) {
537 Error("Couldn't create EGL context");
538 }
539 surf = eglCreateWindowSurface(wt->Display, config, win, NULL);
540 if (!surf) {
541 Error("Couldn't create EGL surface");
542 }
543
544 XMapWindow(wt->Dpy, win);
545 XSync(wt->Dpy, 0);
546
547 /* save the info for this window/context */
548 wt->Win = win;
549 wt->Context = ctx;
550 wt->Surface = surf;
551 wt->Angle = 0.0;
552 wt->WinWidth = width;
553 wt->WinHeight = height;
554 wt->NewSize = GL_TRUE;
555 }
556
557
558 /*
559 * Called by pthread_create()
560 */
561 static void *
562 thread_function(void *p)
563 {
564 struct winthread *wt = (struct winthread *) p;
565 draw_loop(wt);
566 return NULL;
567 }
568
569
570 /*
571 * called before exit to wait for all threads to finish
572 */
573 static void
574 clean_up(void)
575 {
576 int i;
577
578 /* wait for threads to finish */
579 for (i = 0; i < NumWinThreads; i++) {
580 pthread_join(WinThreads[i].Thread, NULL);
581 }
582
583 for (i = 0; i < NumWinThreads; i++) {
584 eglDestroyContext(WinThreads[i].Display, WinThreads[i].Context);
585 XDestroyWindow(WinThreads[i].Dpy, WinThreads[i].Win);
586 }
587 }
588
589
590 static void
591 usage(void)
592 {
593 printf("xeglthreads: test of EGL/GL thread safety (any key = exit)\n");
594 printf("Usage:\n");
595 printf(" xeglthreads [options]\n");
596 printf("Options:\n");
597 printf(" -display DISPLAYNAME Specify display string\n");
598 printf(" -n NUMTHREADS Number of threads to create\n");
599 printf(" -p Use a separate display connection for each thread\n");
600 printf(" -l Use application-side locking\n");
601 printf(" -t Enable texturing\n");
602 printf("Keyboard:\n");
603 printf(" Esc Exit\n");
604 printf(" t Change texture image (requires -t option)\n");
605 printf(" a Toggle animation\n");
606 printf(" s Step rotation (when not animating)\n");
607 }
608
609
610 int
611 main(int argc, char *argv[])
612 {
613 char *displayName = NULL;
614 int numThreads = 2;
615 Display *dpy = NULL;
616 EGLDisplay *egl_dpy = NULL;
617 int i;
618 Status threadStat;
619
620 if (argc == 1) {
621 usage();
622 }
623 else {
624 int i;
625 for (i = 1; i < argc; i++) {
626 if (strcmp(argv[i], "-display") == 0 && i + 1 < argc) {
627 displayName = argv[i + 1];
628 i++;
629 }
630 else if (strcmp(argv[i], "-p") == 0) {
631 MultiDisplays = 1;
632 }
633 else if (strcmp(argv[i], "-l") == 0) {
634 Locking = 1;
635 }
636 else if (strcmp(argv[i], "-t") == 0) {
637 Texture = 1;
638 }
639 else if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) {
640 numThreads = atoi(argv[i + 1]);
641 if (numThreads < 1)
642 numThreads = 1;
643 else if (numThreads > MAX_WINTHREADS)
644 numThreads = MAX_WINTHREADS;
645 i++;
646 }
647 else {
648 usage();
649 exit(1);
650 }
651 }
652 }
653
654 if (Locking)
655 printf("xeglthreads: Using explicit locks around Xlib calls.\n");
656 else
657 printf("xeglthreads: No explict locking.\n");
658
659 if (MultiDisplays)
660 printf("xeglthreads: Per-thread display connections.\n");
661 else
662 printf("xeglthreads: Single display connection.\n");
663
664 /*
665 * VERY IMPORTANT: call XInitThreads() before any other Xlib functions.
666 */
667 if (!MultiDisplays) {
668 if (!Locking) {
669 threadStat = XInitThreads();
670 if (threadStat) {
671 printf("XInitThreads() returned %d (success)\n",
672 (int) threadStat);
673 }
674 else {
675 printf("XInitThreads() returned 0 "
676 "(failure- this program may fail)\n");
677 }
678 }
679
680 dpy = XOpenDisplay(displayName);
681 if (!dpy) {
682 fprintf(stderr, "Unable to open display %s\n",
683 XDisplayName(displayName));
684 return -1;
685 }
686 egl_dpy = eglGetDisplay(dpy);
687 if (!egl_dpy) {
688 fprintf(stderr, "Unable to get EGL display\n");
689 XCloseDisplay(dpy);
690 return -1;
691 }
692 if (!eglInitialize(egl_dpy, NULL, NULL)) {
693 fprintf(stderr, "Unable to initialize EGL display\n");
694 return -1;
695 }
696 }
697
698 pthread_mutex_init(&Mutex, NULL);
699 pthread_mutex_init(&CondMutex, NULL);
700 pthread_cond_init(&CondVar, NULL);
701
702 printf("xeglthreads: creating windows\n");
703
704 NumWinThreads = numThreads;
705
706 /* Create the EGL windows and contexts */
707 for (i = 0; i < numThreads; i++) {
708 EGLContext share;
709
710 if (MultiDisplays) {
711 WinThreads[i].Dpy = XOpenDisplay(displayName);
712 assert(WinThreads[i].Dpy);
713 WinThreads[i].Display = eglGetDisplay(WinThreads[i].Dpy);
714 assert(eglInitialize(WinThreads[i].Display, NULL, NULL));
715 }
716 else {
717 WinThreads[i].Dpy = dpy;
718 WinThreads[i].Display = egl_dpy;
719 }
720 WinThreads[i].Index = i;
721 WinThreads[i].Initialized = GL_FALSE;
722
723 share = (Texture && i > 0) ? WinThreads[0].Context : 0;
724
725 create_window(&WinThreads[i], share);
726 }
727
728 printf("xeglthreads: creating threads\n");
729
730 /* Create the threads */
731 for (i = 0; i < numThreads; i++) {
732 pthread_create(&WinThreads[i].Thread, NULL, thread_function,
733 (void*) &WinThreads[i]);
734 printf("xeglthreads: Created thread %p\n",
735 (void *) WinThreads[i].Thread);
736 }
737
738 if (MultiDisplays)
739 event_loop_multi();
740 else
741 event_loop(dpy);
742
743 clean_up();
744
745 if (MultiDisplays) {
746 for (i = 0; i < numThreads; i++) {
747 eglTerminate(WinThreads[i].Display);
748 XCloseDisplay(WinThreads[i].Dpy);
749 }
750 }
751 else {
752 eglTerminate(egl_dpy);
753 XCloseDisplay(dpy);
754 }
755
756 return 0;
757 }
758
759
760 #else /* PTHREADS */
761
762
763 #include <stdio.h>
764
765 int
766 main(int argc, char *argv[])
767 {
768 printf("Sorry, this program wasn't compiled with PTHREADS defined.\n");
769 return 0;
770 }
771
772
773 #endif /* PTHREADS */