/*
 * 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: rmpipe.c,v 1.32 2008/11/24 16:36:01 wes Exp $
 * Version: $Name: v180-alpha-02 $
 * $Revision: 1.32 $
 * $Log: rmpipe.c,v $
 * Revision 1.32  2008/11/24 16:36:01  wes
 * Minor docs update
 *
 * Revision 1.31  2007/10/26 11:31:32  wes
 * Fix up errors in in-code documentation
 *
 * Revision 1.30  2007/05/14 15:29:56  wes
 * Added rmPipeGetWindowWidth() and rmPipeGetWindowHeight() calls
 *
 * Revision 1.29  2006/08/02 05:00:05  wes
 * Added debug routine to print node & prim index.
 *
 * Revision 1.28  2005/09/12 04:03:51  wes
 * Minor documentation updates.
 *
 * Revision 1.27  2005/06/26 18:57:03  wes
 * Added initialization code to rmPipeNew that clears the new attrib mask stack.
 *
 * Revision 1.26  2005/06/12 21:18:23  wes
 * Minor language changes to in-code documentation.
 *
 * Revision 1.25  2005/06/06 02:04:29  wes
 * Lots of small additions to clean up compiler warnings.
 *
 * Revision 1.24  2005/05/16 01:02:02  wes
 * Streamlining rmPipeMakeCurrent
 *
 * Revision 1.23  2005/03/16 16:45:47  wes
 * Minor code changes, documentation update.
 *
 * Revision 1.22  2005/02/24 16:17:25  wes
 * Added support for fbClears to be set at the RMpipe level. Apps can
 * still set them at the RMnode level if desired for fine-grained control.
 *
 * Revision 1.21  2005/02/19 16:40:20  wes
 * Distro sync and consolidation.
 * Repairs to fix memory leak associated with repeated calls to rmPipeNew,
 * rmPipeMakeCurrent, rmPipeClose.
 *
 * Revision 1.20  2005/01/23 17:08:25  wes
 * Copyright updated to 2005.
 * Updates to support access and use of extensions; multitexturing on all
 * platforms, use of 3d texture extension to realize implementation of
 * volume rendering and related functions on Windows platforms.
 *
 * Revision 1.19  2004/09/26 21:47:09  wes
 * Modified rmPipeMakeCurrent to do more thorough checking before
 * making the OpenGL context current. In X, you must have a valid
 * Display, a valid Window and a pre-assigned context before OpenRM
 * will do a glXMakeCurrent call.
 *
 * Also, modified rmPipeClose to zero out the internal variables for
 * the contextCache and displayLists fields so that you can call rmPipeClose
 * on an RMpipe more than once and not get an error.
 *
 * Revision 1.18  2004/08/07 17:12:59  wes
 * Updated rmPipeMakeCurrent so that the return status accurately reflects
 * that both (1) the OpenGL context is active and usable, and (2) that
 * RM was able to initialize the context cache and component manager.
 *
 * Revision 1.17  2004/05/09 22:23:13  wes
 * Update to rmPipeMakeCurrent to accommodate component mgr, context cache
 * and quadrics display list construction when the app has specified
 * NULL as a drawable. The implication here is that they're using their
 * own OpenGL context. Added a routine to check for the presence of
 * an active context. The new routine contains arch-specific code.
 *
 * Revision 1.16  2004/03/27 16:46:56  wes
 * Tweak rmPipeMakeCurrent so that glXMakeCurrent is called only if we have
 * a valid window and context handle under X11 (those checks were already
 * done for Win32)
 *
 * Revision 1.15  2004/02/23 03:03:48  wes
 * New routine: rmPipeGetFrameNumber().
 *
 * Revision 1.14  2004/01/16 16:57:03  wes
 * Added time synchronization function which is used to achieve
 * constant-rate rendering.
 *
 * Revision 1.13  2003/12/12 00:34:13  wes
 * Added documentation for rmPipeSetFrameRate and rmPipeGetFrameRate.
 *
 * Revision 1.12  2003/12/06 03:26:06  wes
 * Documentation tweaks to RMtime routines, updates to RMtime routines for
 * Windows.
 *
 * Revision 1.11  2003/12/01 02:13:05  wes
 * Additions to support constant frame-rate rendering on both Unix and Win32.
 *
 * Revision 1.10  2003/10/03 19:20:47  wes
 * Use platform-independent interface to access the OpenGL context.
 *
 * Revision 1.9  2003/07/23 13:32:28  wes
 * Win32: problems with offscreen rendering appeared with new context
 * initialization code sequence (1.5.0). Minor repairs needed to fix the bug.
 *
 * Revision 1.8  2003/04/12 21:02:00  wes
 * Undo of movement of RMpipe view-render buffer init back into rendering
 * path, and out of rmPipeNew. The init code can't be called from rmPipeNew
 * because the init code depends upon the channel format, which is usually
 * set by the app after rmPipeNew.
 *
 * Revision 1.7  2003/04/12 19:48:30  wes
 * Set the default comm size to 1, default rank to zero.
 *
 * Revision 1.6  2003/04/05 14:12:06  wes
 * Add code to rmPipeClose() that calls routines freeing inter-stage resources.
 *
 * Revision 1.5  2003/03/16 21:56:16  wes
 * Documentation updates.
 *
 * Revision 1.4  2003/02/14 00:21:02  wes
 * *** empty log message ***
 *
 * Revision 1.3  2003/02/02 02:07:15  wes
 * Updated copyright to 2003.
 *
 * Revision 1.2  2003/02/01 17:56:15  wes
 * Win32 code work to reflect new RMpipe initialization sequence.
 *
 * Revision 1.1.1.1  2003/01/28 02:15:23  wes
 * Manual rebuild of rm150 repository.
 *
 * Revision 1.19  2003/01/27 05:04:42  wes
 * Changes to RMpipe API and initialization sequence to unify GLX, WGL and CR
 * platforms w/o too much disruption to existing apps.
 *
 * Revision 1.18  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.17  2003/01/11 18:44:22  wes
 * Added global control over whether or not display lists are used during
 * rendering by adding the new RMpipe controls rmPipeSetDisplayListEnable()
 * and rmPipeGetDisplayListEnable().
 *
 * Revision 1.16  2002/12/31 00:55:22  wes
 *
 * Various enhancements to support Chromium - achitecture-specific sections
 * of RMpipe were cleaned up, etc.
 *
 * Revision 1.15  2002/08/29 22:20:32  wes
 *
 * Massive upgrade to accommodate dynamic object reallocation within
 * the component manager, and within the context cache. Use the
 * debug #define DEBUG_LEVEL DEBUG_REALLOC_TRACE to get a printf
 * whenever a realloc occurs. With this upgrade, there are no
 * OpenRM limits on the size of the scene graph. There will be external
 * limits, such as the amount of RAM and the amount of space available
 * to your OpenGL implementation.
 *
 * Revision 1.14  2002/06/30 21:51:22  wes
 * Added code by SF member barrett that checks for NULL when deleting
 * an RMpipe's context cache.
 *
 * Revision 1.13  2002/04/30 19:33:05  wes
 * Updated copyright dates.
 *
 * Revision 1.12  2001/10/15 00:11:31  wes
 * Minor tweak.
 *
 * Revision 1.11  2001/10/15 00:10:20  wes
 * Added code to close the window on when an RMpipe is closed.
 *
 * Revision 1.10  2001/07/15 17:17:59  wes
 * Added code to cleanse the context cache when the RMpipe is either
 * rmPipeClose()'d or rmPipeDelete()'d.
 *
 * Revision 1.9  2001/06/04 00:59:55  wes
 * Added documentation for RMpipe processing modes.
 *
 * Revision 1.8  2001/06/03 20:49:46  wes
 * Removed "rmPipeGetCurrent", added calls to query current processing
 * mode to be able to determine if it's an offscreen mode or a
 * multithreaded mode.
 *
 * Revision 1.7  2001/03/31 17:12:39  wes
 * v1.4.0-alpha-2 checkin.
 *
 * Revision 1.6  2000/12/03 22:33:55  wes
 * Mods for thread-safety.
 *
 * Revision 1.5  2000/08/23 23:27:45  wes
 * Minor edits for readability.
 *
 * Revision 1.4  2000/05/14 23:37:11  wes
 * Added control via RMpipe attribute to how OpenGL matrix stack
 * is initialized or used during rendering.
 *
 * Revision 1.3  2000/04/20 16:29:47  wes
 * Documentation additions/enhancements, some code rearragement.
 *
 * Revision 1.2  2000/02/29 23:43:53  wes
 * Compile warning cleanups.
 *
 * Revision 1.1.1.1  2000/02/28 21:29:40  wes
 * OpenRM 1.2 Checkin
 *
 * Revision 1.1.1.1  2000/02/28 17:18:48  wes
 * Initial entry - pre-RM120 release, source base for OpenRM 1.2.
 *
 */

/* 
 * generic pipe utilities (window system independent) in this file.
 * specific pipe/window utilities are in rmx.c or rmw.c
 */

#include <rm/rm.h>
#include "rmprivat.h"
#include "rmmultis.h"

/* stuff for debugging constant-rate rendering */
#define DEBUG_RSYNC 0
#if DEBUG_RSYNC

#define MAX_RSYNC_SAMPLES 1000

typedef struct
{
    RMtime t;
    int frameNumber;
} RMtimeSyncStruct;

RMtimeSyncStruct timeSyncData[1000];
int timeSyncIndx = 0;

#endif

static int private_rmCheckForActiveContext(void);
static void private_rmPipeGetCapabilities(RMpipeOGLCapabilities *);

/*
 * ----------------------------------------------------
 * @Name rmPipeGetWindowWidth
 @pstart
 int rmPipeGetWindowWidth (const RMpipe *toQuery)
 @pend

 @astart
 RMpipe *toQuery - a handle to an RMpipe.
 @aend

 @dstart

 This routine allows the application to query the width in pixels of
 the window currently bound to the RMpipe toQuery.

 If the RMpipe toQuery is NULL, this routine returns a value of -1.
 If there is no window currently bound to the RMpipe, the return
 value is undefined.

 See also rmPipeGetWindowSize, which returns both width and height
 of the RMpipe's window, and rmPipeGetWindowHeight, which returns
 just the height of the RMpipe's window.

 @dend
 * ----------------------------------------------------
 */
int
rmPipeGetWindowWidth (const RMpipe *pipe)
{
    if (RM_ASSERT(pipe, "rmPipeGetWindowWidth() error: the input RMpipe is NULL") == RM_WHACKED)
      return(-1);
    return(pipe->xwindow_width);
}

/*
 * ----------------------------------------------------
 * @Name rmPipeGetWindowHeight
 @pstart
 int rmPipeGetWindowHeight (const RMpipe *toQuery)
 @pend

 @astart
 RMpipe *toQuery - a handle to an RMpipe.
 @aend

 @dstart

 This routine allows the application to query the height in pixels of
 the window currently bound to the RMpipe toQuery.

 If the RMpipe toQuery is NULL, this routine returns a value of -1.
 If there is no window currently bound to the RMpipe, the return
 value is undefined.

 See also rmPipeGetWindowSize, which returns both width and height
 of the RMpipe's window, and rmPipeGetWindowWidth, which returns
 just the width of the RMpipe's window.

 @dend
 * ----------------------------------------------------
 */
int
rmPipeGetWindowHeight (const RMpipe *pipe)
{
    if (RM_ASSERT(pipe, "rmPipeGetWindowHeight() error: the input RMpipe is NULL") == RM_WHACKED)
      return(-1);
    return(pipe->xwindow_height);
}

