/*
 * Copyright (C) 1997-2009, 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
 *   http://www.r3vis.com/
 * 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: v180-alpha-02 $
 * $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 <rm/rm.h>
#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 */

