/* * Copyright (C) 1997-2005, R3vis Corporation. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA, or visit http://www.gnu.org/copyleft/lgpl.html. * * Original Contributor: * Wes Bethel, R3vis Corporation, Marin County, California * Additional Contributor(s): * * The OpenRM project is located at http://openrm.sourceforge.net/. */ /* * $Id: rmthread.c,v 1.7 2005/02/19 16:41:34 wes Exp $ * Version: $Name: OpenRM-1-6-0-RC5 $ * $Revision: 1.7 $ * $Log: rmthread.c,v $ * Revision 1.7 2005/02/19 16:41:34 wes * Distro sync and consolidation. * Support for NO_PTHREADS build. * * Revision 1.6 2005/01/23 17:00:22 wes * Copyright updated to 2005. * * Revision 1.5 2004/01/16 16:56:02 wes * * Rearranged calls within rendering routines so that: * (1) the time synchronization for constant-rate rendering happens after * the actual rendering, but right before SwapBuffers; * (2) the user-assignable routines for grabbing frambuffer pixels are * relocated to after the SwapBuffers - they were located before in * the previous version. * * These changes are expected to have the following benefits: * (1) frame sync is more stable when associated with SwapBuffers rather * than having it placed immediately before when rendering starts; * (2) We have removed an embedded glFinish() call; SwapBuffers always * implies a glFlush(), and in some implementations, also implies * a glFinish(). The oddball here in terms of execution behavior is * software Mesa. The only detrimental impact will be on timing rendering * as you must explicitly insert your own glFinish() to ensure that * timings are accurate. We are looking at this for the next rev of OpenRM. * * Revision 1.4 2003/10/03 19:19:32 wes * Use platform-independent interface to access the OpenGL context. * * Revision 1.3 2003/04/05 14:13:46 wes * Renamed rmMutexDestroy to rmMutexDelete for API consistency. * * Revision 1.2 2003/02/02 02:07:16 wes * Updated copyright to 2003. * * Revision 1.1.1.1 2003/01/28 02:15:23 wes * Manual rebuild of rm150 repository. * * Revision 1.5 2003/01/16 22:21:17 wes * Updated all source files to reflect new organization of header files: * all header files formerly located in include/rmaux, include/rmi, include/rmv * are now located in include/rm. * * Revision 1.4 2002/04/30 19:34:03 wes * Updated copyright dates. * * Revision 1.3 2001/06/03 20:50:16 wes * No significant differences. * * Revision 1.2 2001/03/31 17:12:39 wes * v1.4.0-alpha-2 checkin. * * Revision 1.1 2000/12/03 22:33:24 wes * Initial entry. * */ /* * this file contains wrappers for MP code that is based upon a * threaded programming model. * * We use Posix threads for mutex'es and threads. Win32 requires * an add-on library that provides Posix threads functionality. Such * a library is available for free download from: * http://sources.redhat.com/pthreads-win32/ * See the OpenRM RELEASENOTES for more information about the * installation and use of pthreads-win32. */ #include #include "rmprivat.h" #include "rmmultis.h" #ifdef _NO_PTHREADS #include "rmpthrd.h" #endif /* * ---------------------------------------------------- * @Name rmThreadCreate @pstart RMenum rmThreadCreate(RMthread *threadID, void * (*threadFunc)(void *), void *args) @pend @astart RMthread *threadID - a handle to an RMthread object (modified). void * (*threadFunc)(void *) - a handle to a function that will be executed by the new thread (input). void *args - arguments to the threadFunc, cast to a void *. @aend @dstart Use this routine to create a new execution thread. rmThreadCreate is a threads-abstraction layer that creates new execution threads in both Unix and Win32 environments. The Unix version is built using POSIX threads - for more information, see pthread_create(3). On Win32, see the MSDN documentation for _beginthread(). The new thread is detached, and begins immediate execution of the code contained in the routine "threadFunc". Arguments to the threadFunc may be passed through the parameter "args." Typically, args are packaged into a struct, and the handle to the struct is cast to a void *. The threadFunc then performs an inverse cast of the void *args to a struct * to gain access to the arguments. rmThreadCreate only creates a detached thread. Any synchronization must be performed by the application using the appropriate constructs. RMmutex's can be used (both Win32 and Unix) to implement synchronization. Alternatively, developers who have specialized knowledge of OS-specific features (e.g., semaphores, condition variables, etc) may use those constructs. Upon success, this routine will return RM_CHILL, and the RMthread *threadID is modified to contain thread-specific identification information. Upon failure, an error message is printed, and a RM_WHACKED is returned. @dend * ---------------------------------------------------- */ RMenum rmThreadCreate(RMthread *threadID, void * (*threadFunc)(void *), void *args) { int stat; RMenum rval; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); stat = pthread_create(threadID, &attr, threadFunc, (void *)args); if (stat != 0) { rval = RM_WHACKED; perror("rmThreadCreate/pthread_create error:"); } else rval = RM_CHILL; return(rval); } /* * ---------------------------------------------------- * @Name rmThreadJoin @pstart RMenum rmThreadJoin(RMthread *threadID, void **threadReturn) @pend @astart RMthread *threadID - a handle to an RMthread object (input). void **threadReturn - a handle to a void * (modified). @aend @dstart "Thread joining" means "wait for the thread to finish." Use rmThreadJoin to wait for completion of a thread. Upon success, this routine returns RM_CHILL, indicating the thread in question has completed. Upon failure, an error message is printed, and RM_WHACKED is returned (Unix only). NOTE: this function is a no-op on Win32, as there is no equivalent thread join routine in Win32. Developers must use explicit synchronization mechanisms on Win32 to coordinate signaling of completion between app and detached threads. The RMmutex will work nicely for that purpose. @dend * ---------------------------------------------------- */ RMenum rmThreadJoin(RMthread *threadID, void **threadReturn) { int stat; RMenum rval; stat = pthread_join(*threadID, threadReturn); if (stat != 0) { rval = RM_WHACKED; perror("rmThreadJoin/pthread_join error:"); } else rval = RM_CHILL; return(rval); } /* * ---------------------------------------------------- * @Name rmMutexNew @pstart RMmutex * rmMutexNew(RMenum initLockState) @pend @astart RMenum initLockState - an RMenum value (input) specifying the initial state of the RMmutex returned to the caller. @aend @dstart Creates an initialized RMmutex object, and returns the handle of the new RMmutex object to the caller upon success, or NULL upon failure. The initial value of the RMmutex is set to the value specified by the input parameter initLockState. Valid values for initLockState are RM_MUTEX_LOCK or RM_MUTEX_UNLOCK. The RMmutex object is a synchronization mechanism that can be used to control access to critical resources, and may be used across multiple threads or processes. A full description of mutex usage and theory is beyond the scope of this document. Please refer to literature for more details (eg, Programming With Posix Threads, by Butenhof, Addison-Wesley). Attempting to access (or lock) an already locked mutex using rmMutexLock() will cause the caller to block until the mutex is released (unlocked). Attempting to unlock and already-unlocked mutex using rmMutexUnlock() will have no effect. Callers can use check the status of a mutex with rmMutexTryLock(), which is non-blocking. OpenRM RMmutex objects (in Linux) use the "error checking" kind of mutex - which means attempts to lock a mutex already owned and locked by the calling thread will not result in multiple locks (like a semaphore). Instead, an error is reported. In general, OpenRM applications developers should use lots of programming discipline to avoid cases in which code will apply multiple locks to a given mutex. OpenRM mutex objects are intended to behave similarly in both Unix and Win32 environments. @dend * ---------------------------------------------------- */ RMmutex * rmMutexNew(RMenum initLockState) { RMmutex *m; pthread_mutexattr_t attr; int stat; m = (RMmutex *)malloc(sizeof(RMmutex)); pthread_mutex_init(m, NULL); pthread_mutexattr_init(&attr); #ifdef LINUX pthread_mutexattr_setkind_np(&attr, PTHREAD_MUTEX_ERRORCHECK_NP); #endif /* what is the initial state of a mutex after being initialized? */ if (initLockState == RM_MUTEX_LOCK) stat = pthread_mutex_lock(m); return(m); } /* * ---------------------------------------------------- * @Name rmMutexDelete @pstart RMenum rmMutexDelete(RMmutex *toDelete) @pend @astart RMmutex * toDelete - a handle to an RMmutex object (modified). @aend @dstart Releases resources associated with an RMmutex object. Callers should take care to ensure the RMmutex object is unlocked prior to calling rmMutexDelete(). Returns RM_CHILL upon success, or RM_WHACKED upon failure. The RMmutex object is a synchronization mechanism that can be used to control access to critical resources, and may be used across multiple threads or processes. A full description of mutex usage and theory is beyond the scope of this document. Please refer to literature for more details (eg, Programming With Posix Threads, by Butenhof, Addison-Wesley). Attempting to access (or lock) an already locked mutex using rmMutexLock() will cause the caller to block until the mutex is released (unlocked). Attempting to unlock and already-unlocked mutex using rmMutexUnlock() will have no effect. Callers can use check the status of a mutex with rmMutexTryLock(), which is non-blocking. OpenRM RMmutex objects (in Linux) use the "error checking" kind of mutex - which means attempts to lock a mutex already owned and locked by the calling thread will not result in multiple locks (like a semaphore). Instead, an error is reported. In general, OpenRM applications developers should use lots of programming discipline to avoid cases in which code will apply multiple locks to a given mutex. OpenRM mutex objects are intended to behave similarly in both Unix and Win32 environments. @dend * ---------------------------------------------------- */ RMenum rmMutexDelete(RMmutex *toDelete) { if (RM_ASSERT(toDelete,"rmMutexDelete error: the input RMmutex is NULL.")== RM_WHACKED) return (RM_WHACKED); if ((pthread_mutex_destroy(toDelete)) != 0) { perror("rmMutexDelete"); return(RM_WHACKED); } free((void *)toDelete); return(RM_CHILL); } /* * ---------------------------------------------------- * @Name rmMutexLock @pstart RMenum rmMutexLock(RMmutex *toLock) @pend @astart RMmutex * toLock - a handle to an RMmutex object (modified). @aend @dstart Performs a blocking wait until the RMmutex object toLock is unlocked, then will apply a lock and return RM_CHILL to the caller. A return status of RM_WHACKED indicates an error of some type. When an error is detected, this routine will call perror() (on Unix) or its equivalent (on Win32) to report the cause of the error. The RMmutex object is a synchronization mechanism that can be used to control access to critical resources, and may be used across multiple threads or processes. A full description of mutex usage and theory is beyond the scope of this document. Please refer to literature for more details (eg, Programming With Posix Threads, by Butenhof, Addison-Wesley). Attempting to access (or lock) an already locked mutex using rmMutexLock() will cause the caller to block until the mutex is released (unlocked). Attempting to unlock and already-unlocked mutex using rmMutexUnlock() will have no effect. Callers can use check the status of a mutex with rmMutexTryLock(), which is non-blocking. OpenRM RMmutex objects (in Linux) use the "error checking" kind of mutex - which means attempts to lock a mutex already owned and locked by the calling thread will not result in multiple locks (like a semaphore). Instead, an error is reported. In general, OpenRM applications developers should use lots of programming discipline to avoid cases in which code will apply multiple locks to a given mutex. OpenRM mutex objects are intended to behave similarly in both Unix and Win32 environments. @dend * ---------------------------------------------------- */ RMenum rmMutexLock(RMmutex *toLock) { if (RM_ASSERT(toLock,"rmMutexLock error: the input RMmutex is NULL.")== RM_WHACKED) return (RM_WHACKED); if ((pthread_mutex_lock(toLock)) != 0) { perror("rmMutexLock"); return(RM_WHACKED); } return(RM_CHILL); } /* * ---------------------------------------------------- * @Name rmMutexUnlock @pstart RMenum rmMutexUnlock(RMmutex *toUnlock) @pend @astart RMmutex * toUnlock - a handle to an RMmutex object (modified). @aend @dstart Unlocks an RMmutex object. This call is non-blocking. Upon success, RM_CHILL is returned to the caller. Upon failure, RM_WHACKED is returned. When an error is detected, this routine will call perror() (on Unix) or its equivalent (on Win32) to report the cause of the error. The RMmutex object is a synchronization mechanism that can be used to control access to critical resources, and may be used across multiple threads or processes. A full description of mutex usage and theory is beyond the scope of this document. Please refer to literature for more details (eg, Programming With Posix Threads, by Butenhof, Addison-Wesley). Attempting to access (or lock) an already locked mutex using rmMutexLock() will cause the caller to block until the mutex is released (unlocked). Attempting to unlock and already-unlocked mutex using rmMutexUnlock() will have no effect. Callers can use check the status of a mutex with rmMutexTryLock(), which is non-blocking. OpenRM RMmutex objects (in Linux) use the "error checking" kind of mutex - which means attempts to lock a mutex already owned and locked by the calling thread will not result in multiple locks (like a semaphore). Instead, an error is reported. In general, OpenRM applications developers should use lots of programming discipline to avoid cases in which code will apply multiple locks to a given mutex. OpenRM mutex objects are intended to behave similarly in both Unix and Win32 environments. @dend * ---------------------------------------------------- */ RMenum rmMutexUnlock(RMmutex *toUnlock) { if (RM_ASSERT(toUnlock,"rmMutexUnlock error: the input RMmutex is NULL.")== RM_WHACKED) return (RM_WHACKED); if ((pthread_mutex_unlock(toUnlock)) != 0) { perror("rmMutexUnlock"); return(RM_WHACKED); } return(RM_CHILL); } /* * ---------------------------------------------------- * @Name rmMutexTryLock @pstart RMenum rmMutexTryLock(RMmutex *toLock) @pend @astart RMmutex * toLock - a handle to an RMmutex object (modified). @aend @dstart Attempts to lock an RMmutex object - this call is non-blocking. If the RMmutex object is locked, a value of RM_MUTEX_BUSY is returned to the caller, and the input RMmutex object remains unmodified. If the RMmutex was unlocked, this routine will lock it, and return RM_MUTEX_LOCK to the caller. The RMmutex object is a synchronization mechanism that can be used to control access to critical resources, and may be used across multiple threads or processes. A full description of mutex usage and theory is beyond the scope of this document. Please refer to literature for more details (eg, Programming With Posix Threads, by Butenhof, Addison-Wesley). Attempting to access (or lock) an already locked mutex using rmMutexLock() will cause the caller to block until the mutex is released (unlocked). Attempting to unlock and already-unlocked mutex using rmMutexUnlock() will have no effect. Callers can use check the status of a mutex with rmMutexTryLock(), which is non-blocking. OpenRM RMmutex objects (in Linux) use the "error checking" kind of mutex - which means attempts to lock a mutex already owned and locked by the calling thread will not result in multiple locks (like a semaphore). Instead, an error is reported. In general, OpenRM applications developers should use lots of programming discipline to avoid cases in which code will apply multiple locks to a given mutex. OpenRM mutex objects are intended to behave similarly in both Unix and Win32 environments. @dend * ---------------------------------------------------- */ RMenum rmMutexTryLock(RMmutex *toQuery) { int rstat; if (RM_ASSERT(toQuery,"rmMutexTryLock error: the input RMmutex is NULL.")== RM_WHACKED) return (RM_WHACKED); rstat = pthread_mutex_trylock(toQuery); if (rstat == 0) return(RM_MUTEX_LOCK); else return(RM_MUTEX_BUSY); } /* #endif */ /* PRIVATE */ void * private_rmViewThreadFunc(void *args) { /* * for use with blocking MULTISTAGE rendering (serial or parallel) */ RMthreadArgs *ta; int command=THREAD_WORK; RMmatrix initModelMatrix, initViewMatrix, initProjectionMatrix; RMmatrix initTextureMatrix; rmMatrixIdentity(&initModelMatrix); rmMatrixIdentity(&initViewMatrix); rmMatrixIdentity(&initProjectionMatrix); rmMatrixIdentity(&initTextureMatrix); ta = (RMthreadArgs *)args; #if (DEBUG_LEVEL & DEBUG_TRACE) fprintf(stderr," view thread started. \n"); fflush(stderr); #endif for (;command!=THREAD_QUIT;) { barrier_wait(ta->one); command = ta->commandOpcode; #if (DEBUG_LEVEL & DEBUG_TRACE) /* work goes here */ fprintf(stderr," view command %d, frame %d, buffer %d\n",command, ta->frameNumber, private_rmSelectEvenOddBuffer(ta->p->frameNumber)); fflush(stderr); #endif if (ta->initModel != NULL) rmMatrixCopy(&initModelMatrix, ta->initModel); if (ta->initView != NULL) rmMatrixCopy(&initViewMatrix, ta->initView); if (ta->initProjection != NULL) rmMatrixCopy(&initProjectionMatrix, ta->initProjection); if (ta->initTexture != NULL) rmMatrixCopy(&initTextureMatrix, ta->initTexture); private_rmView(ta->p, ta->n, ta->frameNumber, &initModelMatrix, &initViewMatrix, &initProjectionMatrix, &initTextureMatrix); barrier_wait(ta->two); } #if (DEBUG_LEVEL & DEBUG_TRACE) fprintf(stderr,"view thread exiting \n"); fflush(stderr); #endif return NULL; } /* PRIVATE */ void * private_rmRenderThreadFunc(void *args) { /* * for use with blocking MULTISTAGE rendering (serial or parallel) */ int stat; RMthreadArgs *ta; int command=THREAD_WORK; ta = (RMthreadArgs *)args; #ifdef RM_X /* * make the OpenGL context current for this thread. Note that * the caller has to have done a context release in order for * this to work - a glXMakeCurrent apparently does not unbind * a thread-context binding that might have been done elsewhere. */ stat = glXMakeCurrent(rmxPipeGetDisplay(ta->p), rmPipeGetWindow(ta->p), rmPipeGetContext(ta->p)); #endif #ifdef RM_WIN stat = wglMakeCurrent(ta->p->hdc, ta->p->hRC); #endif private_rmSetBackBuffer(ta->p); #if (DEBUG_LEVEL & DEBUG_GLERRORCHECK) rmGLGetError("private_rmRenderThreadFunc start"); #endif #if (DEBUG_LEVEL & DEBUG_TRACE) fprintf(stderr," render thread started. \n"); fflush(stderr); #endif for (;command!=THREAD_QUIT;) { barrier_wait(ta->one); command = ta->commandOpcode; #if (DEBUG_LEVEL & DEBUG_TRACE) fprintf(stderr," render command %d, frame %d, buffer=%d\n",command, ta->frameNumber, private_rmSelectEvenOddBuffer(ta->frameNumber)); fflush(stderr); #endif if (ta->frameNumber >= 0) { private_rmRender(ta->p, ta->frameNumber); private_postRenderBarrierFunc(ta->p); if (ta->p->timeSyncFunc != NULL) (*(ta->p->timeSyncFunc))(ta->p); private_postRenderSwapBuffersFunc(ta->p); private_postRenderImageFuncs(ta->p, GL_FRONT); /* glFlush(); this glFlush may not always be needed! Depends on whether or not SwapBuffers includes a flush! */ } barrier_wait(ta->two); } #if (DEBUG_LEVEL & DEBUG_TRACE) fprintf(stderr,"render thread exiting \n"); fflush(stderr); #endif return NULL; } /* EOF */