/*
 * ----------------------------------------------------
 * @Name rmPipeCreateContext
 @pstart
 RMenum rmPipeCreateContext (RMpipe *toUse)
 @pend

 @astart
 RMpipe *toUse - a handle to an RMpipe (modified).
 @aend

 @dstart

 This routine will create a platform-specific OpenGL context that honors
 the display format attributes contained in the RMpipe. Use
 rmPipeSetChannelFormat() to set such parameters prior to creating the context.
 Changing the channel format parameters after the context has been created
 with this routine will have no effect.

 After the context has been created with this routine, be sure to call
 rmPipeMakeCurrent() to perform final intialization steps needed to ready
 the context for use in rendering.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.


 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeCreateContext (RMpipe *p)
{
    if (p->createContextFunc != NULL)
	return(p->createContextFunc(p));
    else
	rmError(" rmPipeCreateContext() error - no context creation function assigned!! Ouch!");

    return RM_WHACKED;
}

/*
 * ----------------------------------------------------
 * @Name rmPipeNew
  @pstart
  RMpipe * rmPipeNew (RMenum targetPlatform)
  @pend

  @astart
  RMenum targetPlatform - select the appropriate platform. This parameter
    must be one of RM_PIPE_GLX, RM_PIPE_WGL or RM_PIPE_CR.
  @aend

  @dstart

  Use this routine to create a new RMpipe object for use with a specified
  display platform. Upon success, a handle to the new RMpipe object is
  returned to the caller, or NULL is returned upon failure.

  The input parameter must be one of RM_PIPE_GLX, RM_PIPE_WGL, RM_PIPE_CR or
  RM_PIPE_NOPLATFORM. RM_PIPE_GLX specifies use on any X-based system that
  has the GLX extension (supports OpenGL through the server). RM_PIPE_WGL
  specifies use on a Win32 platform. RM_PIPE_CR specifies use only on a
  Chromium-enabled system. RM_PIPE_NOPLATFORM may be be used on any
  platform. The difference between RM_PIPE_NOPLATFORM and others is that
  some RMpipe fields are not initialized (swapbuffers function, context
  creation function,... ).
  
  Note that RM_PIPE_GLX and RM_PIPE_WGL platforms can make use of Chromium
  as well for doing single application to one or more crservers. However,
  an RM_PIPE_CR platform is highly Chromium-specific. Refer to the RM
  Programming Guide for more details. 

  During rmPipeNew(), the RMpipe object is initialized with the following
  platform-specific and platform-neutral settings:

  1. The "swapbuffers" function is set to a platform-appropriate value. The
  application may later override this setting using rmPipeSetSwapBuffersFunc().
  By assigning a default swapbuffers function, we are assuming that the
  OpenGL context (created later) will be double buffered. For the
  RM_PIPE_NOPLATFORM, no swapbuffers function is assigned.

  2. The "context creation function" is set to a platform-appropriate value.
  The application may indirectly override the context creation function using
  rmPipeSetContext() to assign an OpenGL context to the RMpipe. Such an
  assignment must occur between rmPipeNew() and rmPipeMakeCurrent().

  3. The default channel format is RM_MONO_CHANNEL, which corresponds to
  an OpenGL visual that has RGBA color buffers, a depth buffer, and which
  is double-buffered. The channel format can be set using
  rmPipeSetChannelFormat() after rmPipeNew() returns, and before a window
  is created (or before drawing occurs in the case of RM_PIPE_CR).

  4. The default processing mode is RM_PIPE_MULTISTAGE. This mode corresponds
  to serial rendering using a two-stage pipeline. You can set the processing
  mode after rmPipeNew() returns (see rmPipeSetProcessingMode), but before
  rmPipeMakeCurrent().

  5. The "post render barrier function" is set to NULL (see
  rmPipeSetPostRenderBarrierFunc).

  6. The "post render function" is set to NULL (see
  rmPipeSetPostRenderFunc).

  7. Each of the following three passes of the multipass rendering
  engine is enabled by default: opaque 3D, transparent 3D, opaque
  2D. Applications may not change the order of these rendering passes,
  but may enable or disable a given rendering pass with
  rmPipeSetRenderPassEnable().


  The basic sequence of steps needed to fully initialize RM for rendering are:

  1. Use rmPipeNew(yourPlatform) to create the RMpipe object specific
     for your platform.

  2. Set any optional parameters on the RMpipe: processing mode (see
     rmPipeSetProcessingMode), channel format (see rmPipeSetChannelFormat),
     XDisplay on X systems (see rmxPipeSetDisplay).

     X11 note: if you want to display somewhere other than getenv($DISPLAY),
     you will need to make an explicit call to rmxPipeSetDisplay() using
     an appropriate XDisplay structure. By default, rmPipeNew(RM_PIPE_GLX)
     will perform XOpenDisplay(getenv("$DISPLAY")), and assign the
     resulting XDisplay structure to the RMpipe. An opened X Display is
     necessary for access to bitmap fonts.

  3. Create a window suitable for display. This can be done with
     either rmauxCreateXWindow() or rmauxCreateW32Window(), or a suitable
     drawable can be provided by an external source (for more details, see
     the RM Programming Guide).

  4. Assign the window to the RMpipe (see rmPipeSetWindow)

  5. Make the pipe current (rmPipeMakeCurrent). 

  @dend
  * ----------------------------------------------------
 */
RMpipe *
rmPipeNew (RMenum targetPlatform)
{
    RMpipe *t;
    extern RMenum RM_DEFAULT_PIPE_DISPLAY_LIST_ENABLE;

    if ((targetPlatform != RM_PIPE_GLX) && (targetPlatform != RM_PIPE_WGL) &&
	(targetPlatform != RM_PIPE_CR) && (targetPlatform != RM_PIPE_NOPLATFORM))
    {
	rmError("rmPipeNew() error - the input targetPlatform must be one of RM_PIPE_GLX, RM_PIPE_WGL, RM_PIPE_CR, RM_PIPE_NOPLATFORM");
	return NULL;
    }

    t = (RMpipe *)calloc(1,sizeof(RMpipe));

    if (t == NULL)
	return(NULL);

    private_rmPipeSetPlatform(t, targetPlatform);
    
    rmPipeSetSwapBuffersFunc(t, NULL);
    
#ifdef RM_X
    if (targetPlatform == RM_PIPE_GLX)
    {
	/* 2/5/05 - want ability for selective XOpenDisplay */
	/* 5/1/05 - why? wes */
	const char *displayName = getenv("DISPLAY");
	Display *theDisplay = XOpenDisplay(displayName);
	rmxPipeSetDisplay(t, theDisplay);
	
	rmPipeSetSwapBuffersFunc(t, rmPipeSwapBuffersX11);
	t->shutdownFunc = private_rmPipeCloseContextX11;
	t->createContextFunc = private_rmxPipeCreateContext;
    }
#endif
#ifdef RM_WIN
    if (targetPlatform == RM_PIPE_WGL)
    {
	rmPipeSetSwapBuffersFunc(t, rmPipeSwapBuffersWin32);
	t->shutdownFunc = private_rmPipeCloseContextW32;
	t->createContextFunc = rmwPipeCreateContext;
    }
#endif
#ifdef RM_CR
    if (targetPlatform == RM_PIPE_CR)
    {
#ifdef RM_X
	/* 2/5/05 - want ability for selective XOpenDisplay */
	/* 5/1/05 - why? wes */
	/* 5/1/05 - when building the Chromium-enabled version of RM, you
	 should also build with -DRM_X because the RMpipe needs the $DISPLAY
 	 to access bitmap fonts. */
	const char *displayName = getenv("DISPLAY");
	Display *theDisplay = XOpenDisplay(displayName);
	rmxPipeSetDisplay(t, theDisplay);
#endif
	private_rmPipeInitCR(t);
	t->shutdownFunc = private_rmPipeCloseContextCR;
	t->createContextFunc = rmPipeCreateContextCR;
	t->contextCR = -1;
    }
#endif

    if (targetPlatform == RM_PIPE_NOPLATFORM)
    {
	t->shutdownFunc = NULL;
	t->createContextFunc = NULL;
    }

    rmPipeSetCommSize(t, 1); /* default comm size is 1 */
    rmPipeSetRank(t, 0);     /* default rank is zero */
    
    rmPipeSetPostRenderBarrierFunc(t, NULL);
    rmPipeSetPostRenderFunc(t, NULL);
    rmPipeSetInitMatrixStackMode(t, RM_TRUE);

    t->channel_format = RM_MONO_CHANNEL;
    t->processingMode = RM_PIPE_MULTISTAGE;
    t->offscreen = RM_FALSE;
    
    rmPipeSetChannelFormat(t, RM_MONO_CHANNEL);

    /* enable 3 stage, multipass rendering */
    rmPipeSetRenderPassEnable(t, RM_TRUE, RM_TRUE, RM_TRUE);

    /* set default display list use policy */
    rmPipeSetDisplayListEnable(t, RM_DEFAULT_PIPE_DISPLAY_LIST_ENABLE);

    /* set frame rate to -1. this means constant-rate rendering stuff
       is disabled by default, and the app must specifically request it. */
    rmPipeSetFrameRate(t, -1);
    private_rmPipeSetTimeSyncFunc(t, NULL);

    /* initialize the local attrib mask stack */
    t->localMaskStackTop = 0;
    memset((void *)(t->localMaskStack), 0, sizeof(GLuint)*MAX_MASK_STACK_DEPTH);

    return(t);
}

/*
 * ----------------------------------------------------
 * @Name rmPipeDelete
 @pstart
 RMenum rmPipeDelete (RMpipe *toDelete)
 @pend

 @astart
 RMpipe *toDelete - a handle to an RMpipe to be deleted.
 @aend

 @dstart

 Releases resources associated with an RMpipe object. This is the
 opposite of rmPipeNew(). Returns RM_WHACKED if the input RMpipe is
 NULL.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeDelete (RMpipe *p)
{
    if (RM_ASSERT(p, "rmPipeDelete() error: the input RMpipe is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    /*
     * we could potentially do more work, like close the window, close
     * the display, etc. but for now (Jan 2000) we'll just do the free().
     */
    rmPipeClose(p);

    /* new in 1.6.0, free the capabilities struct if present. */
    if (p->caps != NULL)
    {
	free((void *)p->caps);
	p->caps = NULL;
    }
    free((void *)p);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmPipeMakeCurrent
 @pstart
 RMenum rmPipeMakeCurrent (RMpipe *toUse)
 @pend

 @astart
 RMpipe *toUse - a handle to an RMpipe object (input).
 @aend

 @dstart

 Use this routine to make a named RMpipe "active." Making an RMpipe
 "active" involves three primary processing stages. First, we take steps
 to either verify that an OpenGL context is current already, or we make
 current the OpenGL context assigned to the RMpipe. Second, we initialize
 the RMpipe's context cache. Third, we initialize some OpenGL variables
 needed by RM for rendering.

 This routine will return a value of RM_CHILL to indicate that RM and OpenGL
 are ready for rendering. A value of RM_WHACKED is returned if either of the
 following is true: (1) RM was unable to initialize an OpenGL rendering
 context and/or make it current; (2) RM was unable to initialize its
 internal context cache and component manager. Both conditions (1) and (2)
 must be satisfied before rendering may occur. A return value of RM_WHACKED
 means that the RMpipe is not ready for use in rendering.

 All applications will use this routine to make an RMpipe "active."

 In the first stage of processing, we first check to see if there is an
 already "current" OpenGL context. If so, we do not disturb it, but just
 go ahead and use it. If no OpenGL context is already current, then we will
 attempt to make current the OpenGL context assigned to the RMpipe. This step
 requires the presence of a Window or HWND parameter in the X11 and Windows
 environments, respectively.

 In the second stage of processing, this routine will check for the existance 
 of an RMpipe "context cache," and will create a new one if one does not exist. 
 If a context cache exists, it will be flushed and replaced with a new one.
 
 The context cache is used internally by RM to store retained mode, OpenGL 
 context-specific data, such as display list indices, texture object identifiers, 
 and so forth.  The reason for creating/replacing/activating the context cache
 in this routine, rather than earlier (when the OpenGL context is created) is 
 due to the inconsistencies between X11 and Win32 in how OpenGL is initialized. 
 In X11, the GLX context must be created prior to creating an X11 window 
 (XCreateWindow needs an XVisualInfo structure, which reflects the pixel format 
 chosen with glXChooseVisual). In Win32, the equivalent of an XVisualInfo structure 
 is created using a window handle.

 In nearly all applications, this routine will be called only once. It is not an 
 error call it multiple times, but developers must be aware that any existing 
 retained mode structures will be flushed by rmPipeMakeCurrent, and rebuilt 
 during subsequent renderings.

 This routine *must* be called by all applications between
 rmPipeNew() (RMpipe initialization) and rmFrame() (rendering).

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeMakeCurrent (RMpipe *pipe)
{
    RMenum haveActiveContext=RM_FALSE;
    
    if (RM_ASSERT(pipe, "rmPipeMakeCurrent() error: the input RMpipe is NULL") == RM_WHACKED)
	return RM_WHACKED;
    
#ifdef RM_X
    if (private_rmPipeGetPlatform(pipe) == RM_PIPE_GLX)
    {
	/* first, check to see if the assigned context is active. */
	if ((haveActiveContext = private_rmCheckForActiveContext()) != RM_TRUE)
	{
	    /* don't have an active context. See if we have enough to make an
	     active context. */
	    Window w = rmPipeGetWindow(pipe);
	    GLXContext ctx = rmPipeGetContext(pipe);
	    Display *d = rmxPipeGetDisplay(pipe);

	    if ((w != 0) && (ctx != NULL) && (d != NULL))
	    {
		if (glXMakeCurrent(rmxPipeGetDisplay(pipe), rmPipeGetWindow(pipe), rmPipeGetContext(pipe)) == True)
		    haveActiveContext = RM_TRUE;
	    }
	    else
	    {
		rmWarning("rmPipeMakeCurrent (X11): we don't have an active OpenGL context, and don't have enough variables set in the RMpipe to make an active context. We need: (1) an open Display (see rmxPipeSetDisplay), (2) a window or drawable (see rmPipeSetWindow), and (3) an OpenGL context (see rmPipeSetContext).");
		return RM_WHACKED;
	    }
	}
    }
#endif
#ifdef RM_WIN
    if (private_rmPipeGetPlatform(pipe) == RM_PIPE_WGL)
    {
	/* first, check to see if the assigned context is active. */
	if ((haveActiveContext = private_rmCheckForActiveContext()) != RM_TRUE)
	{
	    /* don't have an active context. See if we have enough to make an
	     active context. */
	    HWND hWnd = rmPipeGetWindow(pipe);
	    HGLRC ctx = rmPipeGetContext(pipe);
	    
	    if ((hWnd != 0) && (ctx != NULL))
	    {
		if (wglMakeCurrent (pipe->hdc, ctx) == TRUE)
		    haveActiveContext = RM_TRUE;
		else
		{
		    private_rmwCheckAndDisplayLastError("rmPipeMakeCurrent: After wglMakeCurrent");
		    return RM_WHACKED;
		}
	    }
	    else 
	    {
		rmWarning("rmPipeMakeCurrent (Win32): we don't have an active OpenGL context, and at least one of an OpenGL context and/or window has not been assigned to the RMpipe.");
		return RM_WHACKED;
	    }
	}
    }
#endif
    
#ifdef RM_CR
    /* special code for making the context current in Chromium */
    if (private_rmPipeGetPlatform(pipe) == RM_PIPE_CR)
	haveActiveContext = rmPipeMakeCurrentCR(pipe);
#endif

    if (private_rmPipeGetPlatform(pipe) == RM_PIPE_NOPLATFORM)
    {
	if ((haveActiveContext = private_rmCheckForActiveContext()) != RM_TRUE)
	{
	    rmError("rmPipeMakeCurrent error -- for your RM_PIPE_NOPLATFORM RMpipe, it does not appear there is a current, active OpenGL context. Therefore, OpenGL is not initialized and ready for use by OpenRM Scene Graph. ");
	    return RM_WHACKED;
	}
    }

    if (haveActiveContext == RM_TRUE)
    {
#ifdef RM_WIN
	glPixelStorei(GL_PACK_ALIGNMENT, 4);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 4); /* say's that we're word aligned on each scanline */
#endif
#ifdef RM_X
	glPixelStorei(GL_PACK_ALIGNMENT, 1);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1); /* say's that we're byte aligned on each scanline */
