1 /*
2  * Copyright (C) 1997-2005, R3vis Corporation.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
17  * USA, or visit http://www.gnu.org/copyleft/lgpl.html.
18  *
19  * Original Contributor:
20  *   Wes Bethel, R3vis Corporation, Marin County, California
21  * Additional Contributor(s):
22  *
23  * The OpenRM project is located at http://openrm.sourceforge.net/.
24  */
25 
26 /*
27  * $Id: rmthread.c,v 1.7 2005/02/19 16:41:34 wes Exp $
28  * Version: $Name: OpenRM-1-6-0-2-RC2 $
29  * $Revision: 1.7 $
30  * $Log: rmthread.c,v $
31  * Revision 1.7  2005/02/19 16:41:34  wes
32  * Distro sync and consolidation.
33  * Support for NO_PTHREADS build.
34  *
35  * Revision 1.6  2005/01/23 17:00:22  wes
36  * Copyright updated to 2005.
37  *
38  * Revision 1.5  2004/01/16 16:56:02  wes
39  *
40  * Rearranged calls within rendering routines so that:
41  * (1) the time synchronization for constant-rate rendering happens after
42  * the actual rendering, but right before SwapBuffers;
43  * (2) the user-assignable routines for grabbing frambuffer pixels are
44  * relocated to after the SwapBuffers - they were located before in
45  * the previous version.
46  *
47  * These changes are expected to have the following benefits:
48  * (1) frame sync is more stable when associated with SwapBuffers rather
49  * than having it placed immediately before when rendering starts;
50  * (2) We have removed an embedded glFinish() call; SwapBuffers always
51  * implies a glFlush(), and in some implementations, also implies
52  * a glFinish(). The oddball here in terms of execution behavior is
53  * software Mesa. The only detrimental impact will be on timing rendering
54  * as you must explicitly insert your own glFinish() to ensure that
55  * timings are accurate. We are looking at this for the next rev of OpenRM.
56  *
57  * Revision 1.4  2003/10/03 19:19:32  wes
58  * Use platform-independent interface to access the OpenGL context.
59  *
60  * Revision 1.3  2003/04/05 14:13:46  wes
61  * Renamed rmMutexDestroy to rmMutexDelete for API consistency.
62  *
63  * Revision 1.2  2003/02/02 02:07:16  wes
64  * Updated copyright to 2003.
65  *
66  * Revision 1.1.1.1  2003/01/28 02:15:23  wes
67  * Manual rebuild of rm150 repository.
68  *
69  * Revision 1.5  2003/01/16 22:21:17  wes
70  * Updated all source files to reflect new organization of header files:
71  * all header files formerly located in include/rmaux, include/rmi, include/rmv
72  * are now located in include/rm.
73  *
74  * Revision 1.4  2002/04/30 19:34:03  wes
75  * Updated copyright dates.
76  *
77  * Revision 1.3  2001/06/03 20:50:16  wes
78  * No significant differences.
79  *
80  * Revision 1.2  2001/03/31 17:12:39  wes
81  * v1.4.0-alpha-2 checkin.
82  *
83  * Revision 1.1  2000/12/03 22:33:24  wes
84  * Initial entry.
85  *
86  */
87 
88 /*
89  * this file contains wrappers for MP code that is based upon a
90  * threaded programming model.
91  *
92  * We use Posix threads for mutex'es and threads. Win32 requires
93  * an add-on library that provides Posix threads functionality. Such
94  * a library is available for free download from:
95  *  http://sources.redhat.com/pthreads-win32/
96  * See the OpenRM RELEASENOTES for more information about the
97  * installation and use of pthreads-win32.
98  */
99 
100 #include <rm/rm.h>
101 #include "rmprivat.h"
102 #include "rmmultis.h"
103 
104 #ifdef _NO_PTHREADS
105 #include "rmpthrd.h"
106 #endif
107 
108 /*
109  * ----------------------------------------------------
110  * @Name rmThreadCreate
111  @pstart
112  RMenum rmThreadCreate(RMthread *threadID,
113 	               void * (*threadFunc)(void *),
114 	               void *args)
115  @pend
116 
117  @astart
118  RMthread *threadID - a handle to an RMthread object (modified).
119  void * (*threadFunc)(void *) - a handle to a function that will be
120     executed by the new thread (input).
121  void *args - arguments to the threadFunc, cast to a void *.
122  @aend
123 
124  @dstart
125  Use this routine to create a new execution thread. rmThreadCreate
126  is a threads-abstraction layer that creates new execution threads
127  in both Unix and Win32 environments. The Unix version is built using
128  POSIX threads - for more information, see pthread_create(3). On
129  Win32, see the MSDN documentation for _beginthread().
130 
131  The new thread is detached, and begins immediate execution of the
132  code contained in the routine "threadFunc". Arguments to the threadFunc
133  may be passed through the parameter "args." Typically, args are
134  packaged into a struct, and the handle to the struct is cast to
135  a void *. The threadFunc then performs an inverse cast of the
136  void *args to a struct * to gain access to the arguments.
137 
138  rmThreadCreate only creates a detached thread. Any synchronization
139  must be performed by the application using the appropriate
140  constructs. RMmutex's can be used (both Win32 and Unix) to
141  implement synchronization. Alternatively, developers who have
142  specialized knowledge of OS-specific features (e.g., semaphores,
143  condition variables, etc) may use those constructs.
144 
145  Upon success, this routine will return RM_CHILL, and the RMthread
146  *threadID is modified to contain thread-specific identification
147  information. Upon failure, an error message is printed, and
148  a RM_WHACKED is returned.
149  @dend
150  * ----------------------------------------------------
151  */
152 RMenum
rmThreadCreate(RMthread * threadID,void * (* threadFunc)(void *),void * args)153 rmThreadCreate(RMthread *threadID,
154 	       void * (*threadFunc)(void *),
155 	       void *args)
156 {
157     int stat;
158     RMenum rval;
159     pthread_attr_t attr;
160 
161     pthread_attr_init(&attr);
162     pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
163     stat = pthread_create(threadID, &attr, threadFunc, (void *)args);
164 
165     if (stat != 0)
166     {
167 	rval = RM_WHACKED;
168 	perror("rmThreadCreate/pthread_create error:");
169     }
170     else
171 	rval = RM_CHILL;
172     return(rval);
173 }
174 
175 /*
176  * ----------------------------------------------------
177  * @Name rmThreadJoin
178  @pstart
179  RMenum rmThreadJoin(RMthread *threadID,
180 	             void **threadReturn)
181  @pend
182 
183  @astart
184  RMthread *threadID - a handle to an RMthread object (input).
185  void **threadReturn - a handle to a void * (modified).
186  @aend
187 
188  @dstart
189  "Thread joining" means "wait for the thread to finish." Use
190  rmThreadJoin to wait for completion of a thread. Upon
191  success, this routine returns RM_CHILL, indicating the
192  thread in question has completed. Upon failure, an error
193  message is printed, and RM_WHACKED is returned (Unix only).
194 
195  NOTE: this function is a no-op on Win32, as there is no equivalent
196  thread join routine in Win32. Developers must use explicit
197  synchronization mechanisms on Win32 to coordinate signaling
198  of completion between app and detached threads. The RMmutex
199  will work nicely for that purpose.
200  @dend
201  * ----------------------------------------------------
202  */
203 RMenum
rmThreadJoin(RMthread * threadID,void ** threadReturn)204 rmThreadJoin(RMthread *threadID,
205 	     void **threadReturn)
206 {
207     int stat;
208     RMenum rval;
209     stat = pthread_join(*threadID, threadReturn);
210     if (stat != 0)
211     {
212 	rval = RM_WHACKED;
213 	perror("rmThreadJoin/pthread_join error:");
214     }
215     else
216 	rval = RM_CHILL;
217     return(rval);
218 }
219 
220 /*
221  * ----------------------------------------------------
222  * @Name rmMutexNew
223  @pstart
224  RMmutex * rmMutexNew(RMenum initLockState)
225  @pend
226 
227  @astart
228  RMenum initLockState - an RMenum value (input) specifying the
229  initial state of the RMmutex returned to the caller.
230  @aend
231 
232  @dstart
233  Creates an initialized RMmutex object, and returns the handle of
234  the new RMmutex object to the caller upon success, or NULL upon
235  failure. The initial value of the RMmutex is set to the value
236  specified by the input parameter initLockState.
237 
238  Valid values for initLockState are RM_MUTEX_LOCK or RM_MUTEX_UNLOCK.
239 
240  The RMmutex object is a synchronization mechanism that can be
241  used to control access to critical resources, and may be used
242  across multiple threads or processes. A full description of
243  mutex usage and theory is beyond the scope of this document. Please
244  refer to literature for more details (eg, Programming With
245  Posix Threads, by Butenhof, Addison-Wesley).
246 
247  Attempting to access (or lock) an already locked mutex using
248  rmMutexLock() will cause  the caller to block until the mutex is
249  released (unlocked). Attempting to unlock and already-unlocked mutex
250  using rmMutexUnlock() will have no effect. Callers can use
251  check the status of a mutex with rmMutexTryLock(), which is
252  non-blocking.
253 
254  OpenRM RMmutex objects (in Linux) use the "error checking" kind of
255  mutex - which means attempts to lock a mutex already owned and locked
256  by the calling thread will not result in multiple locks (like a semaphore).
257  Instead, an error is reported. In general, OpenRM applications developers
258  should use lots of programming discipline to avoid cases in which
259  code will apply multiple locks to a given mutex.
260 
261  OpenRM mutex objects are intended to behave similarly in both
262  Unix and Win32 environments.
263  @dend
264  * ----------------------------------------------------
265  */
266 RMmutex *
rmMutexNew(RMenum initLockState)267 rmMutexNew(RMenum initLockState)
268 {
269     RMmutex *m;
270     pthread_mutexattr_t attr;
271     int stat;
272 
273     m = (RMmutex *)malloc(sizeof(RMmutex));
274     pthread_mutex_init(m, NULL);
275 
276     pthread_mutexattr_init(&attr);
277 #ifdef LINUX
278     pthread_mutexattr_setkind_np(&attr, PTHREAD_MUTEX_ERRORCHECK_NP);
279 #endif
280 
281     /* what is the initial state of a mutex after being initialized? */
282 
283     if (initLockState == RM_MUTEX_LOCK)
284 	stat = pthread_mutex_lock(m);
285     return(m);
286 }
287 
288 /*
289  * ----------------------------------------------------
290  * @Name rmMutexDelete
291  @pstart
292  RMenum rmMutexDelete(RMmutex *toDelete)
293  @pend
294 
295  @astart
296  RMmutex * toDelete - a handle to an RMmutex object (modified).
297  @aend
298 
299  @dstart
300  Releases resources associated with an RMmutex object. Callers
301  should take care to ensure the RMmutex object is unlocked
302  prior to calling rmMutexDelete().
303 
304  Returns RM_CHILL upon success, or RM_WHACKED upon failure.
305 
306  The RMmutex object is a synchronization mechanism that can be
307  used to control access to critical resources, and may be used
308  across multiple threads or processes. A full description of
309  mutex usage and theory is beyond the scope of this document. Please
310  refer to literature for more details (eg, Programming With
311  Posix Threads, by Butenhof, Addison-Wesley).
312 
313  Attempting to access (or lock) an already locked mutex using
314  rmMutexLock() will cause  the caller to block until the mutex is
315  released (unlocked). Attempting to unlock and already-unlocked mutex
316  using rmMutexUnlock() will have no effect. Callers can use
317  check the status of a mutex with rmMutexTryLock(), which is
318  non-blocking.
319 
320  OpenRM RMmutex objects (in Linux) use the "error checking" kind of
321  mutex - which means attempts to lock a mutex already owned and locked
322  by the calling thread will not result in multiple locks (like a semaphore).
323  Instead, an error is reported. In general, OpenRM applications developers
324  should use lots of programming discipline to avoid cases in which
325  code will apply multiple locks to a given mutex.
326 
327  OpenRM mutex objects are intended to behave similarly in both
328  Unix and Win32 environments.
329  @dend
330  * ----------------------------------------------------
331  */
332 RMenum
rmMutexDelete(RMmutex * toDelete)333 rmMutexDelete(RMmutex *toDelete)
334 {
335     if (RM_ASSERT(toDelete,"rmMutexDelete error: the input RMmutex is NULL.")== RM_WHACKED)
336 	return (RM_WHACKED);
337 
338     if ((pthread_mutex_destroy(toDelete)) != 0)
339     {
340 	perror("rmMutexDelete");
341 	return(RM_WHACKED);
342     }
343 
344     free((void *)toDelete);
345     return(RM_CHILL);
346 }
347 
348 /*
349  * ----------------------------------------------------
350  * @Name rmMutexLock
351  @pstart
352  RMenum rmMutexLock(RMmutex *toLock)
353  @pend
354 
355  @astart
356  RMmutex * toLock - a handle to an RMmutex object (modified).
357  @aend
358 
359  @dstart
360 
361  Performs a blocking wait until the RMmutex object toLock is
362  unlocked, then will apply a lock and return RM_CHILL to the caller.
363  A return status of RM_WHACKED indicates an error of some type.
364  When an error is detected, this routine will call perror() (on
365  Unix) or its equivalent (on Win32) to report the cause of the error.
366 
367  The RMmutex object is a synchronization mechanism that can be
368  used to control access to critical resources, and may be used
369  across multiple threads or processes. A full description of
370  mutex usage and theory is beyond the scope of this document. Please
371  refer to literature for more details (eg, Programming With
372  Posix Threads, by Butenhof, Addison-Wesley).
373 
374  Attempting to access (or lock) an already locked mutex using
375  rmMutexLock() will cause  the caller to block until the mutex is
376  released (unlocked). Attempting to unlock and already-unlocked mutex
377  using rmMutexUnlock() will have no effect. Callers can use
378  check the status of a mutex with rmMutexTryLock(), which is
379  non-blocking.
380 
381  OpenRM RMmutex objects (in Linux) use the "error checking" kind of
382  mutex - which means attempts to lock a mutex already owned and locked
383  by the calling thread will not result in multiple locks (like a semaphore).
384  Instead, an error is reported. In general, OpenRM applications developers
385  should use lots of programming discipline to avoid cases in which
386  code will apply multiple locks to a given mutex.
387 
388  OpenRM mutex objects are intended to behave similarly in both
389  Unix and Win32 environments.
390  @dend
391  * ----------------------------------------------------
392  */
393 RMenum
rmMutexLock(RMmutex * toLock)394 rmMutexLock(RMmutex *toLock)
395 {
396     if (RM_ASSERT(toLock,"rmMutexLock error: the input RMmutex is NULL.")== RM_WHACKED)
397 	return (RM_WHACKED);
398 
399     if ((pthread_mutex_lock(toLock)) != 0)
400     {
401 	perror("rmMutexLock");
402 	return(RM_WHACKED);
403     }
404 
405     return(RM_CHILL);
406 }
407 
408 /*
409  * ----------------------------------------------------
410  * @Name rmMutexUnlock
411  @pstart
412  RMenum rmMutexUnlock(RMmutex *toUnlock)
413  @pend
414 
415  @astart
416  RMmutex * toUnlock - a handle to an RMmutex object (modified).
417  @aend
418 
419  @dstart
420 
421  Unlocks an RMmutex object. This call is non-blocking. Upon
422  success, RM_CHILL is returned to the caller. Upon failure,
423  RM_WHACKED is returned.  When an error is detected, this routine
424  will call perror() (on
425  Unix) or its equivalent (on Win32) to report the cause of the error.
426 
427  The RMmutex object is a synchronization mechanism that can be
428  used to control access to critical resources, and may be used
429  across multiple threads or processes. A full description of
430  mutex usage and theory is beyond the scope of this document. Please
431  refer to literature for more details (eg, Programming With
432  Posix Threads, by Butenhof, Addison-Wesley).
433 
434  Attempting to access (or lock) an already locked mutex using
435  rmMutexLock() will cause  the caller to block until the mutex is
436  released (unlocked). Attempting to unlock and already-unlocked mutex
437  using rmMutexUnlock() will have no effect. Callers can use
438  check the status of a mutex with rmMutexTryLock(), which is
439  non-blocking.
440 
441  OpenRM RMmutex objects (in Linux) use the "error checking" kind of
442  mutex - which means attempts to lock a mutex already owned and locked
443  by the calling thread will not result in multiple locks (like a semaphore).
444  Instead, an error is reported. In general, OpenRM applications developers
445  should use lots of programming discipline to avoid cases in which
446  code will apply multiple locks to a given mutex.
447 
448  OpenRM mutex objects are intended to behave similarly in both
449  Unix and Win32 environments.
450  @dend
451  * ----------------------------------------------------
452  */
453 RMenum
rmMutexUnlock(RMmutex * toUnlock)454 rmMutexUnlock(RMmutex *toUnlock)
455 {
456     if (RM_ASSERT(toUnlock,"rmMutexUnlock error: the input RMmutex is NULL.")== RM_WHACKED)
457 	return (RM_WHACKED);
458 
459     if ((pthread_mutex_unlock(toUnlock)) != 0)
460     {
461 	perror("rmMutexUnlock");
462 	return(RM_WHACKED);
463     }
464 
465     return(RM_CHILL);
466 }
467 
468 /*
469  * ----------------------------------------------------
470  * @Name rmMutexTryLock
471  @pstart
472  RMenum rmMutexTryLock(RMmutex *toLock)
473  @pend
474 
475  @astart
476  RMmutex * toLock - a handle to an RMmutex object (modified).
477  @aend
478 
479  @dstart
480 
481  Attempts to lock an RMmutex object - this call is non-blocking.
482 
483  If the RMmutex object is locked, a value of RM_MUTEX_BUSY is
484  returned to the caller, and the input RMmutex object remains
485  unmodified.
486 
487  If the RMmutex was unlocked, this routine will lock it, and
488  return RM_MUTEX_LOCK to the caller.
489 
490  The RMmutex object is a synchronization mechanism that can be
491  used to control access to critical resources, and may be used
492  across multiple threads or processes. A full description of
493  mutex usage and theory is beyond the scope of this document. Please
494  refer to literature for more details (eg, Programming With
495  Posix Threads, by Butenhof, Addison-Wesley).
496 
497  Attempting to access (or lock) an already locked mutex using
498  rmMutexLock() will cause  the caller to block until the mutex is
499  released (unlocked). Attempting to unlock and already-unlocked mutex
500  using rmMutexUnlock() will have no effect. Callers can use
501  check the status of a mutex with rmMutexTryLock(), which is
502  non-blocking.
503 
504  OpenRM RMmutex objects (in Linux) use the "error checking" kind of
505  mutex - which means attempts to lock a mutex already owned and locked
506  by the calling thread will not result in multiple locks (like a semaphore).
507  Instead, an error is reported. In general, OpenRM applications developers
508  should use lots of programming discipline to avoid cases in which
509  code will apply multiple locks to a given mutex.
510 
511  OpenRM mutex objects are intended to behave similarly in both
512  Unix and Win32 environments.
513  @dend
514  * ----------------------------------------------------
515  */
516 RMenum
rmMutexTryLock(RMmutex * toQuery)517 rmMutexTryLock(RMmutex *toQuery)
518 {
519     int rstat;
520     if (RM_ASSERT(toQuery,"rmMutexTryLock error: the input RMmutex is NULL.")== RM_WHACKED)
521 	return (RM_WHACKED);
522 
523     rstat = pthread_mutex_trylock(toQuery);
524 
525     if (rstat == 0)
526 	return(RM_MUTEX_LOCK);
527     else
528 	return(RM_MUTEX_BUSY);
529 }
530 
531 /* #endif */
532 
533 
534 /* PRIVATE */
535 void *
private_rmViewThreadFunc(void * args)536 private_rmViewThreadFunc(void *args)
537 {
538     /*
539      * for use with blocking MULTISTAGE rendering (serial or parallel)
540      */
541     RMthreadArgs *ta;
542     int command=THREAD_WORK;
543     RMmatrix initModelMatrix, initViewMatrix, initProjectionMatrix;
544     RMmatrix initTextureMatrix;
545 
546     rmMatrixIdentity(&initModelMatrix);
547     rmMatrixIdentity(&initViewMatrix);
548     rmMatrixIdentity(&initProjectionMatrix);
549     rmMatrixIdentity(&initTextureMatrix);
550 
551     ta = (RMthreadArgs *)args;
552 
553 #if (DEBUG_LEVEL & DEBUG_TRACE)
554     fprintf(stderr," view thread started. \n");
555     fflush(stderr);
556 #endif
557 
558     for (;command!=THREAD_QUIT;)
559     {
560 	barrier_wait(ta->one);
561 
562 	command = ta->commandOpcode;
563 
564 #if (DEBUG_LEVEL & DEBUG_TRACE)
565 	/* work goes here */
566 	fprintf(stderr," view command %d, frame %d, buffer %d\n",command, ta->frameNumber, private_rmSelectEvenOddBuffer(ta->p->frameNumber));
567 	fflush(stderr);
568 #endif
569 
570 	if (ta->initModel != NULL)
571 	    rmMatrixCopy(&initModelMatrix, ta->initModel);
572 
573 	if (ta->initView != NULL)
574 	    rmMatrixCopy(&initViewMatrix, ta->initView);
575 
576 	if (ta->initProjection != NULL)
577 	    rmMatrixCopy(&initProjectionMatrix, ta->initProjection);
578 
579 	if (ta->initTexture != NULL)
580 	    rmMatrixCopy(&initTextureMatrix, ta->initTexture);
581 
582 	private_rmView(ta->p, ta->n, ta->frameNumber,
583 		       &initModelMatrix, &initViewMatrix,
584 		       &initProjectionMatrix, &initTextureMatrix);
585 
586 	barrier_wait(ta->two);
587     }
588 #if (DEBUG_LEVEL & DEBUG_TRACE)
589     fprintf(stderr,"view thread exiting \n");
590     fflush(stderr);
591 #endif
592     return NULL;
593 }
594 
595 /* PRIVATE */
596 void *
private_rmRenderThreadFunc(void * args)597 private_rmRenderThreadFunc(void *args)
598 {
599     /*
600      * for use with blocking MULTISTAGE rendering (serial or parallel)
601      */
602     int stat;
603     RMthreadArgs *ta;
604     int command=THREAD_WORK;
605 
606     ta = (RMthreadArgs *)args;
607 
608 #ifdef RM_X
609     /*
610      * make the OpenGL context current for this thread. Note that
611      * the caller has to have done a context release in order for
612      * this to work - a glXMakeCurrent apparently does not unbind
613      * a thread-context binding that might have been done elsewhere.
614      */
615     stat = glXMakeCurrent(rmxPipeGetDisplay(ta->p),
616 			  rmPipeGetWindow(ta->p),
617 			  rmPipeGetContext(ta->p));
618 #endif
619 #ifdef RM_WIN
620     stat = wglMakeCurrent(ta->p->hdc, ta->p->hRC);
621 #endif
622     private_rmSetBackBuffer(ta->p);
623 
624 #if (DEBUG_LEVEL & DEBUG_GLERRORCHECK)
625     rmGLGetError("private_rmRenderThreadFunc start");
626 #endif
627 
628 #if (DEBUG_LEVEL & DEBUG_TRACE)
629     fprintf(stderr," render thread started. \n");
630     fflush(stderr);
631 #endif
632 
633     for (;command!=THREAD_QUIT;)
634     {
635 	barrier_wait(ta->one);
636 	command = ta->commandOpcode;
637 
638 #if (DEBUG_LEVEL & DEBUG_TRACE)
639 	fprintf(stderr," render command %d, frame %d, buffer=%d\n",command, ta->frameNumber, private_rmSelectEvenOddBuffer(ta->frameNumber));
640 	fflush(stderr);
641 #endif
642 	if (ta->frameNumber >= 0)
643         {
644 
645 	    private_rmRender(ta->p, ta->frameNumber);
646 
647 	    private_postRenderBarrierFunc(ta->p);
648 
649 	    if (ta->p->timeSyncFunc != NULL)
650 		(*(ta->p->timeSyncFunc))(ta->p);
651 
652 	    private_postRenderSwapBuffersFunc(ta->p);
653 	    private_postRenderImageFuncs(ta->p, GL_FRONT);
654 
655 /*	    glFlush(); this glFlush may not always be needed! Depends on
656  whether or not SwapBuffers includes a flush! */
657 	}
658 
659 	barrier_wait(ta->two);
660     }
661 
662 #if (DEBUG_LEVEL & DEBUG_TRACE)
663     fprintf(stderr,"render thread exiting \n");
664     fflush(stderr);
665 #endif
666     return NULL;
667 }
668 /* EOF */
669 
670