#endif
    }

    /* at this point, wstat == 1 means we have a valid, active OpenGL
       context. if wstat == 0, OpenGL is not ready to go. */

    /*
     * flush any existing context cache.
     */
    if (pipe->contextCache != NULL && haveActiveContext == RM_TRUE)
	private_rmCacheFlush(pipe->contextCache);

    /*
     * then build a new one.
     */
    if (private_rmCacheInit(&(pipe->contextCache)) != RM_CHILL)
    {
	rmError("rmPipeMakeCurrent - unable to initialize the RMpipe context cache. Please file a bug report.");
	return RM_WHACKED;
    }

    /*
     * load prebuilt display lists for quadrics objects into the
     * context cache of each pipe.
     */

    if (pipe->contextCache != NULL && haveActiveContext == RM_TRUE)
	private_rmInitQuadrics(pipe->contextCache);

    /* 
     * if we have a valide OpenGL context and the component manager/context
     * cache is ready to go, go ahead and obtain the capabilities for
     * the RMpipe object.
     */
    if (haveActiveContext == RM_TRUE)
    {
	RMpipeOGLCapabilities *caps = (RMpipeOGLCapabilities *)calloc(1,sizeof(RMpipeOGLCapabilities));
	private_rmPipeGetCapabilities(caps);
	pipe->caps = caps;
    }

    /*
     * and get rid of any lingering multistage display lists. These
     * can be lingering in the event the app calls rmPipeMakeCurrent
     * on an existing pipe. Note that these things are *not* OpenGL
     * display lists. They are basically opcode buffers used to hold
     * data passed between stages in  OpenRM's multistage renderer.
     */
    private_rmPipeDisplayListsDelete(pipe);
    
    /*
     * return status of 1 indicates that we have both initialized the
     * context cache as well as the OpenGL context, and that RM is ready
     * to render stuff. A value of 0 means that either one of these two
     * init operations failed, and that RM cannot render anything.
     */
    return( (haveActiveContext == RM_TRUE) ? RM_CHILL : RM_WHACKED );
}


/*
 * ----------------------------------------------------
 * @Name rmPipeClose
 @pstart
 RMenum rmPipeClose(RMpipe *toClose)
 @pend

 @astart
 RMpipe *toClose - a handle to an RMpipe (modified).
 @aend

 @dstart

 This routine will destroy the OpenGL rendering context associated
 with the input RMpipe object, and delete all resources associated
 with the RMpipe's context cache, including all OpenGL display lists
 and texture object id's.

 On X11, this routine will also close the X Display associated with
 the RMpipe.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeClose (RMpipe *toClose)
{
    /* deletes an OpenGL rendering context */
    if (RM_ASSERT(toClose, "rmPipeClose() error: the input RMpipe is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    /*
     * we want to first shut down any rendering threads so that we can
     * reattach to the OpenGL context in order to delete display lists, etc.
     */

    /* temp - assume that the rendering stuff is serial, no parallel stuff.
     TODO: we need to add a call that will close down the rendering threads,
     if any. this call will return control of the OpenGL rendering
     context to the caller (us) so that we can do stuff to the
     OpenGL rendering context. 7/4/01 w.bethel */

    if (toClose->contextCache != NULL)
    {
	private_rmCacheDelete(toClose, toClose->contextCache);
	toClose->contextCache = NULL;
    }

    if (toClose->shutdownFunc != NULL)
	toClose->shutdownFunc(toClose);

    /*
     * free up resources associated with view-render buffers
     */
    if (toClose->displayLists != NULL)
    {
	private_rmPipeDisplayListsDelete(toClose);
	toClose->displayLists = NULL;
    }
    
#if DEBUG_RSYNC
    {
	FILE *f=fopen("timeSync.txt","w");
	int i, n;
	double d;
	int thisUn, nextUn;

	/* need to weave in some code to test for start != 0, which
	   would be caused by wrapping around in the ring buffer */

	if (timeSyncIndx >= MAX_RSYNC_SAMPLES)
	{
	    n = MAX_RSYNC_SAMPLES;
	    thisUn = (timeSyncIndx+1) % MAX_RSYNC_SAMPLES;
	}
	else
	{
	    n = timeSyncIndx;
	    thisUn = 0;
	}

	nextUn = (thisUn + 1) % MAX_RSYNC_SAMPLES;

	for (i=0; i<n-1; i++)
	{
	    if (i == 0)
		d = 0.;
	    else
		d = rmTimeDifferenceMS(&(timeSyncData[thisUn].t), 
				       &(timeSyncData[nextUn].t));
	    fprintf(f,"%d\t%g\n", timeSyncData[thisUn].frameNumber, d);

	    thisUn++;
	    thisUn = thisUn % MAX_RSYNC_SAMPLES;
	    nextUn++;
	    nextUn = nextUn % MAX_RSYNC_SAMPLES;
	}

	fclose(f);
	timeSyncIndx = 0;
    }
#endif
    
    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmPipeSwapBuffers
 @pstart
 RMenum rmPipeSwapBuffers (RMpipe *p)
 @pend

 @astart
 RMpipe *p - a handle to an RMpipe (input).
 @aend

 @dstart

 This routine will cause a buffer-swap on the display and window
 specified by the input RMpipe.

 In the interest of speed, no error checking is performed inside this
 routine.

 This routine is most typically called from within a rendering manager
 routine, and not directly from the application. There is nothing that
 precludes it's use directly by applications, if so desired.


 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeSwapBuffers(const RMpipe *p)
{
    if (RM_ASSERT(p, "rmPipeSwapBuffers() error: the input RMpipe is NULL.") == RM_WHACKED)
	return(RM_WHACKED);
#if 0
    if (RM_ASSERT(p->swapBuffersFunc, "rmPipeSwapBuffers() warning: there is no swapbuffers function assigned to the input RMpipe.") == RM_WHACKED)
	return RM_WHACKED;
#endif
    
    if (p->swapBuffersFunc != NULL)
	p->swapBuffersFunc(p);
    
    return RM_CHILL;
}


/*
 * ----------------------------------------------------
 * @Name rmPipeSetChannelFormat
 @pstart
 RMenum rmPipeSetChannelFormat (RMpipe *toModify,
		                RMenum channelFormat)
 @pend

 @astart
 RMpipe *toModify - a handle to an RMpipe object (modified).

 RMenum channelFormat - an RMenum value specifying a display
    format. May be one of RM_MONO_CHANNEL, RM_REDBLUE_STEREO_CHANNEL,
    RM_BLUERED_STEREO_CHANNEL, or RM_MBUF_STEREO_CHANNEL.  @aend

 @dstart

 Use this routine to set the "channel format" of an RMpipe object.
 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 The RMpipe channel format serves two purposes. First, when
 multibuffered stereo is requested, this information must be known
 prior to creating an OpenGL rendering context. So, the channel format
 provides hints about the type of OpenGL capabilities that are needed
 by the application.  As such, it is likely that the channel format
 interface will evolve over time to include more access to additional
 OpenGL capabilities.

 Second, when a channel format is specified, internal to
 rmPipeSetChannelFormat(), an RM rendering callback is assigned to the
 pipe. The rendering callback knows about multipass rendering as well
 as how to instruct the underlying rendering engine in the details of
 managing stereo rendering.

 This routine must be called between the time you create the RMpipe with
 rmPipeNew(), and before an OpenGL rendering context is created with
 rmPipeCreateContext(). It is a logic error to change an RMpipe's channel
 format after the OpenGL context has been created.


 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeSetChannelFormat (RMpipe *p,
		        RMenum newFormat)
{
    RMenum rstat = RM_CHILL;
#if 0
    RMenum oldFormat = p->channel_format;

    if (oldFormat == newFormat)
	return RM_CHILL;
#endif    
    /*
     * and get rid of any lingering multistage display lists. These
     * can be lingering in the event the app calls rmPipeMakeCurrent
     * on an existing pipe. Note that these things are *not* OpenGL
     * display lists. They are basically opcode buffers used to hold
     * data passed between stages in  OpenRM's multistage renderer.
     */
    private_rmPipeDisplayListsDelete(p);
    
    if (RM_ASSERT(p, "rmPipeSetChannelFormat() error: the input RMpipe pointer is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    switch (newFormat)
       {
       case RM_MONO_CHANNEL:
       case RM_REDBLUE_STEREO_CHANNEL:
       case RM_BLUERED_STEREO_CHANNEL:
       case RM_MBUF_STEREO_CHANNEL:
	   p->channel_format = newFormat;
	   rstat = RM_CHILL;
	   /* need to reset the swapbuffers func here? */
	   break;
	   
       case RM_OFFSCREEN_MONO_CHANNEL:
       case RM_OFFSCREEN_REDBLUE_STEREO_CHANNEL:
       case RM_OFFSCREEN_BLUERED_STEREO_CHANNEL:
	   p->offscreen = RM_TRUE;
	   p->channel_format = newFormat;
	   rmPipeSetSwapBuffersFunc(p, NULL); /* turn off swapbuffers when
						 rendering offscreen */
	   rstat = RM_CHILL;
	   break;
	  
       default:
	  rmError("rmPipeSetChannelFormat() error: the input channel format is not recognized.");
	  return (RM_WHACKED);
       }

    if (rstat == RM_CHILL)
	private_rmSetChannelRenderFunc(p);

    return(rstat);
}


/*
 * ----------------------------------------------------
 * @Name rmPipeGetChannelFormat
 @pstart
 RMenum rmPipeGetChannelFormat (const RMpipe *toQuery)
 @pend

 @astart
 const RMpipe *toQuery - a handle to an RMpipe object (input).
 @aend

 @dstart

 Returns to the caller the current channel format of an RMpipe
 object. A successful return value will be one of RM_MONO_CHANNEL,
 RM_REDBLUE_STEREO_CHANNEL, RM_BLUERED_STEREO_CHANNEL or
 RM_MBUF_STEREO_CHANNEL. A return value of RM_WHACKED indicates an
 error of some type.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeGetChannelFormat (const RMpipe *p)
{
    if (RM_ASSERT(p, "rmPipeGetChannelFormat() error: the input RMpipe pointer is NULL.") == RM_WHACKED)
	return(RM_WHACKED);
    
    return(p->channel_format);
}

/*
 * ----------------------------------------------------
 * @Name rmPipeSetInitMatrixStackMode
 @pstart
 RMenum rmPipeSetInitMatrixStackMode(RMpipe *toModify,
			             RMenum newMode)
 @pend

 @astart
 RMpipe *toModify - a handle to an RMpipe object (modified).
 RMenum newMode - an RMenum value, either RM_TRUE or RM_FALSE, that
    controls how matrix transformations are applied to the OpenGL
    matrix stack.
 @aend

 @dstart

 By default, OpenRM will initialize the OpenGL matrix stack during
 a frame rendering operation by setting GL_MODELVIEW, GL_PROJECTION and
 GL_TEXTURE matrices to the Identity matrix. This behavior may be
 overridden, so that OpenRM will honor any existing matrices in
 the matrix stacks while accumulating nested transformations. This is
 helpful in some deployment environments, such as combining OpenRM
 with CAVElib (www.vrco.com).

 When the input enumerator is set to RM_TRUE (the default), OpenRM
 will initialize the OpenGL matrix stack to the identity matrix prior
 to accumulating transformations during rendering. When the input enum
 is set to RM_FALSE, OpenRM will NOT initialize the matrix stack, and
 any existing transformations within the scene graph will be
 accumulated with the contents of the matrix stack at render time.

 More precisely, if the GL_MODELVIEW matrix stack has the matrix M,
 and the scene graph defines a matrix transformation S, the combined
 transformation will be S*M (left multiplication), so that the scene
 graph transformation S is effectively applied prior to the outer
 transformation M when vertex data moves down the OpenGL transformation
 pipeline.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeSetInitMatrixStackMode(RMpipe *t,
			     RMenum newMode)
{
    if (RM_ASSERT(t,"rmPipeSetInitMatrixStackMode() error: the input RMpipe is NULL. ") == RM_WHACKED)
	return(RM_WHACKED);

    if ((newMode != RM_TRUE) && (newMode != RM_FALSE))
    {
	rmError("rmPipeSetInitMatrixStackMode() error: the input newMode RMenum is neither RM_TRUE nor RM_FALSE");
	return(RM_WHACKED);
    }
    
    t->initMatrixStack = newMode;	/* RM_TRUE or RM_FALSE */
    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmPipeGetInitMatrixStackMode
 @pstart
 RMenum rmPipeGetInitMatrixStackMode(const RMpipe *toQuery)
 @pend

 @astart
 const RMpipe *toQuery - a handle to an RMpipe object (queried).
 @aend

 @dstart
 This routine will return the "init matrix stack" attribute of an
 RMpipe object. A return value of RM_TRUE means the OpenGL matrix
 stack will be initialized by OpenRM during frame rendering, while
 a value of RM_FALSE means that any existing values in the OpenGL
 matrix stack will be honored during rendering.

 A return value of RM_WHACKED indicates an error condition.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeGetInitMatrixStackMode(const RMpipe *t)
{
    if (RM_ASSERT(t,"rmPipeGetInitMatrixStackMode() error: the input RMpipe is NULL. ") == RM_WHACKED)
	return(RM_WHACKED);
    
    return(t->initMatrixStack);
}

/*
 * ----------------------------------------------------
 * @Name rmPipeSetRenderPassEnable
 @pstart
 RMenum rmPipeSetRenderPassEnable(RMpipe *toModify,
			          RMenum opaque3DEnable,
				  RMenum transparent3DEnable,
				  RMenum opaque2DEnable)
 @pend

 @astart
 RMpipe *toModify - a handle to an RMpipe (modified).

 RMenum opaque3DEnable,transparent3DEnable, opaque2DEnable - RMenum
    values, may be either RM_TRUE or RM_FALSE. 
 @aend

 @dstart

 This routine is used to selectely enable or disable one of the
 rendering passes of the RM multipass rendering engine. Applications
 may not change the order of the rendering passes, but may selectively
 enable or disable a given pass. The order of the passes is:

 1. 3D Opaque

 2. 3D Transparent

 3. 2D Opaque

 (There may be a 2D transparent pass in a later release.)

 During each of these passes, traversal filters are applied at each
 node of the RM scene graph. If, at any stage during the traversal,
 the scene graph node does not pass the traversal filter test, that
 node, and any descendents, are not processed.

 For this reason, developers should carefully consider scene graph
 design such that 3D opaque, 3D transparent and 2D objects are
 appropriately partitioned within the scene graph itself.

 Background scene operations (background clear color, background image
 tile, background depth value, background depth image) are performed
 during rendering passes 1 and 3. A common RM error is to place a
 background scene operation at a node that is processed during
 multiple rendering passes.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeSetRenderPassEnable (RMpipe *t,
			   RMenum opaque3DEnable,
			   RMenum transparent3DEnable,
			   RMenum opaque2DEnable)
{
    if (RM_ASSERT(t, "rmPipeSetRenderPassEnable() error: the input RMpipe is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    t->opaque3DEnable = opaque3DEnable;
    t->transparent3DEnable = transparent3DEnable;
    t->opaque2DEnable = opaque2DEnable;
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmPipeGetRenderPassEnable
 @pstart
 RMenum rmPipeGetRenderPassEnable(const RMpipe *t,
			          RMenum *opaque3DEnableReturn,
				  RMenum *transparent3DEnableReturn,
				  RMenum *opaque2DEnableReturn)

 @pend

 @astart
 const RMpipe *t - a handle to an RMpipe (input).

 RMenum *opaque3DEnableReturn, *transparent3DEnableReturn,
    *opaque2DEnableReturn - handles to RMenum values (result). 
 @aend

 @dstart

 This routine is used to obtain the boolean values that indicate if
 a particular rendering pass is enabled in the RMpipe.

 Each of the return parameters is optional - a value of NULL will skip
 reporting of that particular rendering pass.

 Upon success, RM_CHILL is returned, and RM_TRUE or RM_FALSE is copied
 into non-NULL caller-supplied memory for each of the RMpipe rendering
 passes.  Otherwise, RM_WHACKED is returned, and caller-supplied
 memory remains unmodified.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeGetRenderPassEnable (const RMpipe *t,
			   RMenum *opaque3DEnableReturn,
			   RMenum *transparent3DEnableReturn,
			   RMenum *opaque2DEnableReturn)
{
    if (RM_ASSERT(t, "rmPipeSetGenderPassEnable() error: the input RMpipe is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    if (opaque3DEnableReturn != NULL)
	*opaque3DEnableReturn = t->opaque3DEnable;
    if (transparent3DEnableReturn != NULL)
	*transparent3DEnableReturn = t->transparent3DEnable;
    if (opaque2DEnableReturn != NULL)
	*opaque2DEnableReturn = t->opaque2DEnable;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmPipeSetWindowSize
 @pstart
 RMenum rmPipeSetWindowSize (RMpipe *toModify,
		             int newWidth,
			     int newHeight)
 @pend

 @astart
 RMpipe *toModify - a handle to an RMpipe (modified).

 int newWidth, newHeight - integer values specifing the pixel width
    and height of the window associated with an RMpipe (input). 
 @aend

 @dstart

 This routine sets the RMpipe's notion of pixel width and height of
 it's associated display window. RM_CHILL is returned upon success, or
 RM_WHACKED upon failure.

 The most typical use of this routine will be when an application
 detects, through an event loop, that the window geometry has
 changed. The application is reponsible for notifying RM of such
 changes; RM doesn't manage events and doesn't keep track of the size
 of the display window.

 This routine is called by rmPipeSetWindow().

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeSetWindowSize (RMpipe *pipe,
		     int width,
		     int height)
{
    if (RM_ASSERT(pipe, "rmPipeSetWindowSize() error: the input RMpipe object is NULL.") == RM_WHACKED)
	return(RM_WHACKED);
	    
    pipe->xwindow_width = width;
    pipe->xwindow_height = height;
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmPipeGetWindowSize
 @pstart
 RMenum rmPipeGetWindowSize (const RMpipe *toQuery,
		             int *widthReturn,
			     int *heightReturn)
 @pend

 @astart
 const RMpipe *toQuery - a handle to an RMpipe (input).

 int *widthReturn, *heightReturn - pointers to integers (return).
    Values of NULL are acceptable.
 @aend

 @dstart

 This routine returns via caller-supplied memory the named RMpipe's
 notion of the current window width and height. RM_CHILL is returned
 upon success, or RM_WHACKED upon failure.

 Callers interested in only width or height may specify NULL for the
 parameter for which information is not requested.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeGetWindowSize (const RMpipe *pipe,
		     int *width,
		     int *height)
{
    if (RM_ASSERT(pipe, "rmPipeGetWindowSize() error: the input RMpipe is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    
    if (width != NULL)
	*width = pipe->xwindow_width;
    if (height != NULL)
	*height = pipe->xwindow_height;
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmPipeSetSwapBuffersFunc
 @pstart
 RMenum rmPipeSetSwapBuffersFunc (RMpipe *toModify,
			          void (newFunc)(RMpipe *))
 @pend

 @astart
 RMpipe *toModify - a handle to an RMpipe (modified).

 void (newFunc)(RMpipe *) - a handle to an application callback
    (input). 
 @aend

 @dstart

 Use this routine to set the "swapbuffers" callback function
 associated with an RMpipe object. Returns RM_CHILL upon success, or
 RM_WHACKED upon failure.

 The swapbuffers function is invoked after rendering has completed,
 and after the post-render barrier function, and the post-render
 framebuffer and depthbuffer grab callbacks, if any. By default,
 rmSwapBuffers() is assigned to all RMpipe objects' swapbuffers
 callback functions at the time the RMpipe is created with RMpipe
 new. The function rmSwapBuffers() calls the native window-system
 procedure that causes front and back buffers to be swapped.

 If the application overrides the default swapbuffers function, the
 application callback will be provided a single input parameter: a
 handle to an RMpipe object. The RMpipe object contains enough
 information to enable application code to do the swapbuffers call.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeSetSwapBuffersFunc (RMpipe *p,
			  RMenum (*sbfunc)(const RMpipe *))
{
#if 0
    if ((RM_ASSERT(p, "rmPipeSetSwapBuffersFunc() error: the input RMpipe is NULL") == RM_WHACKED) ||
	(RM_ASSERT((void *)sbfunc, "rmPipeSetSwapBuffersFunc() error: the input function handle is NULL") == RM_WHACKED))
	return(RM_WHACKED);
#endif
    /* cave change: allow for setting a NULL swapbuff func */
    if (RM_ASSERT(p, "rmPipeSetSwapBuffersFunc() error: the input RMpipe is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    
    p->swapBuffersFunc = sbfunc;
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmPipeSetPostRenderFunc
 @pstart
 RMenum rmPipeSetPostRenderFunc (RMpipe *toModify,
			         void (*postRenderFunc)(const RMimage *, RMenum))
 @pend

 @astart
 RMpipe *toModify - a handle to an RMpipe (modified).

 void (*postRenderFunc)(RMimage *, RMenum) - a handle to an
    application callback.
 @aend

 @dstart

 This routine assigns a "post render" application callback to the
 RMpipe. The post render callback is invoked after rendering has
 occured, and after the "post render barrier function" has been called
 (if any), but before the "post render depthbuffer function" (if any)
 and before the swapbuffers function. RM_CHILL is returned upon
 success, or RM_WHACKED upon failure.

 Use a value of NULL for the postRenderFunc to effectively disable
 post rendering callbacks.

 The purpose of the post render function is to allow applications to
 grab a copy of the color planes of the framebuffer after rendering
 has occured. If you want the raw image data produced by the
 rendering, this is the path to use.

 When the application callback is invoked, the callback is invoked
 with two parameters. The first is a handle to an RMimage object. Use
 rmImageGetPixelData() to obtain the raw pixel data from this image.
 At this time (Jan 2000) the image provided to the app callback is in
 GL_RGBA, GL_UNSIGNED_BYTE format. Applications are advised to use the
 rmImageGet*() series of routines to obtain RMimage configuration
 information. In particular, pay close attention to scanline-padding
 issues.

 The second parameter provided to the application callback is an
 RMenum value that is one of RM_ALL_CHANNELS, RM_LEFT_CHANNEL or
 RM_RIGHT_CHANNEL. Multibuffered stereo channels will invoke the
 application callback twice, once for each channel. Anaglyph stereo
 formats (RM_REDBLUE_STEREO_CHANNEL and RM_BLUERED_STEREO_CHANNEL)
 will trigger the callback just once. This may change in the future
 (Jan 2000).

 The RMimage object provided to applications is managed by RM.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeSetPostRenderFunc (RMpipe *p,
			 void (*prfunc)(const RMimage *, RMenum))
{
    if (RM_ASSERT(p, "rmPipeSetPostRenderFunc() error: the input RMpipe object is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    
    p->postrenderfunc = prfunc;
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmPipeSetPostRenderDepthFunc
 @pstart
 RMenum rmPipeSetPostRenderDepthFunc (RMpipe *toModify,
			              void (*postRenderDepthFunc)(const RMimage *, RMenum))
 @pend

 @astart
 RMpipe *toModify - a handle to an RMimage object (modified).

 void (*postRenderDepthFunc)(RMimage *, RMenum) - a handle to an
    application callback (input). 
 @aend

 @dstart

 Use this routine to assign a "post render depth buffer" callback to
 an RMpipe object. RM_CHILL is returned upon success, or RM_WHACKED
 upon failure.

 Use a value of NULL for the postRenderDepthFunc to effectively
 disable this post rendering callback.
 
 Whereas the "post render callback" gives applications access to the
 color planes of the framebuffer after rendering, the purpose of this
 callback is to give applications access to the depth buffer after
 rendering has completed. Assigning a "post render depth buffer
 callback" will cause the application callback to be invoked after
 rendering, and after the post-render barrier function, after the post
 render callback (the color planes) but before the swapbuffers
 function.

 The pixel data of the RMimage object provided to the application, the
 first application callback parameter, contains the contents of the
 depth buffer of the scene just rendered. The pixel data in the
 RMimage object is in RM_FLOAT format. Applications should use the
 rmImageGet*() series of routines to query specific RMimage
 attributes.
 
 The second parameter provided to the application callback is an
 RMenum value that is one of RM_ALL_CHANNELS, RM_LEFT_CHANNEL or
 RM_RIGHT_CHANNEL. Multibuffered stereo channels will invoke the
 application callback twice, once for each channel. Anaglyph stereo
 formats (RM_REDBLUE_STEREO_CHANNEL and RM_BLUERED_STEREO_CHANNEL)
 will trigger the callback just once. This may change in the future
 (Jan 2000).

 The RMimage object provided to applications is managed by RM.

 @dend
 * ---------------------------------------------------- */
RMenum
rmPipeSetPostRenderDepthFunc (RMpipe *p,
			      void (*prfunc)(const RMimage *, RMenum))
{
    if (RM_ASSERT(p, "rmPipeSetPostRenderDepthFunc() error: the input RMpipe object is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    
    p->postrender_depthbufferfunc = prfunc;
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmPipeSetPostRenderBarrierFunc 
 @pstart
 RMenum rmPipeSetPostRenderBarrierFunc (RMpipe *toModify,
			                void (*barrierFunc)(RMpipe *))
 @pend

 @astart
 RMpipe *toModify - a handle to an RMpipe object (modified).

 void (*barrierFunc)(RMpipe *) - a handle to an application callback
    (input).

 @aend

 @dstart

 Use this routine to set the "post render barrier function" on an
 RMpipe object. Use a value of NULL for barrierFunc to disable this
 callback, or remove a previously installed callback. Returns RM_CHILL
 upon success, or RM_WHACKED upon failure.

 The "post render barrier" callback, if present, will be invoked
 immediately after rendering, but prior to any other callbacks (post
 render color planes callback, post render depth buffer callback or
 swapbuffers). This routine is intended for use by parallel rendering
 applications.

 The application callback will be provided a single parameter, an
 RMpipe.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeSetPostRenderBarrierFunc (RMpipe *t,
			        void (*barrierFunc)(RMpipe *))
{
    if (RM_ASSERT(t, "rmPipeSetPostRenderBarrierFunc error: the input RMpipe * is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    t->postRenderBarrierFunc = barrierFunc;
    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmPipeProcessingModeIsMultithreaded
 @pstart
 RMenum rmPipeProcessingModeIsMultithreaded (const RMpipe *toQuery)
 @pend

 @astart
 const RMpipe *toQuery - a handle to an RMpipe (input).
 @aend

 @dstart
 This routine can be used to determine if the processing mode of
 the RMpipe toQuery is a multithreaded format. If toQuery's
 processing mode is either RM_PIPE_MULTISTAGE_VIEW_PARALLEL or
 RM_PIPE_MULTISTAGE_PARALLEL, this routine will return RM_TRUE.
 Otherwise, RM_FALSE is returned.

 See also rmPipeGetProcessingMode().
 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeProcessingModeIsMultithreaded(const RMpipe *toQuery)
{
    RMenum pmode = rmPipeGetProcessingMode(toQuery);

    if ((pmode == RM_PIPE_MULTISTAGE_VIEW_PARALLEL) ||
	(pmode == RM_PIPE_MULTISTAGE_PARALLEL))
	return(RM_TRUE);
    else
	return(RM_FALSE);
}

/*
 * ----------------------------------------------------
 * @Name rmPipeGetProcessingMode
 @pstart
 RMenum rmPipeGetProcessingMode (const RMpipe *toQuery)
 @pend

 @astart
 const RMpipe *toQuery - a handle to an RMpipe object (input).
 @aend

 @dstart
 This routine will return the processing mode of the RMpipe toQuery.
 Upon success, one of the following is returned to the caller:
 RM_PIPE_SERIAL, RM_PIPE_MULTISTAGE, RM_PIPE_MULTISTAGE_PARALLEL or
 RM_PIPE_MULTISTAGE_VIEW_PARALLEL.

 Upon failure, RM_WHACKED is returned to the caller.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeGetProcessingMode(const RMpipe *toQuery)
{
    if (RM_ASSERT(toQuery,"rmPipeGetProcessingMode() error: the input RMpipe is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    return(toQuery->processingMode);
}

/*
 * ----------------------------------------------------
 * @Name rmPipeSetProcessingMode
 @pstart
 RMenum rmPipeSetProcessingMode (RMpipe *toModify,
			         RMenum newMode)
 @pend

 @astart
 RMpipe *toModify - a handle to an RMpipe (modified).
 RMenum newMode - a RMenum specifying a processing mode. This value must
 be one of:  RM_PIPE_SERIAL, RM_PIPE_MULTISTAGE, RM_PIPE_MULTISTAGE_PARALLEL
 or RM_PIPE_MULTISTAGE_VIEW_PARALLEL.

 @aend

 @dstart
 This routine will select the "processing mode" for an RMpipe object.
 The processing mode assigns a rendering engine to the RMpipe for use
 in subsequent rendering operations. Upon success, RM_CHILL is returned
 to the caller; upon failure, RM_WHACKED is returned.

 Applications may set the processing mode on an RMpipe any time between
 when it is created (using either rmPipeNew or rmPipeInit) and the
 first time a frame is rendered. As of this time (June 2001), it is
 not possible to change the processing mode of an RMpipe, and this routine
 will not detect this error condition. Your application will likely
 crash if you attempt to alter the processing mode of an RMpipe after
 the first frame has been drawn.

 OpenRM uses a two-stage rendering pipeline: a view traversal of the
 scene graph prepares a list of things to be drawn during a rendering
 traversal.

 The following processing modes are supported:

 RM_PIPE_MULTISTAGE - uses a two-stage rendering traversal (view, render), and
 both stages are called sequentially within the same process/thread as
 the caller. There is no parallelization in this processing mode.

 RM_PIPE_MULTISTAGE_PARALLEL - each of the two rendering stages are called
 from detached Posix threads. The render thread will assume ownership of
 the OpenGL context (applications should NOT make any OpenGL calls except
 from within node callbacks invoked by the OpenRM renderer). This mode
 is fully parallelized.

 RM_PIPE_MULTISTAGE_VIEW_PARALLEL - the view traversal is assigned to a
 detached thread, while the render traversal remains in the same
 execution process/thread as the caller (and does not attempt to exert
 ownership of the OpenGL rendering context). This mode is also fully
 parallelized, and is quite useful when combining OpenRM with other
 toolkits that provide device and event management and which make
 assumptions about ownership of the OpenGL rendering context (e.g.,
 CAVELibrary, VRJuggler, FLTK, etc.).
 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeSetProcessingMode(RMpipe *toModify,
			RMenum newMode)
{
    if (RM_ASSERT(toModify,"rmPipeSetProcessingMode() error: the input RMpipe is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    if ((newMode != RM_PIPE_SERIAL) && (newMode != RM_PIPE_MULTISTAGE) && (newMode != RM_PIPE_MULTISTAGE_PARALLEL) && (newMode != RM_PIPE_MULTISTAGE_VIEW_PARALLEL))
    {
	rmError("rmPipeSetProcessingMode() error: the input processing mode is not valid.");
	return(RM_WHACKED);
    }

#ifdef _NO_PTHREADS
    /*
     * if we're not using or don't have access to pthreads, permit use of
     * only one of the single-threaded processing modes.
     */
    if ((newMode == RM_PIPE_MULTISTAGE_PARALLEL) || (newMode == RM_PIPE_MULTISTAGE_VIEW_PARALLEL))
    {
        rmWarning("You are using a version of RM Scene Graph that was built without threads support and have requested an RMpipe processing mode that requires threading. The RMpipe processing mode you will be using instead is RM_PIPE_MULTISTAGE.");
	newMode = RM_PIPE_MULTISTAGE;
    }
#endif

    toModify->processingMode = newMode;

    /* need to update the renderfunc based on new processing mode */
    rmPipeSetChannelFormat(toModify, rmPipeGetChannelFormat(toModify));
    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmPipeGetDisplayListEnable
 @pstart
 RMenum rmPipeGetDisplayListEnable (const RMpipe *toQuery)
 @pend

 @astart
 const RMpipe *toQuery - a handle to an RMpipe object (input).
 @aend

 @dstart
 This routine will return the RMpipe's notion of whether or not it will use
 OpenGL display lists when rendering primitives. The returned value will be
 either RM_TRUE, which indicates that display lists will be used when
 rendering primitives on the RMpipe, or RM_FALSE, which indicates that no
 RMprimitives will be drawn using display lists.

 See also: rmPipeSetDisplayListEnable, rmPrimitiveSetDisplayListEnable.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeGetDisplayListEnable(const RMpipe *toQuery)
{
    if (RM_ASSERT(toQuery,"rmPipeGetDisplayListEnable() error: the input RMpipe is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    return(toQuery->displayListEnableBool);
}

/*
 * ----------------------------------------------------
 * @Name rmPipeSetDisplayListEnable
 @pstart
 RMenum rmPipeSetDisplayListEnable (RMpipe *toModify,
			            RMenum newMode)
 @pend

 @astart
 RMpipe *toModify - a handle to an RMpipe (modified).
 RMenum newMode - a RMenum specifying whether or not RMprimitives rendered on
   the RMpipe toModify will use display lists. The input value should be
   either RM_TRUE or RM_FALSE.

 @aend

 @dstart

 Set's the RMpipe's policy on the use of display lists. When the value
 of newMode is set to RM_TRUE, use of display lists on the RMpipe toModify
 is enabled. When set to RM_FALSE, use of display lists on the RMpipe is
 disabled. The default policy for use of display lists is RM_TRUE, and this
 value is set at the time the RMpipe is created. You can override the
 default behavior by calling rmPipeSetDisplayListEnable() with a value
 of RM_FALSE.
 
 Use of display lists can greatly accelerate rendering performance on
 many platforms. By default, RM will attempt to create display lists for
 RMprimitives during rendering, and reuse them in subsequent renderings.

 Applications can control use of display lists in two ways: at the RMpipe
 level, and at the RMprimitive level. At the RMpipe level, you can 
 enable or disable use of display lists for all RMprimitives drawn on RMpipe
 using the routine rmPipeSetDisplayListEnable. At the RMprimitive level,
 you can enable or disable the use of display lists for a single primitive
 using rmPrimitiveSetDisplayListEnable().

 The RMprimitive display list  policy does not override the display list
 policy set at the RMpipe level. In other words, if the policy at the RMpipe
 level is set to RM_FALSE, then no display lists will be used, even if the
 policy at the RMprimitive level is set to RM_TRUE. On the other hand, if
 the policy at the RMpipe level is set to RM_TRUE, a policy at the RMprimitive
 level of RM_FALSE will result on no display lists being used for the
 one RMprimitive. In order for display lists to be used at any given
 RMprimitive, the logical AND of RMpipe and RMprimitive display list
 policies must be RM_TRUE.

 To obtain the current display list use policy at an RMpipe, use the routine
 rmPipeGetDisplayListEnable().

 See also rmPrimitiveSetDisplayListEnable().
 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeSetDisplayListEnable(RMpipe *toModify,
			   RMenum newMode)
{
    if (RM_ASSERT(toModify,"rmPipeSetDisplayListEnable() error: the input RMpipe is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    if ((newMode != RM_TRUE) && (newMode != RM_FALSE))
    {
	rmError("rmPipeSetDisplayListEnable() error: the new display list use policy mode is not valid.");
	return(RM_WHACKED);
    }

    toModify->displayListEnableBool = newMode;

    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmPipeSetCommSize
 @pstart
 RMenum rmPipeSetCommSize (RMpipe *toModify)
 @pend

 @astart
 RMpipe *toModify - a handle to an RMpipe object (modified).
 @aend

 @dstart

 Sets the global number of PEs in an MPI-parallel application.
 Docs need to be better written.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeSetCommSize (RMpipe *toModify,
		   int globalNPE)
{
    if (RM_ASSERT(toModify,"rmPipeSetCommSize() error: the input RMpipe is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    toModify->globalNPE = globalNPE;
    return RM_CHILL;
}

/*
 * ----------------------------------------------------
 * @Name rmPipeGetCommSize
 @pstart
 int rmPipeSetCommSize (const RMpipe *toQuery)
 @pend

 @astart
 const RMpipe *toQuery - a handle to an RMpipe object (input).
 @aend

 @dstart

 Returns the global number of PEs in an MPI-parallel application.
 Docs need to be better written.
 
 @dend
 * ----------------------------------------------------
 */
int
rmPipeGetCommSize (const RMpipe *toQuery)
{
    if (RM_ASSERT(toQuery,"rmPipeGetCommSize() error: the input RMpipe is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    return toQuery->globalNPE;
}

/*
 * ----------------------------------------------------
 * @Name rmPipeSetRank
 @pstart
 RMenum rmPipeSetRank (RMpipe *toModify)
 @pend

 @astart
 RMpipe *toModify - a handle to an RMpipe object (modified).
 @aend

 @dstart

 Sets the rank value of one RMpipe in an MPI-parallel application.
 Docs need to be better written.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeSetRank (RMpipe *toModify,
	       int myRank)
{
    if (RM_ASSERT(toModify,"rmPipeSetMyRank() error: the input RMpipe is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    toModify->myRank = myRank;
    return RM_CHILL;
}

/*
 * ----------------------------------------------------
 * @Name rmPipeGetRank
 @pstart
 int rmPipeGetRank (const RMpipe *toQuery)
 @pend

 @astart
 const RMpipe *toQuery - a handle to an RMpipe object (input).
 @aend

 @dstart

 Obtains the rank value of one RMpipe in an MPI-parallel application.
 Docs need to be better written.
 
 @dend
 * ----------------------------------------------------
 */
int
rmPipeGetRank (const RMpipe *toQuery)
{
    if (RM_ASSERT(toQuery,"rmPipeGetMyRank() error: the input RMpipe is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    return toQuery->myRank;
}

#if 0
/*
 * ----------------------------------------------------
 * @Name rmPipeGetDisplayName
 @pstart
 const char * rmPipeGetDisplayName (const RMpipe *toQuery)
 @pend

 @astart
 const RMpipe *toQuery - a handle to an RMpipe object (input).
 @aend

 @dstart

 Returns to the caller a character string defining $DISPLAY, the display
 name. $DISPLAY has an effect only on GL_PIPE_GLX platforms.
 
 @dend
 * ----------------------------------------------------
 */
const char *
rmPipeGetDisplayName(const RMpipe *toQuery)
{
    if (RM_ASSERT(toQuery,"rmPipeGetDisplayName() error: the input RMpipe is NULL") == RM_WHACKED)
	return NULL;
    
    return toQuery->displayName;
}

/*
 * ----------------------------------------------------
 * @Name rmPipeSetDisplayName
 @pstart
 RMenum rmPipeSetDisplayName (RMpipe *toModify, const char *displayName)
 @pend

 @astart
 RMpipe *toModify - a handle to an RMpipe object (modified).
 const char *displayName - character string defining name of the $DISPLAY
   to use in subsequent window system operations.
 @aend

 @dstart

 Use this routine to set the $DISPLAY variable in an RMpipe. This
 variable is used only on RM_PIPE_GLX platforms, and only when a
 Returns to the caller a character string defining $DISPLAY, the display
 name. $DISPLAY has an effect only on GL_PIPE_GLX platforms.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeSetDisplayName(RMpipe *toModify,
		     const char *displayName)
{
    if (RM_ASSERT(toModify,"rmPipeSetDisplayName() error: the input RMpipe is NULL") == RM_WHACKED)
	return RM_WHACKED;

    if (toQuery->displayName != NULL)
    {
	free((void *)(toQuery->displayName));
	toQuery->displayName = NULL;
    }

    if ((displayName != NULL) && (displayName[0] != '\0'))
	toQuery->displayName = strdup(displayName);

    return RM_CHILL;
}

/*
 * ----------------------------------------------------
 * @Name rmPipeGetDisplay
 @pstart
 void * rmPipeGetDisplay (const RMpipe *toQuery)
 @pend

 @astart
 const RMpipe *toQuery - a handle to an RMpipe object that will be
    queried (input).
 @aend

 @dstart

 Returns to the caller the X Display handle, cast to void *, associated
 with an RMpipe.


 @dend
 * ----------------------------------------------------
 */
const void *
rmPipeGetDisplay (const RMpipe *toQuery)
{
}

RMenum
rmPipeSetDisplay (RMpipe *toModify,
		  void *XDisplayStruct)
{
}
#endif

/* PRIVATE */
void
private_rmPipeCopy (RMpipe *source_pipe,
                    RMpipe *dest_pipe)
{
    memcpy((void *)dest_pipe, (void *)source_pipe, sizeof(RMpipe));
}


/* PRIVATE */
int
private_rmComparePipes (RMpipe *p1,
		        RMpipe *p2)
{
    /* returns 1 if pipes are identical, 0 otherwise */
    RM_ASSERT(p1, "NULL pipe (1) for comparison.");
    RM_ASSERT(p2, "NULL pipe (2) for comparison.");

#ifdef RM_X /* do we actually need to compare the xcolormaps? */
    if ((p1->xdisplay == p2->xdisplay) && (p1->xvisual == p2->xvisual) && (p1->xdrawable == p2->xdrawable) &&
	(p1->glxcontext == p2->glxcontext) && (p1->xcolormap == p2->xcolormap) && (p1->channel_format == p2->channel_format) &&
	(p1->xwindow_width == p2->xwindow_width) && (p1->xwindow_height == p2->xwindow_height))
	return(1);
    else
	return(0);
#endif

#ifdef RM_WIN
    if ((p1->hwnd == p2->hwnd))
        return(1);
    else
        return(0);
#endif
}

/* PRIVATE */
RMenum
private_rmPipeIsOffscreenFormat(const RMpipe *p)
{
    RMenum format = rmPipeGetChannelFormat(p);

    if ((format == RM_OFFSCREEN_MONO_CHANNEL) ||
	(format == RM_OFFSCREEN_REDBLUE_STEREO_CHANNEL) ||
	(format == RM_OFFSCREEN_BLUERED_STEREO_CHANNEL))
	return RM_TRUE;
    else
	return RM_FALSE;
}

/* constant frame rate stuff */
/*
 * ----------------------------------------------------
 * @Name rmPipeSetFrameRate
 @pstart
 RMenum rmPipeSetFrameRate (RMpipe *toModify, int newFPS)
 @pend

 @astart
 RMpipe *toModify - a handle to an RMpipe (modified).
 int newFPS - an integer value indicating the target frames-per-second
     renderering rate when rendering on the RMpipe toModify.
 @aend

 @dstart

 This routine is used to specify the desired frame rate when rendering
 on the RMpipe toModify. The caller provides an integer value, newFPS,
 that indicates the desired number of frames per second. A value of 30
 will result in 30 frames per second being rendered, etc. A value of -1
 will disable constant-rate rendering: OpenRM will not "watch the clock"
 if you specify a value of -1 frames per second as the desired target
 rate. The value for newFPS should be a positive integer. Note that not
 all values of newFPS make sense on all platforms for reasons related
 to how OpenGL is implemented on your machine.

 Constant-rate rendering is really a misnomer : what the contstant-rate
 rendering capability in OpenRM really does is to use a high precision
 timer and sleep function to ensure that *no more than a given number
 of frames per second are rendered*. In this way, you can be assured that
 your application will not be rendering more than newFPS per second. If
 your application produces a heavy graphics load, the actual rendering
 rate may fall well short of newFPS frames per second.  Rather than
 calling this capability "constant frame rate" rendering, it is more
 accurately called "bounded frame rate" rendering.

 Future work in this area will result in the ability for the application
 to query the rendering "load" or "stress" values. This way, you'll be
 able to obtain a quantitative value that indicates how far "over budget"
 your rendering is, and take action accordingly. One remedial action
 will be for the application to use switch nodes and render lower-resolution
 models as a way to reduce graphics rendering load.

 From the newFPS parameter, a value of milliseconds-per-frame is computed.
 When your application calls rmFrame() for the first time, OpenRM makes
 note of the current time. It then proceeds with rendering without delay,
 and then returns control to the application after rendering is
 complete. The second time your application calls rmFrame(), OpenRM
 will delay rendering so that the time taken by rendering during the
 previous frame along with the time taken by the application for processing
 inbetween frames does not exceed the occur before the milliseconds-per-frame
 amount of time has passed. In other words, the period of timing is from
 the start of rendering to the start of rendering.

 The bounded-frame-rendering capability works on both Unix/Linux and Win32
 platforms subject to caveats that are described in the OpenRM Programming
 Guide. Additional caveats not specific to a platform are also discussed
 in the OpenRM Progamming Guide.

 Use the routine rmPipeGetFrameRate() to query the frames-per-second
 parameter of an RMpipe.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeSetFrameRate(RMpipe *p,
		   int newFPS)
{
    if (RM_ASSERT(p, "rmPipeSetFrameRate() error: the input RMpipe is NULL") == RM_WHACKED)
	return RM_WHACKED;

    /* do a sanity check on the new FPS value: want either -1 or a positive
     integer value */
    if ((newFPS == 0) || (newFPS < -1))
    {
	rmError("rmPipeSetFrameRate() error: the input newFramesPerSecond value must be either a positive integer indicating frame per second, or a value of -1 to disable constant-rate rendering. ");
	return RM_WHACKED;
    }

    p->targetFrameRate = newFPS;
    rmTimeSet(&(p->timePerFrame), 0, 0);
    rmTimeCurrent(&(p->lastTimeStart));
    
    /* compute and actual RMtime version of newFPS. */
    if (newFPS > 0)
    {
	double msPerFrame = 1000.0/(double)newFPS;
	long usPerFrame = (long)(msPerFrame * 1000.0);
	rmTimeSet(&(p->timePerFrame), 0, usPerFrame);
	p->timePerFrameMS = msPerFrame;

	private_rmPipeSetTimeSyncFunc(p, private_rmPipeTimeSyncFunc);
    }
    else
	private_rmPipeSetTimeSyncFunc(p, NULL);
    
    return RM_CHILL;
}

/*
 * ----------------------------------------------------
 * @Name rmPipeGetFrameRate
 @pstart
 int rmPipeGetFrameRate (constRMpipe *toQuery)
 @pend

 @astart
 RMpipe *toQuery - a handle to an RMpipe (queried).
 @aend

 @dstart

 Use this routine to obtain the RMpipe's notion of how many frames per
 second it is supposed to render. For a more meaningful discussion of
 what the frames-per-second RMpipe attribute means, refer to the
 discussion for rmPipeSetFrameRate() as well as the OpenRM Programming
 Guide.

 Returns either -1 or a positive integer on success, or zero on failure.

 @dend
 * ----------------------------------------------------
 */
int
rmPipeGetFrameRate(const RMpipe *p)
{
    if (RM_ASSERT(p, "rmPipeGetFrameRate() error: the input RMpipe is NULL") == RM_WHACKED)
	return 0;

    return p->targetFrameRate;
}

void
private_rmPipeFPSStart(RMpipe *p)
{
#if 0
    RMtime now, wait, drift;
    double ms, diffMS;

    rmTimeCurrent(&now);
    ms = rmTimeDifferenceMS(&(p->lastTimeStart), &now);

    /*
     * quick test to see if whether or not we need to wait at all.
     */
    if (ms > p->timePerFrameMS)
    {
/*	fprintf(stderr," SKIP!! est ms per frame = %g, current delta = %g\n", p->timePerFrameMS, ms); */
	rmTimeCurrent(&(p->lastTimeStart));
	return;
    }

    /*
     * compute amount of time we need to wait
     */
    diffMS = p->timePerFrameMS - ms;

    rmTimeEncodeMS(&wait, diffMS);
    
    /*
     * do the wait
     */
    rmTimeSleep(&wait);
    /* rmTimeSleepDrift(&wait, &drift); to accumulate/mediate error */

    /*
     * what time is it now?
     */
    rmTimeCurrent(&(p->lastTimeStart));
    
/*    rmTimeDifference(&(p->lastTimeStart),&drift,(&p->lastTimeStart)); */
#endif
    p = NULL; 			/* foil compiler warnings */
}

void
private_rmPipeFPSEnd(RMpipe *p)
{
    RMtime now;
    /*
     * compute the amount of time required for rendering
     */
    rmTimeCurrent(&now);
    rmTimeDifference(&(p->lastTimeStart), &now, &(p->lastRenderTime));
}

void
private_rmPipeSetTimeSyncFunc(RMpipe *t,
			      void (*newFunc)(RMpipe *))
{
    t->timeSyncFunc = newFunc;
}

void
private_rmPipeTimeSyncFunc(RMpipe *p)
{
    RMtime now, wait, drift;
    double ms, diffMS;

    extern RMenum rmTimeSleepDrift(const RMtime *tSleep, RMtime *drift);
    
    rmTimeCurrent(&now);
    ms = rmTimeDifferenceMS(&(p->lastTimeStart), &now);

    /*
     * quick test to see if whether or not we need to wait at all.
     */
#define FUDGE 0.0  /* ms fudge factor */
    
    if ((ms-FUDGE) > p->timePerFrameMS)
    {
/*	fprintf(stderr," SKIP!! est ms per frame = %g, current delta = %g\n", p->timePerFrameMS, ms); */
	rmTimeCurrent(&(p->lastTimeStart));
	return;
    }

    /*
     * compute amount of time we need to wait
     */
    diffMS = p->timePerFrameMS - ms;

    rmTimeEncodeMS(&wait, diffMS);
    
    /*
     * do the wait
     */
#define USE_DRIFT 1
#if USE_DRIFT
    rmTimeSleepDrift(&wait, &drift);
#else
    rmTimeSleep(&wait);
#endif
    
    /*
     * what time is it now?
     */
    rmTimeCurrent(&(p->lastTimeStart));
    
#if DEBUG_RSYNC
    {
	int indx = timeSyncIndx % MAX_RSYNC_SAMPLES;
	timeSyncData[indx].t = p->lastTimeStart; /* current time */
	timeSyncData[indx].frameNumber = rmPipeGetFrameNumber(p);
	timeSyncIndx++;
    }
#endif
    
#if USE_DRIFT
    /*
     * but, the timer should've gone off at now-drift, rather than now.
     * so, adjust "now" backwards by "drift" so that the next
     * frame happens at the right time.
     */

    {
	long nowSecs, nowUSecs, dUSecs;

	rmTimeGet(&(p->lastTimeStart), &nowSecs, &nowUSecs);
	
	/* the seconds portion of the drift is most likely zero,
	   so we'll ignore it */
#define FUDGE_US 0
	rmTimeGet(&drift, NULL, &dUSecs);
	if (nowUSecs < (dUSecs+FUDGE_US))
	{
	    nowUSecs -= (dUSecs + 1000000 + FUDGE_US);
	    nowSecs -= 1;
	}
	else
	    nowUSecs -= (dUSecs+FUDGE_US);
	
	rmTimeSet(&(p->lastTimeStart), nowSecs, nowUSecs);
    }
#endif

}


/*
 * ----------------------------------------------------
 * @Name rmPipeGetFrameNumber
 @pstart
 int rmPipeGetFrameNumber (const RMpipe *toQuery)
 @pend

 @astart
 const RMpipe *toQuery - (input) a handle to an RMpipe.
 @aend

 @dstart

 Use this routine to obtain the RMpipe's notion of the current frame
 number. Upon success, a non-negative integer is returned. Upon failure,
 a value of -1 is returned.

 The RMpipe's frame number is initialized to zero when the RMpipe is
 created with rmPipeNew(). Upon each call to rmFrame(), the the
 frame number is incremented by one. There is no mechanism for applications
 to set the frame number, or for applications to reset the frame number
 to zero.

 @dend
 * ----------------------------------------------------
 */
int
rmPipeGetFrameNumber(const RMpipe *toQuery)
{
    return toQuery->frameNumber;
}

static RMenum
private_rmCheckForActiveContext(void)
{
    /*
     * checks to see if there's an active OpenGL context. Return 1 if
     * all is well, otherwise, return a zero.
     */
#ifdef RM_X
    if (glXGetCurrentContext() == NULL)
	return RM_FALSE;
    else
	return RM_TRUE;
#elif RM_WIN
    if (wglGetCurrentContext() == NULL)
	return RM_FALSE;
    else
	return RM_TRUE;
#else /* assume RM_CR */
    return RM_TRUE;
#endif
    
}

/* PRIVATE */
int 
private_rmBuildExtensionTable(const char *s, 
			      char ***dst)
{
    int n = 0;
    int i;
    char **d;
    
    d = (char **)malloc(sizeof(char *)*1024); /* max of 1024 extensions */

    for (i=0; i< (int)(strlen(s)); )
    {
	int istart, iend;

	/* scan to the first non-blank character */
	while (s[i] == ' ')
	    i++;

	istart = i; 		/* beginning of string */

	/* scan to the next character that isn't a blank or a NULL */
	while ((s[i] != ' ') && (s[i] != '\0') && (s[i] != '\n'))
	    i++;

	iend = i-1;

	d[n] = (char *)calloc(iend-istart+2, sizeof(char)); /* leave room for an extra NUL */
	memcpy((void *)d[n], (void *)(s+istart), iend-istart+1);
	n++;
	i++;
    }

    *dst = d;
    return n;
}

/* PRIVATE */
int
private_rmHaveExtension(char **t, 
			int n, 
			const char *s)
{
/*    int i;

    for (i=0; i<n; i++)
    {
	if (strcmp(t[i], s) == 0)
	    return 1;
    }*/ /*niki*/
    return 0;
}

/* PRIVATE */
static void 
private_rmPipeGetCapabilities(RMpipeOGLCapabilities *c)
{
    const char *extensions = (const char *) glGetString(GL_EXTENSIONS);
    int nExtensions;
    char **extensionTable;
    
    nExtensions = private_rmBuildExtensionTable(extensions, &extensionTable);

    /* check for multitexturing capabilities */
    if (private_rmHaveExtension(extensionTable, nExtensions, "GL_ARB_multitexture") == 1)
    {
	glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &(c->numTextureUnits));
	
#if (DEBUG_LEVEL & DEBUG_TRACE)
	printf(" Multitexturing is supported on this renderer. There are %d texture units available. \n", c->numTextureUnits);
#endif

	c->haveMultiTexturing = RM_TRUE;

	c->activeTextureARB = private_rmGLGetProcAddr("glActiveTextureARB");
	c->multiTexCoord1fvARB = private_rmGLGetProcAddr("glMultiTexCoord1fvARB");
	c->multiTexCoord2fvARB = private_rmGLGetProcAddr("glMultiTexCoord2fvARB");
	c->multiTexCoord3fvARB = private_rmGLGetProcAddr("glMultiTexCoord3fvARB");
    }
    else
    {
#if (DEBUG_LEVEL & DEBUG_TRACE)
	printf("Sorry, GL_ARB_multitexture not supported by this renderer.\n");
#endif
	c->haveMultiTexturing = RM_FALSE;
	c->numTextureUnits = 0;

	c->activeTextureARB = NULL;
	c->multiTexCoord1fvARB = NULL;
	c->multiTexCoord2fvARB = NULL;
	c->multiTexCoord3fvARB = NULL;
    }

    /* check for 3D texturing */
    if (private_rmHaveExtension(extensionTable, nExtensions, "GL_EXT_texture3D") == 1)
    {
	c->have3DTextures = RM_TRUE;
	c->rm_glTexImage3D = private_rmGLGetProcAddr("glTexImage3DEXT");
	c->rm_glTexSubImage3D = private_rmGLGetProcAddr("glTexSubImage3DEXT"); 
    }
    else
    {
	c->have3DTextures = RM_FALSE;
	c->rm_glTexImage3D = NULL;
	c->rm_glTexSubImage3D = NULL;
    }

    /* clean up */
    {
	int i;
	for (i=0; i<nExtensions; i++)
	    free((void *)extensionTable[i]);
	free((void *)extensionTable);
    }
}

/*
 * ----------------------------------------------------
 * @Name rmPipeGetNumMultitextureUnits
 @pstart
 int rmPipeGetNumMultitextureUnits (const RMpipe *toQuery)
 @pend

 @astart
 const RMpipe *toQuery - (input) a handle to an RMpipe.
 @aend

 @dstart

 Use this routine to obtain the number of OpenGL multitexturing
 units available from the underlying OpenGL implementation. This
 routine returns the following values:

 -1 is used to indicate an error of some type, like if the input
 RMpipe is NULL.

 0 (zero) is returned if the underlying OpenGL platform does not support
 multitexturing.

 >0 (positive integer) is returned to indicate the number of usable
 OpenGL multitexture units. Note that the number of texture units is
 implementation dependent, but should be at least two when multitexturing
 is present.

 This routine should be called only after the input RMpipe has been
 subjected to successful call to rmPipeMakeCurrent.

 @dend
 * ----------------------------------------------------
 */
int     
rmPipeGetNumMultitextureUnits(const RMpipe *toQuery)
{
    if (RM_ASSERT(toQuery, "rmPipeGetNumMultitextureUnits error - the input RMpipe is NULL") == RM_WHACKED)
	return -1;

    if (RM_ASSERT(toQuery->caps, "rmPipeGetNumMultitextureUnits internal error - the capabilities field is NULL. Please call rmPipeMakeCurrent before calling this routine. ") == RM_WHACKED)
	return -1;

    if (toQuery->caps->haveMultiTexturing == RM_FALSE)
	return 0;

    return toQuery->caps->numTextureUnits;
}

/*
 * ----------------------------------------------------
 * @Name rmPipeSetSceneBackgroundColor
 @pstart
 RMenum rmPipeSetSceneBackgroundColor (RMpipe *toModify,
			               const RMcolor4D *newColor)
 @pend

 @astart
 RMnode *toModify - a handle to an RMpipe (input).

 const RMcolor4D *newColor - a handle to the desired background color
   (input).
 @aend

 @dstart

 In RM/OpenRM, you may set a "background color" scene parameter at the
 RMnode level, at the RMpipe level, or both. The background color may
 consist of a single color or a color background image. In addition,
 you may also set a depth value or a depth image that will be used to
 initialize the z-buffer. The main issue with assigning a background
 color/depth parameter at the RMnode vs. the RMpipe level is convenience
 vs. fine-grained control. If you assign the background color/depth
 value at the RMpipe level, the background will be cleared once 
 each time your application calls rmFrame() to render a scene. In some cases,
 such as when multiple viewports are rendered within a single window
 with a single call to rmFrame(), then you must assign the background
 color/depth value at the RMnode level. Unlike when assigning such
 values at the RMpipe level, using them at the RMnode level requires
 special care due to the multiple rendering passes RM makes for a single
 call to rmFrame(). The RMnode containing the background color/depth
 values must have its traversal mask set such that it is invoked at most
 once per call to rmFrame().
 
 Use rmPipeSetSceneBackgroundColor to set the background color scene
 parameter at the RMpipe level. Calling this routine with a color
 argument of NULL disables the background color scene parameter.  During
 rendering, the color planes of the framebuffer are cleared to this
 background color (with a depth value of 1.0, see NOTE below).  

 Upon success, RM_CHILL is returned and the specified background color
 scene parameter is set.  Otherwise, RM_WHACKED is returned, and the
 background color scene parameter remains unmodified.

 Passing in a value of NULL for the RMcolor4D object will effectively
 remove any existing background image color parameter from the RMpipe toModify.

 NOTE: internal to this routine, a call to rmPipeSetSceneDepthValue is
 performed, effectively coupling framebuffer and depthbuffer clears.

 Because this routine makes a copy of the input scene parameter (an
 RMcolor4D object), callers do not need to manage the input object after a
 successful return from this routine. This has important ramificiations:

 1. Since a copy is made, any changes made to the caller's object will have
 no effect upon the scene graph, unless this routine is called again to
 update the scene parameter inside the scene graph;

 2. Callers may safely delete their copy of the input object after a 
 successful return from this routine.

 Related routines: rmPipeGetSceneBackgroundColor, rmPipeSetSceneBackgroundImage, rmPipeSetSceneDepthValue.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeSetSceneBackgroundColor (RMpipe *r,
			       const RMcolor4D *new_color)
{
    if (RM_ASSERT(r, "rmPipeSetSceneBackgroundColor() error: the input RMpipe pointer is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    if (r->fbClearNode == NULL)
	r->fbClearNode = rmNodeNew("RMpipe fbClear node", RM_RENDERPASS_ALL, RM_RENDERPASS_ALL);
    
    if (r->fbClearNode->fbClear == NULL)
	r->fbClearNode->fbClear = private_rmFBClearNew();

    if (r->fbClearNode->fbClear->bgColor != NULL)
	rmColor4DDelete(r->fbClearNode->fbClear->bgColor);

    if (new_color != NULL)
    {
	float junk;
	
	r->fbClearNode->fbClear->bgColor = rmColor4DNew(1);
	*(r->fbClearNode->fbClear->bgColor) = *new_color;

	if (rmPipeGetSceneDepthValue(r, &junk) == RM_WHACKED)
	{
	    extern float RM_DEFAULT_DEPTH_VALUE;
	    
	    junk = RM_DEFAULT_DEPTH_VALUE;
	    rmPipeSetSceneDepthValue(r, &junk);
	}
    }
    else
	r->fbClearNode->fbClear->bgColor = NULL;
    
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmPipeGetSceneBackgroundColor
 @pstart
 RMenum rmPipeGetSceneBackgroundColor (const RMpipe *toQuery,
			               RMcolor4D *returnColor)
 @pend

 @astart
 const RMnode *toQuery - a handle to an RMpipe (input).

 RMcolor4D *returnColor - a handle to a caller-supplied RMcolor4D
    object (return).
 @aend

 @dstart

 Use this routine to obtain the background color scene parameter of an
 RMpipe, if such a scene parameter exists.  

 Upon success, RM_CHILL is returned and the scene background color is
 copied into the caller-supplied RMcolor4D (effectively returning a copy
 of the background color scene parameter to the caller). If no such scene 
 parameter exists, or if there is some error condition detected, RM_WHACKED 
 is returned, and the caller-supplied colorReturn object remains unmodified.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeGetSceneBackgroundColor (const RMpipe *r,
			       RMcolor4D *color_return)
{
    if ((RM_ASSERT(r, "rmPipeGetSceneBackgroundColor() error: the input RMpipe pointer is NULL.") == RM_WHACKED) ||
	(RM_ASSERT(color_return, "rmPipeGetSceneBackgroundColor() error: the return RMcolor4D pointer is NULL.") == RM_WHACKED))
	return(RM_WHACKED);

    if ((r->fbClearNode == NULL) || (r->fbClearNode->fbClear == NULL) ||
	(r->fbClearNode->fbClear->bgColor == NULL))
	return(RM_WHACKED);

    /* we are returning the contents of the scene parameter, an RMcolor4D
     object, to the caller, not the handle to the RMcolor4D object stored
     in the scene graph itself. */

    *color_return = *(r->fbClearNode->fbClear->bgColor);

    return(RM_CHILL);	
}


/*
 * ----------------------------------------------------
 * @Name rmPipeSetSceneBackgroundImage
 @pstart
 RMenum rmPipeSetSceneBackgroundImage (RMpipe *toModify,
			               const RMimage *newImageTile)
 @pend

 @astart
 RMpipe *toModify - a handle to an RMpipe (modified).

 const RMimage *newImageTile - a handle to an RMimage (input).
 @aend

 @dstart

 In RM/OpenRM, you may set a "background color" scene parameter at the
 RMnode level, at the RMpipe level, or both. The background color may
 consist of a single color or a color background image. In addition,
 you may also set a depth value or a depth image that will be used to
 initialize the z-buffer. The main issue with assigning a background
 color/depth parameter at the RMnode vs. the RMpipe level is convenience
 vs. fine-grained control. If you assign the background color/depth
 value at the RMpipe level, the background will be cleared once 
 each time your application calls rmFrame() to render a scene. In some cases,
 such as when multiple viewports are rendered within a single window
 with a single call to rmFrame(), then you must assign the background
 color/depth value at the RMnode level. Unlike when assigning such
 values at the RMpipe level, using them at the RMnode level requires
 special care due to the multiple rendering passes RM makes for a single
 call to rmFrame(). The RMnode containing the background color/depth
 values must have its traversal mask set such that it is invoked at most
 once per call to rmFrame().

 rmPipeSetSceneBackgroundImage sets the background image scene parameter
 for the RMpipe.  When rendered, this image is tiled into the viewport at
 the scene depth value (or a default of 1.0) with an orthogonal projection,
 creating a background image. If the image does not fit the display, it will
 be tiled from top to bottom and left to right, so ragged edges fall on
 the bottom and right edges of the viewport.

 Passing in a value of NULL for the newImageTile parameter will 
 effectively remove the background image tile scene parameter, if it
 exists.

 Upon success, RM_CHILL is returned and the background image scene
 parameter has been modified.  Otherwise, RM_WHACKED is returned.

 Because this routine makes a copy of the input scene parameter (an
 RMimage object), callers do not need to manage the input object after a
 successful return from this routine. This has important ramificiations:

 1. Since a copy is made, any changes made to the caller's object will have
 no effect upon the scene graph, unless this routine is called again to
 update the scene parameter inside the scene graph;

 2. Callers may safely delete their copy of the input object after a 
 successful return from this routine.

 NOTE: internal to this routine, a call to rmPipSetSceneDepthValue() is
 performed, effectively coupling framebuffer clears (by image tiling) with
 depth buffer clears.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeSetSceneBackgroundImage (RMpipe *p,
			       const RMimage *tile)
{
    if (RM_ASSERT(p, "rmPipeSetSceneBackgroundImage() error: input RMpipe is NULL. \n") == RM_WHACKED)
	return(RM_WHACKED);
    
    if (p->fbClearNode == NULL)
	p->fbClearNode = rmNodeNew("RMpipe fbClear node", RM_RENDERPASS_ALL, RM_RENDERPASS_ALL);
    
    if (p->fbClearNode->fbClear == NULL)
	p->fbClearNode->fbClear = private_rmFBClearNew();

    /* if there's an existing background image tile parameter, remove it. */
    if (p->fbClearNode->fbClear->bgImageTile != NULL)
	rmImageDelete(p->fbClearNode->fbClear->bgImageTile);

    /* if the input isn't null, make a copy and assign it as a scene
     parameter. */
    if (tile != NULL)
    {
	float junk;
	
	p->fbClearNode->fbClear->bgImageTile = rmImageDup(tile);

	/* if there's no background depth value, create one. this
	 policy may change in the future. */
	if (rmPipeGetSceneDepthValue(p, &junk) == RM_WHACKED)
	{
	    extern float RM_DEFAULT_DEPTH_VALUE;
	    
	    junk = RM_DEFAULT_DEPTH_VALUE;
	    rmPipeSetSceneDepthValue(p, &junk);
	}
    }
    else
	p->fbClearNode->fbClear->bgImageTile = NULL;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmPipeGetSceneBackgroundImage
 @pstart
 RMenum rmPipeGetSceneBackgroundImage (const RMpipe *toQuery,
			               RMimage **returnImageTile)
 @pend

 @astart
 const RMpipe *toQuery - a handle to an RMpipe (input).

 RMimage **returnImageTile - a handle to an RMimage handle (return).
 @aend

 @dstart

 Use this routine to obtain the background image scene parameter of an
 RMpipe.  Note that a handle to the RMimage is returned, not a
 copy of the actual pixel data.

 Upon success, RM_CHILL is returned and a handle to the scene
 background image is copied into the caller-supplied RMimage handle.
 Otherwise, RM_WHACKED is returned.

 Unlike most other rmPipeGetScene*() routines, this routine returns a
 handle to the actual object contained within the RMpipe, rather
 than returning a copy. Applications should exercise appropriate
 discretion when using this object.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeGetSceneBackgroundImage (const RMpipe *r,
			       RMimage **tile_return)
{
    if ((RM_ASSERT(r, "rmPipeGetSceneBackgroundImage() error: input RMpipe is NULL. \n") == RM_WHACKED) ||
	(RM_ASSERT(r, "rmPipeGetSceneBackgroundImage() error: input pointer to RMimage pointer is NULL. ") == RM_WHACKED))
	return(RM_WHACKED);

    /* detect the absence of any scene parms, or the absence of the
       background image tile scene parm.  */

    if ((r->fbClearNode == NULL) || (r->fbClearNode->fbClear == NULL) || (r->fbClearNode->fbClear->bgImageTile == NULL))
	return(RM_WHACKED);
	
    *tile_return = r->fbClearNode->fbClear->bgImageTile;

    return(RM_CHILL);
}

/*
 * ----------------------------------------------------
 * @Name rmPipeSetSceneDepthImage
 @pstart
 RMenum rmPipeSetSceneDepthImage (RMpipe *toModify,
			          const RMimage *newDepthImage)
 @pend

 @astart
 RMpipe *toModify - a handle to an RMpipe (modified).

 const RMimage *newDepthImage - a handle to a depth image (input)
 @aend

 @dstart

 In RM/OpenRM, you may set a "background color" scene parameter at the
 RMnode level, at the RMpipe level, or both. The background color may
 consist of a single color or a color background image. In addition,
 you may also set a depth value or a depth image that will be used to
 initialize the z-buffer. The main issue with assigning a background
 color/depth parameter at the RMnode vs. the RMpipe level is convenience
 vs. fine-grained control. If you assign the background color/depth
 value at the RMpipe level, the background will be cleared once 
 each time your application calls rmFrame() to render a scene. In some cases,
 such as when multiple viewports are rendered within a single window
 with a single call to rmFrame(), then you must assign the background
 color/depth value at the RMnode level. Unlike when assigning such
 values at the RMpipe level, using them at the RMnode level requires
 special care due to the multiple rendering passes RM makes for a single
 call to rmFrame(). The RMnode containing the background color/depth
 values must have its traversal mask set such that it is invoked at most
 once per call to rmFrame().

 rmPipeSetSceneDepthImage assigns a depth image as a scene parameter for
 the input RMpipe toModify. The  depth image is similar to the background
 image tile, but is applied to the depth buffer, rather than the color planes
 of the framebuffer. Like the background image tile, the depth image scene
 parameter is tiled to fill the framebuffer, starting from the upper
 left-hand corner of the viewport. If the size of the depth buffer image
 and viewport do not match exactly, the "ragged edges" or on the right
 and the bottom of the viewport.

 Passing in a value of NULL for the RMimage object will have the effect
 of removing the background depth image scene parameter, if one exists,
 from the RMnode.

 Upon success, a copy of the caller's RMimage object is made, and the
 copy is assigned as a scene parameter (or, if the input RMimage object is
 NULL, the NULL is assigned as a depth image scene parameter, effectively
 turning off that scene parameter), and RM_CHILL is returned; 
 otherwise, RM_WHACKED is returned.

 Because this routine makes a copy of the input scene parameter (an
 RMimage), callers do not need to manage the input object after a
 successful return from this routine. This has important ramificiations:

 1. Since a copy is made, any changes made to the caller's object will have
 no effect upon the scene graph, unless this routine is called again to
 update the scene parameter inside the scene graph;

 2. Callers may safely delete their copy of the input object after a 
 successful return from this routine.

 Note: as a practical matter, it is suggested that the pixel format
 of input RMimage objects should be of type RM_FLOAT, and in the
 range 0..1. By default, depth buffer Pixels in OpenGL range from
 0..1 (or perhaps 0..0.9999, depending upon your interpretation), but
 this range may be manipulated with glPixelTransferf(). As of the time
 of this writing (May 2000), we have tested background depth images
 only with RMimage's consisting of RM_FLOAT pixels in the range 0..1
 (and they work - refer to the demonstration program "pdb" included with
 the openrm-demo distribution).

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeSetSceneDepthImage (RMpipe *p,
			  const RMimage *new_depth_image)
{
    if (RM_ASSERT(p, "rmPipeSetSceneDepthImage() error: the input RMpipe pointer is NULL.") == RM_WHACKED)
	return(RM_WHACKED);
    
    if (p->fbClearNode == NULL)
	p->fbClearNode = rmNodeNew("RMpipe fbClear node", RM_RENDERPASS_ALL, RM_RENDERPASS_ALL);
    
    if (p->fbClearNode->fbClear == NULL)
	p->fbClearNode->fbClear = private_rmFBClearNew();
    
    if (p->fbClearNode->fbClear->depthImage != NULL)
    {
	rmImageDelete(p->fbClearNode->fbClear->depthImage);
	p->fbClearNode->fbClear->depthImage = NULL;
    }

    if (new_depth_image != NULL)
	p->fbClearNode->fbClear->depthImage = rmImageDup(new_depth_image);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmPipeGetSceneDepthImage
 @pstart
 RMenum rmPipeGetSceneDepthImage (const RMpipe *toQuery,
			          RMimage **returnDepthImage)
 @pend

 @astart
 const RMpipe *toQuery - a handle to an RMnode (input).

 RMimage **returnDepthImage - a handle to a depth image handle
    (return). 
 @aend

 @dstart

 Use this routine to obtain the RMpipe's depth image scene parameter. If
 such a scene parameter exists, the handle of the RMimage (depth image)
 scene parameter is copied into caller-supplied memory, and RM_CHILL is
 returned. Otherwise, in the event of an error or the absence of the
 depth image scene parameter, RM_WHACKED is returned and caller supplied
 memory remains unmodified.

 Unlike most other rmPipeGetScene*() routines, this routine returns a
 handle to the actual RMimage object contained within the scene graph, rather
 than returning a copy. Applications should exercise appropriate
 discretion when using this object.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeGetSceneDepthImage (const RMpipe *p,
			  RMimage **depth_image_return)
{
    if ((RM_ASSERT(p, "rmPipeGetSceneDepthImage() error: input RMpipe is NULL. \n") == RM_WHACKED) ||
	(RM_ASSERT(depth_image_return, "rmPipeGetSceneDepthImage() error: input pointer to RMimage pointer is NULL. ") == RM_WHACKED))
	return(RM_WHACKED);

    if ((p->fbClearNode == NULL) || (p->fbClearNode->fbClear == NULL) ||
	(p->fbClearNode->fbClear->depthImage == NULL))
	return(RM_WHACKED);
	
    *depth_image_return = p->fbClearNode->fbClear->depthImage;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmPipeSetSceneDepthValue
 @pstart
 RMenum rmPipeSetSceneDepthValue (RMpipe *toModify,
			          const float *newDepthValue)
 @pend

 @astart
 RMpipe *toModify - a handle to an RMpipe (modified).

 const float *newDepthValue - a handle to the new depth value for the
    node (input).
 @aend

 @dstart

 In RM/OpenRM, you may set a "background color" scene parameter at the
 RMnode level, at the RMpipe level, or both. The background color may
 consist of a single color or a color background image. In addition,
 you may also set a depth value or a depth image that will be used to
 initialize the z-buffer. The main issue with assigning a background
 color/depth parameter at the RMnode vs. the RMpipe level is convenience
 vs. fine-grained control. If you assign the background color/depth
 value at the RMpipe level, the background will be cleared once 
 each time your application calls rmFrame() to render a scene. In some cases,
 such as when multiple viewports are rendered within a single window
 with a single call to rmFrame(), then you must assign the background
 color/depth value at the RMnode level. Unlike when assigning such
 values at the RMpipe level, using them at the RMnode level requires
 special care due to the multiple rendering passes RM makes for a single
 call to rmFrame(). The RMnode containing the background color/depth
 values must have its traversal mask set such that it is invoked at most
 once per call to rmFrame().

 Use rmPipeSetSceneDepthValue to set the scene depth value parameter for
 an RMpipe. The presence of the scene depth value has the effect of
 clearing the depth buffer to the value of newDepthValue.

 Passing in a value of NULL for newDepthValue has the effect of disabling
 depth buffer clears.

 The input newDepthValue should have the magnitude specified by
 glPixelTransferf(GL_DEPTH_SCALE,X) and range specified by
 glPixelTransferf(GL_DEPTH_BIAS,X). In OpenGL, the default depth
 bias is 0.0 and range/scale is 1.0.

 Upon success, RM_CHILL is returned and the node's depth value is set.
 Otherwise, RM_WHACKED is returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeSetSceneDepthValue (RMpipe *p,
			  const float *newDepthValue)
{
    if (RM_ASSERT(p, "rmPipeSetSceneDepthValue() error: the input RMpipe pointer is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    if (p->fbClearNode == NULL)
	p->fbClearNode = rmNodeNew("RMpipe fbClear node", RM_RENDERPASS_ALL, RM_RENDERPASS_ALL);
    
    if (p->fbClearNode->fbClear == NULL)
	p->fbClearNode->fbClear = private_rmFBClearNew();

    if (p->fbClearNode->fbClear->depthValue != NULL)
    {
	free((void *)(p->fbClearNode->fbClear->depthValue));
	p->fbClearNode->fbClear->depthValue = NULL;
    }
    if (newDepthValue != NULL)
    {
	p->fbClearNode->fbClear->depthValue = rmFloatNew(1);
	*(p->fbClearNode->fbClear->depthValue) = *newDepthValue;
    }
	
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmPipeGetSceneDepthValue
 @pstart
 RMenum rmPipeGetSceneDepthValue (const RMpipe *toQuery,
   			          float *returnDepthValue)
 @pend

 @astart
 const RMpipe *toQuery - a handle to an RMpipe (input).

 float *returnDepthValue - a handle to a caller-supplied float for the 
    queried depth value (return). 
 @aend

 @dstart 

 Use this routine to query the scene depth value for a given RMpipe.

 Upon success, RM_CHILL is returned and the node's depth value is
 copied into the caller-supplied float.  If the specified pointers are
 NULL, or no valid scene depth parameter exists, RM_WHACKED is
 returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmPipeGetSceneDepthValue (const RMpipe *p,
			  float *dv)
{
    if ((RM_ASSERT(p, "rmPipeGetSceneDepthValue() error: the input RMpipe pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(dv, "rmPipeGetSceneDepthValue() error: the return float pointer is NULL.") == RM_WHACKED))
	return(RM_WHACKED);
    
    if ((p->fbClearNode == NULL) || (p->fbClearNode->fbClear == NULL) || (p->fbClearNode->fbClear->depthValue == NULL))
	return(RM_WHACKED);

    *dv = *(p->fbClearNode->fbClear->depthValue);

    return(RM_CHILL);
}
/* EOF */
