/*
 * 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: rmframe.c,v 1.26 2008/11/23 15:11:16 wes Exp $
 * Version: $Name: v180-alpha-02 $
 * $Revision: 1.26 $
 * $Log: rmframe.c,v $
 * Revision 1.26  2008/11/23 15:11:16  wes
 * Changes to support use of RMnode's use of the RMarray to hold children
 *
 * Revision 1.25  2008/09/22 01:44:47  wes
 * Add sg data synchronization code
 *
 * Revision 1.24  2007/10/26 11:30:43  wes
 * Minor cleanup in conditional debug code
 *
 * Revision 1.23  2005/06/26 19:01:07  wes
 * Use new routines for push/pop the OpenGL attrib stack. Streamline
 * operations so that the same calls to update scene parms, etc. are made
 * here as in the multi-stage draw code.
 *
 * Revision 1.22  2005/06/09 00:45:29  wes
 * More compiler warning fixes turned up by Windows build.
 *
 * Revision 1.21  2005/06/06 02:04:29  wes
 * Lots of small additions to clean up compiler warnings.
 *
 * Revision 1.20  2005/05/28 16:25:05  wes
 * Added early termination tests based upon status of the pick enable
 * attribute of RMnode's when doing picks.
 *
 * Revision 1.19  2005/05/06 16:33:13  wes
 * Add texture state/env settings for 1D textures.
 *
 * Revision 1.18  2005/04/27 03:33:39  wes
 * Multitexturing support.
 *
 * Revision 1.17  2005/02/27 19:34:04  wes
 * Added support for application supplied texture object IDs and display lists.
 *
 * Revision 1.16  2005/02/24 16:26:51  wes
 * Fixed problem whereby the red channel in red/blue anaglyph wasn't
 * being rendered in the RM_PIPE_SERIAL processing mode.
 *
 * Revision 1.15  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.14  2005/02/19 16:28:54  wes
 * Distro sync and consolidation.
 * Fixes for a number of state tracking buglets.
 *
 * Revision 1.13  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.12  2004/09/28 00:48:57  wes
 * Added render state cache as a parameter to routines that may modify
 * lighting state to fix a lighting state tracking problem.
 *
 * Revision 1.11  2004/06/22 05:01:58  wes
 * Minor tweaks to support consistent state tracking. These changes
 * were made as a result to changes in the main RMSG tree during a
 * big PS bug fixing expedition.
 *
 * Revision 1.10  2004/03/27 17:14:01  wes
 * Remove gratuitous glColor4fv(unlitColor) inside updateSceneParms
 *
 * Revision 1.9  2004/03/10 01:46:01  wes
 * Enabled activation of OpenGL's light attenuation attributes. They were
 * already in RM as scene parms, we just had to enable them in OpenGL.
 *
 * Revision 1.8  2004/01/16 16:56:02  wes
 *
 * Rearranged calls within rendering routines so that:
 * (1) the time synchronization for constant-rate rendering happens after
 * the actual rendering, but right before SwapBuffers;
 * (2) the user-assignable routines for grabbing frambuffer pixels are
 * relocated to after the SwapBuffers - they were located before in
 * the previous version.
 *
 * These changes are expected to have the following benefits:
 * (1) frame sync is more stable when associated with SwapBuffers rather
 * than having it placed immediately before when rendering starts;
 * (2) We have removed an embedded glFinish() call; SwapBuffers always
 * implies a glFlush(), and in some implementations, also implies
 * a glFinish(). The oddball here in terms of execution behavior is
 * software Mesa. The only detrimental impact will be on timing rendering
 * as you must explicitly insert your own glFinish() to ensure that
 * timings are accurate. We are looking at this for the next rev of OpenRM.
 *
 * Revision 1.7  2003/12/01 02:13:05  wes
 * Additions to support constant frame-rate rendering on both Unix and Win32.
 *
 * Revision 1.6  2003/11/05 15:27:43  wes
 * Added a gratuitous glFinish() inside rmFrame() to force a flush after
 * the final swapbuffers. The addition is needed for accurate timing.
 * There are probably some other glFinish'es sprinkled throughout the
 * rendering code that can be removed (as per D. Leech at Sun Micro, who
 * points out these additional glFinish()'s are probably not necessary
 * and also have an adverse impact on performance).
 *
 * Revision 1.5  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.4  2003/04/12 21:00:53  wes
 * Removed debug statements used during CR development.
 *
 * Revision 1.3  2003/02/14 00:17:01  wes
 * Remove dead code.
 *
 * Revision 1.2  2003/02/02 02:07:15  wes
 * Updated copyright to 2003.
 *
 * Revision 1.1.1.1  2003/01/28 02:15:23  wes
 * Manual rebuild of rm150 repository.
 *
 * Revision 1.32  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.31  2003/01/20 05:39:49  wes
 * Rewrote texture state handling code.
 *
 * Revision 1.30  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.29  2003/01/12 23:50:06  wes
 * Minor adjustments to texturing environment controls to fix problems with
 * the texture environment not being set correctly.
 *
 * Revision 1.28  2002/12/31 00:55:22  wes
 *
 * Various enhancements to support Chromium - achitecture-specific sections
 * of RMpipe were cleaned up, etc.
 *
 * Revision 1.27  2002/12/04 14:50:33  wes
 * Cleanup SGI compiles.
 *
 * Revision 1.26  2002/11/14 15:32:33  wes
 * Rearrange texture calls during port to CR. No net changes in performance.
 *
 * Revision 1.25  2002/09/05 15:05:20  wes
 * Fixed minor problems with realloc code for textures in the
 * context cache.
 *
 * Revision 1.24  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.23  2002/06/30 21:40:32  wes
 * Diddling with glColorMask in some of the older serial rendering code.
 * No changes to multistage stuff.
 *
 * Revision 1.22  2002/06/17 00:57:41  wes
 * Removed rmSubtreeFrame. Replaced internal calls to rmSubtreeFrame with
 * rmFrame.
 *
 * Revision 1.21  2002/06/02 15:15:34  wes
 * Added RMstateCache code to help eliminate the number of state changes
 * made during the render traversal. The RMstateCache tracks
 * the actual OpenGL rendering state w/o the need for querying OpenGL
 * directly, and  is queried by draw code that then decides if any
 * real state changes are required given the configuration of data
 * within an RMprimitive.
 *
 * Revision 1.20  2002/04/30 19:31:39  wes
 * Updated copyright dates.
 *
 * Revision 1.19  2001/10/15 01:32:19  wes
 * Minor mods to support 4-byte alignment of framebuffer reads on Win32.
 * Problem showed up in demo "pdb."
 *
 * Revision 1.17  2001/07/15 16:26:39  wes
 *
 * Added missing RM_CULL_NONE code to polygon cull modes scene parms update.
 *
 * Revision 1.16  2001/06/03 20:45:46  wes
 * Removed unused vars to clean up compile warnings.
 *
 * Revision 1.15  2001/05/26 14:34:06  wes
 * Added spotlight parameters, fixed bug in matrix multiplication order
 * when doing picking.
 *
 * Revision 1.14  2001/03/31 17:12:38  wes
 * v1.4.0-alpha-2 checkin.
 *
 * Revision 1.13  2000/12/05 03:04:20  wes
 * Removed now-unnecessary glBindTexture(Glenum, 0) (that formerly
 * disabled textures). We're relying on proper functioning of the
 * OpenGL attribute stack to turn on and off texturing. This call
 * was formerly necessary because texture downloads to OpenGL occured
 * at the time RMtextures were assigned to RMnodes. Textures are now
 * downloaded at render-time.
 *
 * Revision 1.12  2000/12/03 22:35:19  wes
 * Mods for thread-safety.
 *
 * Revision 1.11  2000/08/30 02:06:41  wes
 * Removed glLoadMatrixf()s from private_setClipPlanes(). These lines
 * of OpenGL matrix stack manipulation were leftover from a long time ago.
 *
 * Revision 1.10  2000/08/28 01:36:53  wes
 * Prototype mods to clean up compile errors.
 *
 * Revision 1.9  2000/08/23 23:26:28  wes
 * Fixed a typo.
 *
 * Revision 1.8  2000/08/19 14:59:00  wes
 * Added initMatrixStackBool to anaglyph and multibuffered stereo methods.
 *
 * Revision 1.7  2000/07/20 18:55:35  wes
 * Removed glViewport() call from inside private_rmSubTreeFrame.
 * Added calls to fully set the texture env. scene parms, if present
 * inside private_updateSceneParms(). Thanks to Matt S. of VRCO
 * for turning up the glViewport problem.
 *
 * Revision 1.6  2000/05/17 14:22:39  wes
 * Fixed compile warnings on private_rmStateInit.
 *
 * Revision 1.5  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.4  2000/04/27 03:13:22  wes
 * Minor state management adjustments.
 *
 * 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.
 *
 */


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

/* PRIVATE declarations */
static void render_tree (RMpipe *renderPipe, RMnode *r,int (*filterfunc)(RMnode *),void (*perobj_func)(const RMnode *), void (*perprim_func)(RMnode *, int), void (*precam_func)(void), int (*perchannel_func)(RMnode *), void (*perstate_func)(RMstate *, int), RMenum backgroundSceneEnable, GLenum rendermode, RMstate *parent_state, int *nthState, RMenum initMatrixStack, RMstateCache *rsc);
static void private_rmMonoRender(RMnode *t, RMpipe *p);
static void private_rmRedBlueRender(RMnode *t, RMpipe *p);
static void private_rmBlueRedRender(RMnode *t, RMpipe *p);
static void private_rmMbufStereoRender(RMnode *t, RMpipe *p);
static void private_rmPipeMultiStageSerial(RMnode *n, RMpipe *p);
static void private_rmPipeMultiStageParallel(RMnode *n, RMpipe *p);
static void private_rmPipeMultiStageViewParallel(RMnode *n, RMpipe *p);

/* tmp, fall 2007 */
void rmFrameDataSync (RMpipe *drawOnPipe,
		      RMnode *subTreeToDraw)
{
    rmFrame(drawOnPipe, subTreeToDraw);
    rmSync();
}

/*
 * ----------------------------------------------------
 * @Name rmFrame
 @pstart
 void rmFrame (RMpipe *drawOnPipe, RMnode *subTreeToDraw)
 @pend

 @astart
 RMpipe *drawOnPipe - (input) a handle to an RMpipe.
 RMnode *subTreeToDraw - a handle to an RMnode (input).
 @aend

 @dstart

 Render the scene graph starting at the subtree rooted at "inputNode"
 on the "drawOnPipe."

 @dend
 * ----------------------------------------------------
 */
void rmFrame (RMpipe *drawOnPipe,
	      RMnode *subTreeToDraw)
{
    extern RMcompMgrHdr *global_RMnodePool;
    int frameRate = rmPipeGetFrameRate(drawOnPipe);
    
    if (drawOnPipe == NULL)
	return;

    if (frameRate > 0)
	private_rmPipeFPSStart(drawOnPipe); /* wait happens here */

    /* fall 2007, tmp  */
    if (rmDataSyncGetPolicy() == RM_DATA_SYNC_DRACONIAN)
    {
	while (rmMutexTryLock(global_RMnodePool->guard) != RM_MUTEX_LOCK)
	    ;
    }

    /* invoke the rendering function for this pipe on a scene graph */
    (drawOnPipe->channel_render_func)(subTreeToDraw, drawOnPipe);

    /* fall 2007, tmp  */
    if (rmDataSyncGetPolicy() == RM_DATA_SYNC_DRACONIAN)
	rmMutexUnlock(global_RMnodePool->guard);

    /* 11/1/03 added glFinish() for accurate timings.  */
    /* glFinish(); */

    if (frameRate > 0)
	private_rmPipeFPSEnd(drawOnPipe);
    
    /* increment frame number */
    drawOnPipe->frameNumber += 1;
}


/* PRIVATE */
void
private_rmSetChannelRenderFunc (RMpipe *p)
{
    /*
     * modified 1/21/2001 - the "channel renderfunc" is now a
     * function of 1) the channel display format, and 2) the
     * processing mode of the RMpipe.
     */
    RMenum pmode = rmPipeGetProcessingMode(p);

    if (pmode == RM_PIPE_SERIAL)
    {
	switch (rmPipeGetChannelFormat(p))
	{
	case RM_MONO_CHANNEL:
	case RM_OFFSCREEN_MONO_CHANNEL:
	    p->channel_render_func = private_rmMonoRender;
	    break;
	    
	case RM_REDBLUE_STEREO_CHANNEL:
	case RM_OFFSCREEN_REDBLUE_STEREO_CHANNEL:
	    p->channel_render_func = private_rmRedBlueRender;
	    break;
	    
	case RM_BLUERED_STEREO_CHANNEL:
	case RM_OFFSCREEN_BLUERED_STEREO_CHANNEL:
	    p->channel_render_func = private_rmBlueRedRender;
	    break;
	    
	case RM_MBUF_STEREO_CHANNEL:
	    p->channel_render_func = private_rmMbufStereoRender;
	    break;
	    
	default:
	    rmError(" undefined channel format for rendering.");
	    exit(1);
	}
    }
    else if (pmode == RM_PIPE_MULTISTAGE)	/* processing mode != RM_PIPE_SERIAL */
	p->channel_render_func = private_rmPipeMultiStageSerial;
    else if (pmode == RM_PIPE_MULTISTAGE_PARALLEL)
	p->channel_render_func = private_rmPipeMultiStageParallel;
    else if (pmode == RM_PIPE_MULTISTAGE_VIEW_PARALLEL)
	p->channel_render_func = private_rmPipeMultiStageViewParallel;
    else
	rmError("private_rmPipeSetChannelRenderFunc(): bogus processing mode. \n");
}


/* PRIVATE */
int
private_rmTrueFilterfunc (RMnode *r)
{
    r = NULL; 			/* foil compile warning */
    return(1);
}


/* PRIVATE */
int
private_rmFalseFilterfunc (RMnode *r)
{
    r = NULL; 			/* foil compile warning */
    return(0);
}


/* PRIVATE */
int
left_channel_filterfunc (RMnode *r)
{
    RMenum stat = private_rmNodeGetTraversalMaskChannel(r);
    
    if ((stat == RM_ALL_CHANNELS) || (stat == RM_LEFT_CHANNEL))
	return(1);
    else
	return(0);
}


/* PRIVATE */
int
right_channel_filterfunc (RMnode *r)
{
    RMenum stat = private_rmNodeGetTraversalMaskChannel(r);

    if ((stat == RM_ALL_CHANNELS) || (stat == RM_RIGHT_CHANNEL))
	return(1);
    else
	return(0);
}


/* PRIVATE */
int
opaque_and_3d_filterfunc (RMnode *r)
{
    /* this filterfunc will return 1 if the node "r" has 3D locations and is op
aque */

    RMenum vdims, opacity;

    vdims = private_rmNodeGetTraversalMaskDims(r);
    opacity = private_rmNodeGetTraversalMaskOpacity(r);

    if (((vdims == RM_RENDERPASS_3D) || (vdims == RM_RENDERPASS_ALL)) &&
	((opacity == RM_RENDERPASS_OPAQUE) || (opacity == RM_RENDERPASS_ALL)))
	return(1);
    else
	return(0);
}


/* PRIVATE */
int
transparent_and_3d_filterfunc (RMnode *r)
{
    /* this filterfunc will return 1 if the node "r" has 3D locations and any part of the object is transparent */
    RMenum vdims, opacity;

    vdims = private_rmNodeGetTraversalMaskDims(r);
    opacity = private_rmNodeGetTraversalMaskOpacity(r);

    if (((vdims == RM_RENDERPASS_3D) || (vdims == RM_RENDERPASS_ALL)) &&
	((opacity == RM_RENDERPASS_TRANSPARENT) || (opacity == RM_RENDERPASS_ALL)))
	return(1);
    else
	return(0);
}


/* PRIVATE */
int
only_2d_filterfunc (RMnode *r)
{
    RMenum vdims;
    
    vdims = private_rmNodeGetRenderpassDims(r);
    /*opacity = private_rmNodeGetRenderpassOpacity(r);*/

    if ((vdims == RM_RENDERPASS_2D) || (vdims == RM_RENDERPASS_ALL))
	return(1);
    else
	return(0);
}


/* PRIVATE */
void
private_postRenderBarrierFunc (RMpipe *p)
{
    if (p->postRenderBarrierFunc != NULL)
	(*(p->postRenderBarrierFunc))(p);
}


/* PRIVATE */
void
private_postRenderImageFuncs (RMpipe *p,
			      GLenum whichBuffer)
{
    if (p->postrenderfunc != NULL)
	private_rmPostRender(whichBuffer,p);

    if (p->postrender_depthbufferfunc != NULL)
	private_rmPostRenderDepthBuffer(whichBuffer, p);
}


/* PRIVATE */
void
private_postRenderSwapBuffersFunc (RMpipe *p)
{
    /* 12/26/02 wes */
    rmPipeSwapBuffers(p);
}


/* PRIVATE */
static void
private_rmMonoRender (RMnode *t,
		      RMpipe *p)
{
    RMenum render3DOpaqueEnable;
    RMenum render3DTransparentEnable;
    RMenum render2DOpaqueEnable;
    RMenum initMatrixStackBool;

    /* assume rendermode == GL_RENDER and that we have a double-buffered visual */
    private_rmSetBackBuffer(p);

    initMatrixStackBool = rmPipeGetInitMatrixStackMode(p);
    
    rmPipeGetRenderPassEnable(p, &render3DOpaqueEnable, &render3DTransparentEnable, &render2DOpaqueEnable);
    private_rmSubTreeFrame(p, t, GL_RENDER, NULL, NULL, NULL, NULL, render3DOpaqueEnable, render3DTransparentEnable, render2DOpaqueEnable,initMatrixStackBool);

    private_postRenderBarrierFunc(p);

    if (p->timeSyncFunc != NULL)
	(*(p->timeSyncFunc))(p);
    
    private_postRenderSwapBuffersFunc(p);
    
    private_postRenderImageFuncs(p, GL_FRONT);
}
    
/* PRIVATE */
static void
private_rmBlueRedRender (RMnode *t,
			 RMpipe *p)
{
    RMenum render3DOpaqueEnable = RM_TRUE;
    RMenum render3DTransparentEnable = RM_TRUE;
    RMenum render2DOpaqueEnable = RM_TRUE;
    RMenum initMatrixStackBool = rmPipeGetInitMatrixStackMode(p); 

    /* left channel in green/blue, right channel in red */
    rmPipeGetRenderPassEnable(p, &render3DOpaqueEnable, &render3DTransparentEnable, &render2DOpaqueEnable);

    /* assume rendermode == GL_RENDER and that we have a double-buffered visual */
    private_rmSetBackBuffer(p); 

    /* draw the left channel (green and blue) */
    glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_TRUE);
    private_rmSubTreeFrame(p, t, GL_RENDER, NULL, NULL, left_channel_filterfunc, NULL, render3DOpaqueEnable, render3DTransparentEnable, render2DOpaqueEnable,initMatrixStackBool);

    /* draw the right channel (red) */
    glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE);
    private_rmSubTreeFrame(p, t, GL_RENDER, NULL, NULL, right_channel_filterfunc, NULL, render3DOpaqueEnable, render3DTransparentEnable, render2DOpaqueEnable,initMatrixStackBool);

    /* restore color mask and do functions */
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    
    private_postRenderBarrierFunc(p);
    
    if (p->timeSyncFunc != NULL)
	(*(p->timeSyncFunc))(p);
    
    private_postRenderSwapBuffersFunc(p);
    
    private_postRenderImageFuncs(p, GL_FRONT);
}
    

/* PRIVATE */
static void
private_rmRedBlueRender (RMnode *t,
			 RMpipe *p)
{
    RMenum render3DOpaqueEnable = RM_TRUE;
    RMenum render3DTransparentEnable = RM_TRUE;
    RMenum render2DOpaqueEnable = RM_TRUE;
    RMenum initMatrixStackBool = rmPipeGetInitMatrixStackMode(p);     

    /* left channel in red, right channel in green/blue */
    rmPipeGetRenderPassEnable(p, &render3DOpaqueEnable, &render3DTransparentEnable, &render2DOpaqueEnable);
    
    /* assume rendermode == GL_RENDER and that we have a double-buffered visual */
    private_rmSetBackBuffer(p); 

    /* draw the left channel (red) */
    glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE);
    private_rmSubTreeFrame(p, t, GL_RENDER, NULL, NULL, left_channel_filterfunc, NULL, render3DOpaqueEnable, render3DTransparentEnable, render2DOpaqueEnable,initMatrixStackBool);

    /* draw the right channel */
    glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_TRUE);
    private_rmSubTreeFrame(p, t, GL_RENDER, NULL, NULL, right_channel_filterfunc, NULL, render3DOpaqueEnable, render3DTransparentEnable, render2DOpaqueEnable,initMatrixStackBool);

    /* restore color mask and do functions */
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    private_postRenderBarrierFunc(p);
    
    if (p->timeSyncFunc != NULL)
	(*(p->timeSyncFunc))(p);
    
    private_postRenderSwapBuffersFunc(p);
    
    private_postRenderImageFuncs(p, GL_FRONT);
}
    

/* PRIVATE */
static void
private_rmMbufStereoRender (RMnode *t,
			    RMpipe *p)
{
    RMenum render3DOpaqueEnable = RM_TRUE;
    RMenum render3DTransparentEnable = RM_TRUE;
    RMenum render2DOpaqueEnable = RM_TRUE;
    RMenum initMatrixStackBool = rmPipeGetInitMatrixStackMode(p); 

    rmPipeGetRenderPassEnable(p, &render3DOpaqueEnable, &render3DTransparentEnable, &render2DOpaqueEnable);

    /* assume rendermode == GL_RENDER and that we have a  double-buffered visual */

    /* draw the left channel */
    glDrawBuffer(GL_BACK_LEFT);
    private_rmSubTreeFrame(p, t, GL_RENDER, NULL, NULL, left_channel_filterfunc, NULL, render3DOpaqueEnable, render3DTransparentEnable, render2DOpaqueEnable,initMatrixStackBool);

    /* draw the right channel */
    glDrawBuffer(GL_BACK_RIGHT);
    private_rmSubTreeFrame(p, t, GL_RENDER, NULL, NULL, right_channel_filterfunc, NULL, render3DOpaqueEnable, render3DTransparentEnable, render2DOpaqueEnable,initMatrixStackBool);

    /* restore color mask and do functions */
    private_postRenderBarrierFunc(p);

    if (p->timeSyncFunc != NULL)
	(*(p->timeSyncFunc))(p);
    
    private_postRenderSwapBuffersFunc(p);
    
    private_postRenderImageFuncs(p, GL_FRONT_LEFT);
    private_postRenderImageFuncs(p, GL_FRONT_RIGHT);
}


static int frameNum=0;
/* PRIVATE */
void
private_rmSubTreeFrame (RMpipe *renderPipe,
		        RMnode *user_root,
		        GLenum renderMode,
		        void (*perobj_func)(const RMnode *),
		        void (*perprim_func)(RMnode *, int),
		        int (*perchannel_func)(RMnode *),
		        void (*perstate_func)(RMstate *, int),
		        RMenum render3DOpaque,
		        RMenum render3DTransparent,
		        RMenum render2D,
			RMenum initMatrixStack)
{
    /*
     * this routine is the shepherd which manages all activities required
     * when a new frame is to be rendered.
     */
    int     nthState = 0;
    RMenum  this_channel;
    RMpipe *p;
    RMstate rm_state;
    RMenum backgroundSceneEnable;
    RMmatrix initModel, initProjection, initTexture;
    RMstateCache *rsc = private_rmStateCacheNew();

    /*
     * Policy note (Feb 2000):
     *
     * RMnode scene parameters fall into two classes: "background" operations
     * and non-background operations. Background operations include 
     * background clear color and image tiles (framebuffer clears) and
     * background depth values and depth image tiles.
     *
     * At this time, the order of multipass rendering is:
     * 1. render 3D opaque objects
     * 2. render transparent 3d objects
     * 3. render 2D objects (we assume 2D stuff is opaque at this time;
     *    there is no 2d transparent render pass).
     *
     * We use a policy that background operations are enabled in stages
     * 1 & 3, but disabled in stage 2. Designers should be aware of this
     * policy when building scene graphs.
     *
     * A common error would be to assign a background clear color to
     * rmRootNode() - rmRootNode is processed in all rendering passes
     * (unless explicitly modified by the application). Therefore, if
     * the scene graph contains 3D objects, they will not be visible because
     * the framebuffer would be cleared twice - once during the
     * opaque 3d pass, and again during the 2D pass.
     *
     * A reasonable strategy for developers is to assign background clears,
     * cameras and viewports within a single scene graph node. That will
     * prevent a number of application-level scene graph errors.
     */

    p = renderPipe;
    
    if (user_root == NULL)
	return;

    if (initMatrixStack == RM_TRUE) /* we own OpenGL matrix stack */
    {
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	private_rmStateInit(p, &rm_state, (RMenum) renderMode, NULL, NULL,
			    NULL, NULL);
    }
    else   /* load init matrix values from the OpenGL matrix stack */
    {
	glGetFloatv(GL_MODELVIEW_MATRIX,&(initModel.m[0][0]));
	glGetFloatv(GL_PROJECTION_MATRIX,&(initProjection.m[0][0]));
	glGetFloatv(GL_TEXTURE_MATRIX,&(initTexture.m[0][0]));

	private_rmStateInit(p, &rm_state, (RMenum) renderMode,
			    &initModel,
			    NULL,
			    &initProjection,
			    &initTexture);
    }

    if (perchannel_func == NULL)
	this_channel = RM_ALL_CHANNELS;
    else
    {
	if (perchannel_func == left_channel_filterfunc)
	    this_channel = RM_LEFT_CHANNEL;
	else
	{
	    if (perchannel_func == right_channel_filterfunc)
		this_channel = RM_RIGHT_CHANNEL;
	    else
		this_channel = RM_ALL_CHANNELS;
	}
    }

    /* RMpipe level fbclears? */
    if ((p->fbClearNode != NULL) && (p->fbClearNode->fbClear != NULL))
    {
	glDisable(GL_BLEND);	/* make sure it's turned off */
	glDisable(GL_DEPTH_TEST);
	glDepthMask(GL_TRUE);
	glDisable(GL_LIGHTING);

	private_fbClear(p->fbClearNode->fbClear, &rm_state, RM_TRUE, RM_TRUE);
    }
	
    
#if DO_TIME
    rmStartTimer(); 
#endif

    /* render 3D, opaque objects */
    if (render3DOpaque == RM_TRUE)
    {
	RMstate s;

	backgroundSceneEnable = RM_TRUE;


	if (initMatrixStack == RM_TRUE)
	{
	    glMatrixMode(GL_PROJECTION);
	    glLoadIdentity();
	    glMatrixMode(GL_MODELVIEW);
	    glLoadIdentity();
	}

	glDisable(GL_BLEND);	/* make sure it's turned off */
	glEnable(GL_DEPTH_TEST);
	glDepthMask(GL_TRUE);

	memcpy((void *)&s, (void *)&rm_state, sizeof(RMstate));

	s.renderpass = RM_RENDERPASS_OPAQUE;
	s.which_channel = this_channel;
	s.renderPassDims = RM_RENDERPASS_3D; /* 8/8/04 */
	
	/* 5/11/02 - make sure actual state is same as in RMstate */
	private_rmColorMaterial(&s, rsc, RM_FALSE); /* turn off color material */
	glDisable(GL_LIGHTING);
	s.lightingActive = RM_FALSE;
	rsc->lightingActive = RM_FALSE;
	
	render_tree(renderPipe, user_root, opaque_and_3d_filterfunc, perobj_func, perprim_func, NULL, perchannel_func, perstate_func, backgroundSceneEnable, renderMode, &s, &nthState, initMatrixStack, rsc);
    }

    /* render 3D, transparent objects */
    if (render3DTransparent == RM_TRUE)
    {
	RMstate s;

		
	if (initMatrixStack == RM_TRUE)
	{
	    glMatrixMode(GL_PROJECTION);
	    glLoadIdentity();
	    glMatrixMode(GL_MODELVIEW);
	    glLoadIdentity();
	}
	memcpy((void *)&s, (void *)&rm_state, sizeof(RMstate));
	
	s.renderpass = RM_RENDERPASS_TRANSPARENT;
	s.which_channel = this_channel;
	s.renderPassDims = RM_RENDERPASS_3D; /* 8/8/04 */

	/* goes here or somewhere else? */
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);
	
	glEnable(GL_DEPTH_TEST);
	glDepthMask(GL_FALSE);

	/* 5/11/02 - make sure actual state is same as in RMstate */
	private_rmColorMaterial(&s, rsc, RM_FALSE); /* turn off color material */
	glDisable(GL_LIGHTING);
	s.lightingActive = RM_FALSE;
	rsc->lightingActive = RM_FALSE;
	
	backgroundSceneEnable = RM_FALSE; /* may no longer be necessary: 9/11/04 */
	
	render_tree(renderPipe, user_root, transparent_and_3d_filterfunc, perobj_func, perprim_func, NULL, perchannel_func, perstate_func, backgroundSceneEnable, renderMode, &s, &nthState, initMatrixStack, rsc);
	
	glDisable(GL_BLEND);	/* make sure it's turned off */
    }

    /* render 2D stuff on top of the 3D scene */
    if (render2D == RM_TRUE)
    {
	RMstate s;


	if (initMatrixStack == RM_TRUE)
	{
	    glMatrixMode(GL_PROJECTION);
	    glLoadIdentity();
	    glMatrixMode(GL_MODELVIEW);
	    glLoadIdentity();
	}
	memcpy((void *)&s, (void *)&rm_state, sizeof(RMstate));
	
	s.renderpass = RM_RENDERPASS_OPAQUE;
	s.which_channel = this_channel;
	s.renderPassDims = RM_RENDERPASS_2D; /* 8/8/04 */
	
	glDisable(GL_DEPTH_TEST);
	/* 5/11/02 - make sure actual state is same as in RMstate */
	private_rmColorMaterial(&s, rsc, RM_FALSE); /* turn off color material */
	glDisable(GL_LIGHTING);
	s.lightingActive = RM_FALSE;
	rsc->lightingActive = RM_FALSE;
	
	backgroundSceneEnable = RM_TRUE; /* may no longer be necessary 9/11/04 */

	render_tree(renderPipe, user_root, only_2d_filterfunc, perobj_func, perprim_func, NULL, perchannel_func, perstate_func, backgroundSceneEnable, renderMode, &s, &nthState, initMatrixStack, rsc);
    }

#if DO_TIME
    glFinish();			/* needed for accurate network timing */
    rmElapsedTime(" RM rendering time ");
#endif

    private_rmStateCacheDelete(rsc);
    frameNum++;
}


/* PRIVATE
 *
 * private_setViewport - an internal routine used to create a viewport
 * in OpenGL from RM-style scene parameters.
 */
int
private_setViewport (const RMnode *r,
		     RMstate *s,
		     int push_attrib_enable,
		     RMenum applyGL)
{
    int   doScissor = 1;
    GLint x, y, w, h;

    /*
     * do we need the scissor test? if the viewport is anything but
     * a full-window, then we do need the scissor test.
     */
    if ((r->scene_parms->viewport[0] == 0.0F) && (r->scene_parms->viewport[1] == 0.0F) &&
	(r->scene_parms->viewport[2] == 1.0F) && (r->scene_parms->viewport[3] == 1.0F))
	doScissor = 0;

    x = r->scene_parms->viewport[0] * s->w;
    y = r->scene_parms->viewport[1] * s->h;
    w = r->scene_parms->viewport[2] * s->w - x;
    h = r->scene_parms->viewport[3] * s->h - y;

    if (applyGL == RM_TRUE)
    {
	glViewport(x, y, w, h);
	
	if (doScissor == 1)
	{
	    glEnable(GL_SCISSOR_TEST);
	    glScissor(x, y, w, h);
	}
	else
	    glDisable(GL_SCISSOR_TEST);
	
    }

    /* now update the RMstate */
#if 0    
    memcpy((void *)(s->vp), (void *)r->scene_parms->viewport, sizeof(float)*4);
#endif

    s->vp[0] = x;
    s->vp[1] = y;
    s->vp[2] = w;
    s->vp[3] = h;

    private_rmComputeViewportMatrix(s->vp, (float)w, (float)h, &(s->vpm));

#if 0
    s->vpm.m[0][0] = s->vp[0] * 0.5F;
    s->vpm.m[1][1] = s->vp[1] * 0.5F;
    s->vpm.m[3][0] = s->vpm.m[0][0];
    s->vpm.m[3][1] = s->vpm.m[1][1];
#endif

    push_attrib_enable = 1; /* needed for state tracking */

    return(push_attrib_enable); 
}


/* PRIVATE */
void
private_computeStereoOffset (RMcamera3D *c,
			     RMenum whichChannel,
			     RMcamera3D *r)
{
    float       focaldelta;
    double      mag_eye_at;
    double      delta;
    RMvertex3D  eye_at;
    RMvertex3D  eye_at_cross_up;
    RMvertex3D *local_up;
    RMvertex3D  local_eye, local_at;

    *r = *c;

    focaldelta = rmCamera3DGetFocalDistance (c);

    eye_at.x = c->eye.x - c->at.x;
    eye_at.y = c->eye.y - c->at.y;
    eye_at.z = c->eye.z - c->at.z;
    
    rmVertex3DMagNormalize(&eye_at, &mag_eye_at);

    /* assume the up vector is normalized */
    local_up = &c->up;
    rmVertex3DCross(&eye_at, local_up, &eye_at_cross_up);
    
    delta = sin(RM_DEGREES_TO_RADIANS(rmCamera3DGetEyeSeparation(c) * 0.5));
    delta *= mag_eye_at;
    
    if (whichChannel == RM_LEFT_CHANNEL)
    {
	local_eye.x = c->eye.x - (delta * eye_at_cross_up.x);
	local_eye.y = c->eye.y - (delta * eye_at_cross_up.y);
	local_eye.z = c->eye.z - (delta * eye_at_cross_up.z);
    }
    else
    {
	local_eye.x = c->eye.x + (delta * eye_at_cross_up.x);
	local_eye.y = c->eye.y + (delta * eye_at_cross_up.y);
	local_eye.z = c->eye.z + (delta * eye_at_cross_up.z);
    }
	
    local_at.x = c->eye.x - (eye_at.x * mag_eye_at * focaldelta);
    local_at.y = c->eye.y - (eye_at.y * mag_eye_at * focaldelta);
    local_at.z = c->eye.z - (eye_at.z * mag_eye_at * focaldelta);

    r->eye = local_eye;
    r->at = local_at;
}
	    

#define RM_FARZ 0.99999

/* PRIVATE */
void
private_drawCameraPickableQuad (void (*perobj_func)(const RMnode *),
			        RMnode *n)
{
    /* this block of code draws the pickable background quad */
    if (perobj_func != NULL)
	(*perobj_func)(n);
    
    glMatrixMode(GL_PROJECTION);
    
    glOrtho((GLfloat)(-1.0), (GLfloat)(1.0), (GLfloat)(-1.0), (GLfloat)(1.0), (GLfloat)(1.0), (GLfloat)(-1.0));

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    
    glBegin(GL_QUADS);

#if 0
    glColor3f(0.3F, 0.3F, 0.3F); /* color used in debugging */
#endif
    glVertex3f(-1.0, -1.0, RM_FARZ);
    glVertex3f(1.0, -1.0, RM_FARZ);
    glVertex3f(1.0, 1.0, RM_FARZ);
    glVertex3f(-1.0, 1.0, RM_FARZ);

    glEnd();
    glPopMatrix();
}


#define MODELVIEW_MATRIX_CHANGED	1
#define PROJECTION_MATRIX_CHANGED	2
#define TEXTURE_MATRIX_CHANGED		4

/* PRIVATE */
int
private_collectAndApplyMatrices (RMstate *rState,
				 RMnode *n,
				 void (*perObjFunc)(const RMnode *),
				 GLenum renderMode,
				 int *pushedAttribsReturn,
				 RMenum applyGL)
{
    int      viewMatrixChange = 0;
    int      modelMatrixChange = 0;
    int      textureMatrixChange = 0;
    int      pushedAttribs = 0;
    int      returnStatus = 0;
    RMmatrix model, view, proj, pick;
    RMmatrix oglModelView;
    
    /*
     * this routine will determine what changes to the matrix stack,
     * if any, are dictated by parms at this node. if any changes
     * are needed, this routine will accumulate transformations
     * and do a glLoadMatrixf() on the proper matrix stack.
     *
     * note that RM maintains separate view, model & projection
     * matrices in the RMstate object.
     *
     * the return value from this routine is the bitwise OR of the following:
     * MODELVIEW_MATRIX_CHANGED,PROJECTION_MATRIX_CHANGED  and
     * TEXTURE_MATRIX_CHANGED. each of these bits indicates which of
     * corresponding OpenGL matrix stacks was modified by this routine. a
     * return value of zero means that no matrix stacks were modified.
     *
     * at this time (feb 2000), all OpenGL state management is
     * done using glPushAttrib()/glPopAttrib(). this is a costly
     * operation, so in the future, a performance enhancement will be
     * to do something more efficient.
     *
     * in the future, we want to cache the matrix associated with
     * 2D/3D cameras so we don't have to recompute it on each frame.
     *
     * the viewport is processed here, rather than later, because the
     * camera routines have occasion to use the current viewport.
     *
     */

    if (n->scene_parms && n->scene_parms->viewport)	/* do viewport */
	pushedAttribs = private_setViewport(n, rState, pushedAttribs, applyGL);

    if (n->scene_parms && n->scene_parms->camera3d)
    {
	RMcamera3D tmp;
	
	if ((rState->which_channel != RM_ALL_CHANNELS) && (rmCamera3DGetStereo(n->scene_parms->camera3d) == RM_TRUE))
	{
	    /*
	     * for binocular stereo, we compute a new camera model for
	     * the left or right eye, then use that modified camera in
	     * the derivation of a view matrix.
	     */
	    private_computeStereoOffset(n->scene_parms->camera3d, rState->which_channel, &tmp);
	}
	else
	    tmp = *(n->scene_parms->camera3d);
	
	rmCamera3DComputeViewMatrix(&tmp, &view, &proj);

	if (renderMode == GL_SELECT)
	{
	    private_rmComputePickMatrix(rState, &pick);
	    rState->pick = pick;
	    private_drawCameraPickableQuad(perObjFunc, n);
	}

	/* nested view matrices are permissible, but weird. transformations
	 occur such that child transformations apply first. */
	rmMatrixMultiply(&view, &(rState->view), &view);
	rState->view = view;

	rmMatrixMultiply(&(rState->projection), &proj, &(rState->projection));
	viewMatrixChange = 1;
    }   
    else if (n->scene_parms && n->scene_parms->camera2d)
    {

	rmCamera2DComputeViewMatrix(n->scene_parms->camera2d, &view);
	rState->view = view;

	if (renderMode == GL_SELECT)
	{
	    private_rmComputePickMatrix(rState, &pick);
	    rState->pick = pick;
	    private_drawCameraPickableQuad(perObjFunc, n);
	}
	rmMatrixIdentity(&proj);
	rState->projection = proj;
	viewMatrixChange = 1;
    }

    if ((n->transforms != NULL) && (n->transforms->transform_mode != RM_TRANSFORM_IGNORE))
    {
	rmNodeGetCompositeModelMatrix(n, &model);

	/* texture and model matrix transformations accumulate - child transformations are applied first */
	if (n->transforms->transform_mode == RM_TRANSFORM_TEXTURE)
	{
	    rmMatrixMultiply(&model, &(rState->textureMatrix), &model);
	    rState->textureMatrix = model;
	    textureMatrixChange = 1;
	}
	else
	{
	    rmMatrixMultiply(&model, &(rState->model), &model);
	    rState->model = model;
	    modelMatrixChange = 1;
	}
    }
    
    /* now apply the matrices to the RMstate object - load the matrices into OpenGL */
    if (textureMatrixChange == 1)
    {
	if (applyGL == RM_TRUE)
	{
	    glMatrixMode(GL_TEXTURE);
	    glLoadMatrixf(&(model.m[0][0]));
	
	    glMatrixMode(GL_MODELVIEW);
	}
	returnStatus = TEXTURE_MATRIX_CHANGED;
    }
    else if (modelMatrixChange==1 || viewMatrixChange == 1)
    {
	RMmatrix m;
	
	returnStatus = MODELVIEW_MATRIX_CHANGED;
	rmMatrixMultiply(&(rState->model), &(rState->view), &oglModelView);
	
	if (viewMatrixChange == 1)
	{
	    rmMatrixMultiply(&(rState->projection), &(rState->pick), &m); 

	    if (applyGL == RM_TRUE)
	    {
		glMatrixMode(GL_PROJECTION);
		glLoadMatrixf(&(m.m[0][0]));
	    }
	    returnStatus |= PROJECTION_MATRIX_CHANGED;
	}

	if (applyGL == RM_TRUE)
	{
	    glMatrixMode(GL_MODELVIEW);
	    glLoadMatrixf(&(oglModelView.m[0][0]));
	}
	rState->modelView = oglModelView;
	rmMatrixMultiply(&(rState->modelView), &(rState->projection), &(rState->composite));
    }

    /* leave this routine in glMatrixMode(GL_MODELVIEW) */
    if (pushedAttribs == 1)
       rState->attrib_stack_depth++;

    *pushedAttribsReturn = pushedAttribs;

    return(returnStatus);
}

static int
private_invokeSerialCallbacks(RMnode *r,
			      int (*afunc)(const RMnode *, const RMstate *),
			      int (*bfunc)(const RMnode *, const RMstate *),
			      RMstate *s,
			      RMenum honorStatus)
{
    int rstat=1;

    /*
     * serial view+render traversal pre/post callbacks:
     *
     * afunc represents the view traversal callback, and bfunc represents
     * the render traversal callback.
     *
     * if afunc returns <= 0 AND honorStatus == RM_TRUE, return immediately (including status).
     * if bfunc, then compute it's status.
     * return the status;
     *
     * honorStatus is used since the returnstatus from the postcallback
     * function is supposed to be ignored.
     */

    if (afunc != NULL)
    {
	/* do we need to set a value in parent state to say this
	 is the view traversal? */
	rstat = (*afunc)(r, (const RMstate *)s);
	if ((rstat <= 0) && (honorStatus == RM_TRUE))
	    return(rstat);
    }

    if (bfunc != NULL)
	/* do we need to set a value in parent state to say this
	 is the draw traversal? */
	rstat = (*bfunc)(r, (const RMstate *)s);

    return(rstat);
}

/* PRIVATE
 *
 * render_tree() is the main rendering workhorse. it does a recursive traversal
 * of the scene graph, processes the multipass filtering functions, node
 * callbacks and invokes draw routines for each primitive.
 *
 * higher-level routines are used to invoke this routine, setting the node
 * filter functions and other initialization parameters.
 */
static void
render_tree (RMpipe *renderPipe,
	     RMnode *r,
	     int (*filterfunc)(RMnode *), /* used to filter objects for rendering */
	     void (*perobj_func)(const RMnode *),
	     void (*perprim_func)(RMnode *, int),
	     void (*precam_func)(void),
	     int (*perchannel_func)(RMnode *),
	     void (*perstate_func)(RMstate *, int),
	     RMenum backgroundSceneEnable,
	     GLenum rendermode,
	     RMstate *parent_state,
	     int *nthState,
	     RMenum initMatrixStack,
	     RMstateCache *rsc)
{
    int          status;
    RMenum       display_status;
    int          i;
    int          pushed_attribs=0;
    int          newMatrices=0;
    int        (*channelfunc)();
    RMprimitive *p;
    RMstate      local_state;

    /* pre traversals for both view and draw are processed here - this is
     serial rendering code. the view traversal funcs are called first,
     followed by the rendering traversal routine. */
    
    if ((r->viewPretraverseCallback != NULL) || (r->renderPretraverseCallback != NULL))
    {
	int   rstat;
	rstat = private_invokeSerialCallbacks(r, r->viewPretraverseCallback, r->renderPretraverseCallback, parent_state, RM_TRUE);
	
	/*
	 * when the pretraverse routine returns something <= 0, skip
	 * any further processing of this node, except we need to call
	 * the post-traversal routine, if defined.
	 */
	if (((r->renderPosttraverseCallback != NULL) || (r->viewPosttraverseCallback)) && (rstat <= 0))
	{
	    rstat = private_invokeSerialCallbacks(r, r->viewPosttraverseCallback, r->renderPosttraverseCallback, parent_state, RM_FALSE);
	}
	if (rstat <= 0)
	    return;
    }
    if (perchannel_func == NULL)
	channelfunc = private_rmTrueFilterfunc;
    else
	channelfunc = perchannel_func;

    /* early termination tests  */

    display_status = rmNodeGetTraverseEnable(r);

    status = (display_status == RM_TRUE) && ((*filterfunc)(r)) 
	&& (*channelfunc)(r);

    if (status != 0)
    {
	/* if doing a pick... */
	if (rendermode == GL_SELECT)
	{
	    /* make sure the node's pick enable attribute is set */
	    RMenum pickEnable = rmNodeGetPickEnable(r);
	    if (pickEnable == RM_FALSE)
		status = 0; 	/* early termination */
	}
    }

    /*
     * at this point, if status == 0, the node will not be
     * displayed because it did not pass the filtering tests. 
     * before returning, we need to invoke the post-traversal
     * function, if any. 
     */
    if (status == 0)
    {
	private_invokeSerialCallbacks(r, r->viewPosttraverseCallback, r->renderPosttraverseCallback, parent_state, RM_FALSE);
	return;
    }

    /* use a special routine here to see if we need to make
     any changes to the rendering state, and if this node includes any
     changes to the current scene parms. */
    local_state = *parent_state;
    
    newMatrices = private_collectAndApplyMatrices(&local_state, r, perobj_func, rendermode, &pushed_attribs, RM_TRUE);

#if 0
    /* the following shows a divergence in processing paths in the
       RM_PIPE_SERIAL vs. RM_PIPE_MULTISTAGE* code bases. we need to
       unify these divergent paths in a future version of the code. */
    {
	/* debug test. */
	int t1 =  ((r->scene_parms != NULL) || (r->rprops != NULL) || (r->sprops != NULL));
	int t2 = (private_rmNodeGetAttribMask(r) != 0);

	if (t2 && !t1)
	    printf(" rmframe: odd truth test! \n");
    }
#endif

    /* honor scene parms, if any */
    if ((r->scene_parms != NULL) || (r->rprops != NULL) || (r->sprops != NULL))
    {
	/* starting in RM 1.6.0, we're using the same call sequence
	 for updating the OpenGL and local state, including pushattrib.
	 This code still needs more cleanup but appears to be functioning
	 correctly in all available test cases.
	*/
	extern int mtUpdateSceneParms (const RMnode *r, RMstate *rState, RMpipe *renderPipe, RMstateCache *rsc);
	extern void mtUpdateRenderProps(const RMnode *r, RMstate *rState, RMstateCache *rsc);
	extern void mtUpdateSurfaceProps(const RMnode *r, RMstate *rState);
	
	if (private_rmNodeGetAttribMask(r) != 0)
	{
	    private_rmGLPushAttrib(renderPipe, r, private_rmNodeGetAttribMask(r));
	    pushed_attribs = 1;
	}

	if (r->scene_parms != NULL)
	    mtUpdateSceneParms(r, &local_state, renderPipe, rsc);

	if (r->rprops != NULL)
	    mtUpdateRenderProps(r, &local_state, rsc);
    
	if (r->sprops != NULL)
	    mtUpdateSurfaceProps(r, &local_state);

	private_rmStateCacheSync(&local_state, rsc);

	if (perstate_func != 0 && pushed_attribs != 0)
	    (*perstate_func)(&local_state, *nthState);
	
	if (pushed_attribs != 0)
	{
	    *nthState += 1;
	    /*	    private_rmStateCacheSync(&local_state, rsc); used during dev/debug  */
	}
    }
    
#if (DEBUG_LEVEL & DEBUG_GLSTATECHECK)
    if (pushed_attribs)
    {
	char buf[128];
	sprintf(buf," render_tree post-attrib-update:%s ",r->object_info.name);
	private_rmLightStateConsistencyCheck(buf, &local_state, rsc);
    }
#endif

    /* xeq any fb/db clears, if requested */
    if (r->fbClear != NULL)
    {
	private_fbClear(r->fbClear, &local_state, RM_TRUE, backgroundSceneEnable);

    }

#if 0
    /* 1.6.2 and earlier */
    if (r->nchildren == 0) /* a leaf node */
#endif
    if (private_rmNodeGetNumChildren(r) == 0) /* a leaf node */
    {
	/* filterfunc() is called on a per-node basis to selectively
	 not render certain nodes.  for example, we might want to render
	 only 3d, opaque nodes in one pass, then 3d transparent nodes
	 in a later pass.   here's where that filtering gets done. */
	
	if ((display_status == RM_TRUE) && ((*filterfunc)(r))
	    && (*channelfunc)(r))
	{
	    int nprims = 0;
	    nprims = rmNodeGetNumPrims(r);

	    for (i = 0; i < nprims; i++) 
	    {
	        void (*renderfunc)  OGLPRIMPARMLIST() ;

		if (perprim_func)
		    (*perprim_func)(r, i);
		
		p = r->prims[i]; /* bad, use API!! */

		if (p != NULL)
		{
#if (DEBUG_LEVEL & DEBUG_GLSTATECHECK)
		    {
			char buf[128];
			sprintf(buf," render_tree pre-render:%s ",r->object_info.name);
			private_rmLightStateConsistencyCheck(buf, &local_state, rsc);
		    }
#endif	    
		    renderfunc = p->renderfunc;
		    (*renderfunc)(p, r, &local_state, renderPipe, rsc);
		    
#if (DEBUG_LEVEL & DEBUG_GLERRORCHECK)
		    {
			char buf[128];

			sprintf(buf, " after prim draw routine %s ", r->object_info.name);
			rmGLGetError(buf);
		    }
#endif
#if (DEBUG_LEVEL & DEBUG_GLSTATECHECK)
		    {
			sprintf(buf," render_tree post-render:%s ",r->object_info.name);
			private_rmLightStateConsistencyCheck(buf, &local_state, rsc);
		    }
#endif
		}
	    } /* loop over prims */
	} /* passes filter test */
    } /* a leaf node */
    else /* walk the tree */
    {
	RMenum  status;
        RMnode *c;

	/* walk the subtree iff traverse-enable is true */

	status = rmNodeGetTraverseEnable(r);

	if (status == RM_TRUE)
	{
	    if (r->viewSwitchCallback != NULL)
	    {
		int   indx, status;
		int (*afunc)(const RMnode *, const RMstate *);

		afunc = r->viewSwitchCallback;
		indx = (*afunc)(r, &local_state);
/*		status = ((indx >= 0) && (indx < r->nchildren)); 1.6.2 and earlier*/
		status = ((indx >= 0) && (indx < private_rmNodeGetNumChildren(r)));

		/* if the switch callback returns something less than zero, depth traversal is discontinued */
		if (status == 1)
		{
/*		    c = r->children[indx]; 1.6.2 */
		    c = private_rmNodeGetIthChild(r, indx);
		    
		    if (RM_ASSERT(c, "NULL child error: the index returned by the switch callback function indexes a NULL child.\n") == RM_CHILL)
			render_tree(renderPipe, c, filterfunc, perobj_func, perprim_func, precam_func, perchannel_func, perstate_func, backgroundSceneEnable, rendermode, &local_state, nthState, initMatrixStack, rsc);
/*			private_rmStateCacheSync(&local_state, rsc); used during dev/debug */
		}
	    }
	    else
	    {
/*		for (i = 0; i < r->nchildren; i++) 1.6.2*/
		for (i = 0; i < private_rmNodeGetNumChildren(r); i++) 
		{
/*		    c = r->children[i]; 1.6.2 */
		    c = private_rmNodeGetIthChild(r, i);
		    render_tree(renderPipe, c, filterfunc, perobj_func, perprim_func, precam_func, perchannel_func, perstate_func, backgroundSceneEnable, rendermode, &local_state, nthState, initMatrixStack, rsc);
		    
/*			private_rmStateCacheSync(&local_state, rsc); used during dev/debug */
		}
	    }
	}
    }

    if (pushed_attribs)
    {
	private_rmGLPopAttrib(renderPipe, parent_state, rsc);
	
#if (DEBUG_LEVEL & DEBUG_GLSTATECHECK)
	{
	    char buf[128];

	    sprintf(buf," render_tree post-popattrib:%s ",r->object_info.name);
	    private_rmLightStateConsistencyCheck(buf, parent_state, rsc);
	}
#endif
    }
    if (newMatrices != 0)
    {
	if (newMatrices & TEXTURE_MATRIX_CHANGED)
        {
	    glMatrixMode(GL_TEXTURE);
	    glLoadMatrixf(&(parent_state->textureMatrix.m[0][0]));
	}
	if (newMatrices & PROJECTION_MATRIX_CHANGED)
	{
	    glMatrixMode(GL_PROJECTION);
	    glLoadMatrixf(&(parent_state->projection.m[0][0]));
	}
	if (newMatrices & MODELVIEW_MATRIX_CHANGED)
        {
	    glMatrixMode(GL_MODELVIEW);
	    glLoadMatrixf(&(parent_state->modelView.m[0][0]));
	}
    }

   if ((r->viewPosttraverseCallback != NULL) || (r->renderPosttraverseCallback != NULL))
   {
	private_invokeSerialCallbacks(r, r->viewPosttraverseCallback, r->renderPosttraverseCallback, &local_state, RM_FALSE);
    }
}


/* PRIVATE
 *
 * dumplight: internal routine that creates a light source in OpenGL
 * from an RMlight object.
 */
static void
dumplight (GLenum lightno,
	   RMlight *l)
{
    GLfloat pos[4];
    
    glEnable(lightno);
    glLightfv(lightno, GL_AMBIENT, (GLfloat *)&(l->ambientLightColor));
    glLightfv(lightno, GL_DIFFUSE, (GLfloat *)&(l->diffuseLightColor));
    glLightfv(lightno, GL_SPECULAR, (GLfloat *)&(l->specularLightColor));

    memcpy(pos, &(l->lightXYZ), sizeof(RMvertex3D));

    if (l->ltype == RM_LIGHT_DIRECTIONAL)
	pos[3] = 0.0F;
    else
	pos[3] = 1.0F;

    glLightfv(lightno, GL_POSITION, pos);

    /* todo: spot parms, attenuation */
    if (l->ltype == RM_LIGHT_SPOT)
    {
	glLightf(lightno, GL_SPOT_CUTOFF, l->spotCutoff);
	glLightfv(lightno, GL_SPOT_DIRECTION, (float *)&(l->spotDirection));
	glLightf(lightno, GL_SPOT_EXPONENT, l->spotExponent);
    }

    glLightf(lightno, GL_CONSTANT_ATTENUATION, l->constantAttenuation);
    glLightf(lightno, GL_LINEAR_ATTENUATION, l->linearAttenuation);
    glLightf(lightno, GL_QUADRATIC_ATTENUATION, l->quadraticAttenuation);
}


/* PRIVATE
 *
 * process_scene_lights: an internal routine used to activate OpenGL
 * lights given RM-style light source scene parameters.
 */
int
process_scene_lights (const RMnode *r,
		      int push_attrib_enable,
		      RMstate *s,
		      RMenum applyGL,
		      RMstateCache *rsc)
{
    int i;

    /*
     * 8/8/04 - if we're doing a 2D render traversal, skip doing the
     * lights.
     */
    if ((s->renderPassDims == RM_RENDERPASS_2D) ||
	(r->scene_parms == NULL))
	return 0;

    /* we assume that r->scene_parms is valid. */
    for (i = 0; i < RM_MAX_LIGHTS; i++)
    {
	RMlight *l = r->scene_parms->lightSources[i];
	if ((l != NULL) && (l->enabled == RM_TRUE))
	{
	    if (applyGL == RM_TRUE)
	    {
		/* 8/8/04 - do a conditional activation to reduce the number
		 of redundant state changes. */
		if (s->lightingActive != RM_TRUE)
		    glEnable(GL_LIGHTING);
		
		s->lightingActive = RM_TRUE;
		if (rsc != NULL)
		    rsc->lightingActive = RM_TRUE;

		dumplight(GL_LIGHT0 + i, l);
	    }
	    /* now update the render state. */
	    s->lightSources[i] = l;
	}
    }
    
    if (r->scene_parms->lmodel != NULL)
    {
	int           localview;
	int           twoside;
	RMlightModel *lm = r->scene_parms->lmodel;
	    
	localview = (lm->localViewerEnable == RM_TRUE) ? 1 : 0;
	twoside = (lm->twoSideEnable == RM_TRUE) ? 1 : 0; 

	if (applyGL == RM_TRUE)
	{
	    glLightModelfv (GL_LIGHT_MODEL_AMBIENT, (GLfloat *)&(lm->globalAmbient));
	    glLightModeli (GL_LIGHT_MODEL_LOCAL_VIEWER, localview);
	    glLightModeli (GL_LIGHT_MODEL_TWO_SIDE, twoside);
	}

	/* update the render state */
	s->lmodel = lm;
    }
    return(push_attrib_enable);
}


/* PRIVATE  
 *
 * private_setBackgroundTile - an internal routine that draws a
 * background image into the framebuffer. if the image is smaller than
 * the viewport, it is tiled across the entire viewport, starting at
 * the upper-left corner of the window. if the tile does not evenly
 * fill the viewport, the "ragged" edges are on the right and bottom
 * of the viewport.
 */
int
private_setBackgroundTile (const internals_RMfbClear *fbClear,
			   RMstate *s,
			   int push_attrib_enable,
			   RMenum applyGL)
{
    int      dwidth, dheight, tiles_across, tiles_high, i, j, dx, dy;
    float    xzoom, yzoom;
    GLint    save_viewport[4];
    RMimage *tile;

    if (applyGL == RM_FALSE)
	return 0;

    /*
     * todo: GL_ALL_ATTRIB_BITS is clearly overkill; find a smaller mask.
     */
    glPushAttrib(GL_ALL_ATTRIB_BITS);
    
    dwidth = s->vp[2]; /* pixels across in viewport */
    dheight = s->vp[3]; /* pixels high in viewport */

    tile = fbClear->bgImageTile;
    rmImageGetPixelZoom(tile, &xzoom, &yzoom);

    dx = tile->w * xzoom;
    dy = tile->h * yzoom;
	
    tiles_across = dwidth / dx;
    tiles_high = dheight / dy;

    if (dheight % dy != 0)
	tiles_high++;

    if (dwidth % dx != 0)
	tiles_across++;

    /*
     * first, tile the viewport with the image
     *
     * steps we have to take to make this happen:
     * 1. turn off lighting
     * 2. establish the appropriate camera, preserving whatever is
     *    on the projection matrix stack.
     * 3. create identity transform, preserving whatever is on the
     *    modelview matrix stack.
     * 4. draw the tile a bunch of times.
     * 5. restore the saved matrices.
     *
     * TODO: only do one drawpixels. the rest can be done more
     * efficiently with copypixels. performance degrades as the size
     * of the image tile gets smaller.
     *
     * June 2005: ToDO: alternate: reinstate pre-multistage display lists
     */
    glDisable(GL_LIGHTING);
    glDisable(GL_DEPTH_TEST);

    /*
     * don't need to set state in RMstate object because we are
     * pushing/popping the OpenGL state inside this routine.
     */

    glGetIntegerv(GL_VIEWPORT, save_viewport); 

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    glOrtho(0.0, (double)save_viewport[2], 0.0, (double)save_viewport[3], -1.0, 1.0);
    
    glPixelZoom(xzoom, yzoom);
    {
	int ix, iy = 0;

	for (j = 0; j < tiles_high; j++)
	{
	    for (i = 0, ix = 0; i < tiles_across; i++, ix += dx)
	    {
		glRasterPos2i(ix, iy);
#if 0
    /* 10/5/2000 all RMimage display list stuff removed during SBIR work */

		glCallList(tile->d1);

		/*
		 * this hunk of code was left in so that we can play
		 * around w/ testing of display lists for bg image tiles
		 * and compare w/raw drawpixels calls.
		 */
		
		glDrawPixels(tile->w, tile->h, private_rmImageGetOGLFormat(tile), private_rmImageGetOGLType(tile), rmImageGetPixelData(tile));
#endif		
		private_glDrawPixels(tile->w, tile->h, private_rmImageGetOGLFormat(tile), private_rmImageGetOGLType(tile), private_rmImageGetPixelData(tile), tile);
		
	    }
	    iy += dy;
	}
    }

    /* restore matrices */
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();

    glMatrixMode(GL_PROJECTION);
    glLoadMatrixf((float *)&(s->projection.m[0][0]));
    
    glPopAttrib();

    return(push_attrib_enable); 
}


/* PRIVATE
 *
 * private_setBackgroundColor - an internal routine used to perform a
 * framebuffer clear using a background color scene parameter.
 */
int
private_setBackgroundColor (const internals_RMfbClear *fbClear,
			    RMstate *s,
			    int push_attrib_enable,
			    RMenum applyGL)
{
    GLclampf red, g, b, a;

    s = NULL; 			/* foil compiler warning */
    /* future: update RMstate to indicate the current background color */
    
    red = fbClear->bgColor->r;
    g = fbClear->bgColor->g;
    b = fbClear->bgColor->b;
    a = fbClear->bgColor->a;

    if (applyGL == RM_TRUE)
    {
	glClearColor(red, g, b, a);
	glClear(GL_COLOR_BUFFER_BIT);
    }
    
    /* no changes to RMstate at this time - maybe in the future we'll add background color & tile to the RMstate */
    return(push_attrib_enable);
}


/* PRIVATE
 *
 * private_setBackgroundDepthImage - internal routine used to tile the
 * depth buffer with a depth image. if the source image is smaller than
 * the viewport, the source image is tiled across the depth buffer starting
 * at the upper left corner of the viewport. if the source image does not
 * evenly divide the viewport, the ragged edges are on the right and bottom.
 */
int
private_setBackgroundDepthImage (const internals_RMfbClear *fbClear,
				 RMstate *s,
				 int push_attrib_enable,
				 RMenum applyGL)
{
    int      dwidth, dheight, tiles_across, tiles_high, i, j, dx, dy;
    float    xzoom, yzoom;
    GLint save_viewport[4];
    RMimage *tile;

    if (applyGL == RM_FALSE)
	return 0;

    glPushAttrib(GL_ALL_ATTRIB_BITS);
	    
    glGetIntegerv(GL_VIEWPORT, save_viewport);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    glEnable(GL_DEPTH_TEST);
    glDisable(GL_LIGHTING);
    glDepthMask(GL_TRUE);
    glDepthFunc(GL_ALWAYS);

    /* disable color mask - so depth image doesn't get
       written to the color buffer! */
    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

    tile = fbClear->depthImage;
    
    rmImageGetPixelZoom(tile, &xzoom, &yzoom);
    glPixelZoom(xzoom, yzoom);

    dx = tile->w * xzoom;
    dy = tile->h * yzoom;
	
    dwidth = s->vp[2] - s->vp[0]; /* pixels across in viewport */
    dheight = s->vp[3] - s->vp[1]; /* pixels high in viewport */

    glOrtho((GLdouble)(s->vp[0]), (GLdouble)(s->vp[2]), (GLdouble)(s->vp[1]), (GLdouble)(s->vp[3]), -1.0, 1.0);
	    
    tiles_across = dwidth / dx;
    tiles_high = dheight / dy;
	
    if (dheight % dy != 0)
	tiles_high++;
    if (dwidth % dx != 0)
	tiles_across++;
#if 0
    /* 10/5/2000 - all RMimage display list code removed for SBIR work */
    if (tile->d1 == -1)
    {
	float *p, t;		/* temp */
	
	tile->d1 = glGenLists(1);
	glNewList(tile->d1, GL_COMPILE);

	p = (float *)(tile->pixeldata);
	t = *p;

	private_glDrawPixels(tile->w, tile->h, private_rmImageGetOGLFormat(tile), private_rmImageGetOGLType(tile), private_rmImageGetPixelData(tile), tile);
	glEndList();
    }
#endif

    {
	int ix, iy = 0;

	for (j = 0; j < tiles_high; j++)
	{
	    for (i = 0, ix = 0; i < tiles_across; i++, ix += dx)
	    {
		glRasterPos3i(ix, iy, 0);
#if 0
		/* 10/5/2000 - all RMimage display list code removed, SBIR */
		glCallList(tile->d1);
		/*
		 * this hunk of code was left in so that we can play
		 * around w/ testing of display lists for bgimage tiles
		 * and compare w/raw drawpixels calls.
		 */

		glDrawPixels(private_rmImageGetWidth(tile), private_rmImageGetHeight(tile), private_rmImageGetOGLFormat(tile), private_rmImageGetOGLType(tile), rmImageGetPixelData(tile));
#endif
		private_glDrawPixels(tile->w, tile->h, private_rmImageGetOGLFormat(tile), private_rmImageGetOGLType(tile), private_rmImageGetPixelData(tile), tile);
		
	    }
	    iy += dy;
	}
    }

    /* restore matrices */
    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixf((float *)&(s->modelView.m[0][0]));

    glMatrixMode(GL_PROJECTION);
    glLoadMatrixf((float *)&(s->projection.m[0][0]));

    glPopAttrib();

    return(push_attrib_enable);
}


/* PRIVATE 
 *
 * private_setFog() - an internal routine used to activate OpenGL fogging
 * given RM-style scene parameters.
 */
int
private_setFog (const RMnode *r,
	        int push_attrib_enable,
	        RMstate *rState,
	        RMenum applyGL)
{
    if (r->scene_parms == NULL)
	return 0;
    
    if (r->scene_parms->fog)
    {
	if (applyGL == RM_TRUE)
	{
	    RMfog *f;
	    
	    f = r->scene_parms->fog;
	    
	    glEnable(GL_FOG);
	    glFogi(GL_FOG_MODE, f->fogMode);

	    if (f->fogMode == GL_LINEAR)
	    {
		glFogf(GL_FOG_START, f->fogStart);
		glFogf(GL_FOG_END, f->fogEnd);
	    }
	    else
		glFogf(GL_FOG_DENSITY, f->fogDensity);
	    
	    glFogfv(GL_FOG_COLOR, (GLfloat *)&(f->fogColor));

	    rState->fog = *(r->scene_parms->fog);
	    rState->fogActive = RM_TRUE;

	    /* temp */
/*	    glHint(GL_FOG_HINT, GL_NICEST); */
	}
    }

    return(push_attrib_enable);
}


/* PRIVATE 
 *
 * private_setClipPlanes() - internal routine used to map from RM scene
 * parameters to OpenGL style clipping planes.
 */
int
private_setClipPlanes (const RMnode *r,
		       int push_attrib_enable,
		       RMstate *rState,
		       RMenum applyGL)
{
    if (r->scene_parms == NULL)
	return 0;
    
    if ((r->scene_parms->cp0) && (rmClipPlaneIsEnabled(r->scene_parms->cp0)))
    {
	GLdouble eq[4];
	
	if (applyGL == RM_TRUE)
	{
	    eq[0] = r->scene_parms->cp0->a;
	    eq[1] = r->scene_parms->cp0->b;
	    eq[2] = r->scene_parms->cp0->c;
	    eq[3] = r->scene_parms->cp0->d;
	    glClipPlane(GL_CLIP_PLANE0, eq);
	    glEnable(GL_CLIP_PLANE0);
	}
	rState->cp0 = r->scene_parms->cp0;
    }

    if ((r->scene_parms->cp1) && (rmClipPlaneIsEnabled(r->scene_parms->cp1)))
    {
        GLdouble eq[4];

	if (applyGL == RM_TRUE)
	{
	    eq[0] = r->scene_parms->cp1->a;
	    eq[1] = r->scene_parms->cp1->b;
	    eq[2] = r->scene_parms->cp1->c;
	    eq[3] = r->scene_parms->cp1->d;
	    glClipPlane(GL_CLIP_PLANE1, eq);
	    glEnable(GL_CLIP_PLANE1);
	}
	rState->cp1 = r->scene_parms->cp1;
    }

    if ((r->scene_parms->cp2) && (rmClipPlaneIsEnabled(r->scene_parms->cp2)))
    {
        GLdouble eq[4];

	if (applyGL == RM_TRUE)
	{
	    eq[0] = r->scene_parms->cp2->a;
	    eq[1] = r->scene_parms->cp2->b;
	    eq[2] = r->scene_parms->cp2->c;
	    eq[3] = r->scene_parms->cp2->d;
	    glClipPlane(GL_CLIP_PLANE2, eq);
	    glEnable(GL_CLIP_PLANE2);
	}
	rState->cp2 = r->scene_parms->cp2;
    }

    if ((r->scene_parms->cp3) && (rmClipPlaneIsEnabled(r->scene_parms->cp3)))
    {
        GLdouble eq[4];

	if (applyGL == RM_TRUE)
	{
	    eq[0] = r->scene_parms->cp3->a;
	    eq[1] = r->scene_parms->cp3->b;
	    eq[2] = r->scene_parms->cp3->c;
	    eq[3] = r->scene_parms->cp3->d;
	    glClipPlane(GL_CLIP_PLANE3, eq);
	    glEnable(GL_CLIP_PLANE3);
	}
	rState->cp3 = r->scene_parms->cp3;
    }

    if ((r->scene_parms->cp4) && (rmClipPlaneIsEnabled(r->scene_parms->cp4)))
    {
        GLdouble eq[4];

	if (applyGL == RM_TRUE)
	{
	    eq[0] = r->scene_parms->cp4->a;
	    eq[1] = r->scene_parms->cp4->b;
	    eq[2] = r->scene_parms->cp4->c;
	    eq[3] = r->scene_parms->cp4->d;
	    glClipPlane(GL_CLIP_PLANE4, eq);
	    glEnable(GL_CLIP_PLANE4);
	}
	rState->cp4 = r->scene_parms->cp4;
    }
    
    if ((r->scene_parms->cp5) && (rmClipPlaneIsEnabled(r->scene_parms->cp5)))
    {
        GLdouble eq[4];

	if (applyGL == RM_TRUE)
	{
	    eq[0] = r->scene_parms->cp5->a;
	    eq[1] = r->scene_parms->cp5->b;
	    eq[2] = r->scene_parms->cp5->c;
	    eq[3] = r->scene_parms->cp5->d;
	    glClipPlane(GL_CLIP_PLANE5, eq);
	    glEnable(GL_CLIP_PLANE5);
	}
	rState->cp5 = r->scene_parms->cp5;
    }
    return(push_attrib_enable);
}


/* PRIVATE 
 *
 * private_setSurfaceProps - internal routine used to set current surface
 * reflectance properties given RMnode material properties.
 */
int
private_setSurfaceProps (const RMnode *r,
			 int push_attrib_enable,
			 RMstate *s,
			 RMenum applyGL)
{    
    /* we assume that r->sprops is defined */
#define RM_WHICHFACE GL_FRONT_AND_BACK
	
    if (r->sprops->ambient_color != NULL)
    {
	if (applyGL == RM_TRUE)
	    glMaterialfv(RM_WHICHFACE, GL_AMBIENT, (GLfloat *)(r->sprops->ambient_color));
	s->ambient = *(r->sprops->ambient_color);
    }

    if (r->sprops->diffuse_color != NULL)
    {
	if (applyGL == RM_TRUE)
	    glMaterialfv(RM_WHICHFACE,GL_DIFFUSE, (GLfloat *)(r->sprops->diffuse_color));
	s->diffuse = *(r->sprops->diffuse_color);
    }
	
    if (r->sprops->specular_color != NULL)
    {
	if (applyGL == RM_TRUE)
	    glMaterialfv(RM_WHICHFACE,GL_SPECULAR, (GLfloat *)(r->sprops->specular_color));
	s->specular = *(r->sprops->specular_color);
    }

    if (r->sprops->unlit_color != NULL)
    {
	/* use the unlit color for the diffuse property iff the diffuse property isn't already defined */

	if (r->sprops->diffuse_color == NULL && applyGL == RM_TRUE)
	{
	    glMaterialfv(RM_WHICHFACE, GL_DIFFUSE, (GLfloat *)(r->sprops->unlit_color));
	    glMaterialfv(RM_WHICHFACE, GL_AMBIENT, (GLfloat *)(r->sprops->unlit_color));
	}

	/* just copy it into the render state struct */
	s->unlit_color = *(r->sprops->unlit_color);
	glColor4fv((GLfloat *)&(s->unlit_color));
    }

    if (r->sprops->specular_exponent != NULL)
    {
	float f;

	rmNodeGetSpecularExponent(r,&f);
	s->specular_exponent = f;

	if (applyGL == RM_TRUE)
	    glMaterialf(RM_WHICHFACE, GL_SHININESS,f);
    }
    return(push_attrib_enable);
}


/* PRIVATE 
 *
 * private_setRenderProps() - an internal routine used to set the current
 * shademodel, establish front-face definitions, polygon fill mode,
 * line width and dashing style, point-size, and current cull mode.
 */
int
private_setRenderProps (const RMnode *r,
		        int push_attrib_enable,
		        RMstate *s,
		        RMenum applyGL,
			RMstateCache *rsc)
{
    if (r->rprops->normalizeNormals)
    {
	if (*(r->rprops->normalizeNormals) == RM_TRUE)
	    glEnable(GL_NORMALIZE);
    }

    if (r->rprops->shademodel)
    {
	if (applyGL == RM_TRUE)
	{
	   switch(*(r->rprops->shademodel))
	       {
	       case RM_SHADER_SMOOTH:
		   glShadeModel(GL_SMOOTH); 
		   break;

	       case RM_SHADER_FLAT:
		   glShadeModel(GL_FLAT);
		   break;

	       case RM_SHADER_NOLIGHT:
		   glShadeModel(GL_FLAT);
		   glDisable(GL_LIGHTING);
		   s->lightingActive = RM_FALSE;
		   if (rsc != NULL)
		       rsc->lightingActive = RM_FALSE;
		   break;

	       default:
		   /* ? */
		   break;
	    }
	}
	s->shademodel = *(r->rprops->shademodel);
    }

    if (r->rprops->front_face)
    {
	if (applyGL == RM_TRUE)
	{
	   switch (*(r->rprops->front_face))
	       {
	       case RM_CCW:
		   glFrontFace(GL_CCW);
		   break;
		 
	       case RM_CW:
		   glFrontFace(GL_CW);
		   break;
		 
	       default:
		   /* ? */
		   break;
	       }
	}
	s->front_face = *(r->rprops->front_face);
    }

    /* both the following must be defined */
    if (r->rprops->poly_mode_face && r->rprops->poly_mode_drawstyle)
    {
	int face, mode;

	switch (*(r->rprops->poly_mode_face))
	    {
	    case RM_FRONT:
	        face = GL_FRONT;
	        break;
	      
	    case RM_BACK:
	        face = GL_BACK;
	        break;
	      
	    case RM_FRONT_AND_BACK:
	        face = GL_FRONT_AND_BACK;
	        break;
	      
	    default:
	        face = GL_FRONT_AND_BACK;
	        break;
	    }
	
	switch (*(r->rprops->poly_mode_drawstyle))
	    {
	    case RM_POINT:
	        mode = GL_POINT;
	        break;
	      
	    case RM_LINE:
	        mode = GL_LINE;
	        break;
	      
	    case RM_FILL:
	    default:
	        mode = GL_FILL;
	        break;
	    }

	if (applyGL == RM_TRUE)
	    glPolygonMode(face,mode);

	s->poly_mode_face = *(r->rprops->poly_mode_face);
	s->poly_mode_drawstyle = *(r->rprops->poly_mode_drawstyle);
    }
	
    if (r->rprops->linewidth)
    {
	int indx;
	extern RMLinewidthEnum rmlinewidths[];
	    
	s->linewidth = *(r->rprops->linewidth);
	indx = private_rmLinewidthToIndex(s->linewidth);

	if (applyGL == RM_TRUE)
	    glLineWidth(rmlinewidths[indx].width);
    }

    if (r->rprops->linestyle)
    {
	int                    indx;
	GLint                  factor;
	GLushort               pattern;
	extern RMLinestyleEnum rmlinestyles[];

	s->linestyle = *(r->rprops->linestyle);

	/* convert from an enumerator to an index */
	indx = private_rmLinestyleToIndex(s->linestyle);
	    
	factor = rmlinestyles[indx].ogl_factor;
	pattern = rmlinestyles[indx].ogl_pattern;

	if (applyGL == RM_TRUE)
	{
	    if (s->linestyle == RM_LINES_SOLID)
		glDisable(GL_LINE_STIPPLE);
	    else
	    {
		glLineStipple(factor, pattern);
		glEnable(GL_LINE_STIPPLE);
	    }
	}
    }

    if (r->rprops->pointsize)
    {
	if (applyGL == RM_TRUE)
	    glPointSize((GLfloat)*(r->rprops->pointsize));
	s->pointsize = *(r->rprops->pointsize);
    }

    if (r->rprops->cull_mode)
    {
	if (applyGL == RM_TRUE)
	{
	   switch (*(r->rprops->cull_mode))
	       {
	       case RM_CULL_FRONT:
		   glEnable(GL_CULL_FACE);
		   glCullFace(GL_FRONT);
		   break;
		 
	       case RM_CULL_BACK:
		   glEnable(GL_CULL_FACE);
		   glCullFace(GL_BACK);
		   break;
		 
	       case RM_CULL_FRONT_AND_BACK:
		   glEnable(GL_CULL_FACE);
		   glCullFace(GL_FRONT_AND_BACK);
		   break;

	       case RM_CULL_NONE:
		   glDisable(GL_CULL_FACE);
		   break;
		 
	       default:
		   /* ? */
		   break;
	       }
	}
	s->cull_mode = *(r->rprops->cull_mode);
    }
    return(push_attrib_enable);
}


/* PRIVATE
 *
 * private_rmPostRender() - internal routine called after frame rendering
 * has occured. if we're here, we assume that the application has attached
 * a post-rendering callback to the pipe, so we'll read the framebuffer
 * into an RMimage object, invoke the application callback with the
 * framebuffer contents as a parameter, then free the temp RMimage upon
 * return from the app callback.
 */
void
private_rmPostRender (int whichbuffer_enum,
		      RMpipe *p)
{
    /* if we're here, assume that p->postrenderfunc != NULL */
    unsigned char *pixelbuf;
    int            w, h;
    RMimage       *img;
    RMenum	   channel;

    /* get the image dims */
    rmPipeGetWindowSize(p, &w, &h);

    /* malloc an rmImage */
    img = rmImageNew(2, w, h, 1, RM_IMAGE_RGB, RM_UNSIGNED_BYTE, RM_COPY_DATA);
    
    pixelbuf = rmImageGetPixelData(img);
	
    /* call the internal routine to read the framebuffer */
    glReadBuffer(whichbuffer_enum);
    private_rmReadBytePixels(pixelbuf, w, h, private_rmImageGetElements(img), GL_RGB, private_rmImageGetBytesPerScanline(img));

    /* call the user function */
    switch (whichbuffer_enum)
        {
        case GL_BACK:
	    channel = RM_ALL_CHANNELS;
	    break;
	  
        case GL_BACK_LEFT:
        case GL_FRONT_LEFT:
	    channel = RM_LEFT_CHANNEL;
	    break;
	  
        case GL_BACK_RIGHT:
        case GL_FRONT_RIGHT:
	    channel = RM_RIGHT_CHANNEL;
	    break;
	  
        default: /* bogus buffer? */
	    channel = RM_ALL_CHANNELS;
	    break;
        }
    (*p->postrenderfunc)(img, channel);

    /* free the local memory */
    rmImageDelete(img);
}


/* PRIVATE 
 *
 * private_rmPostRenderDepthBuffer() - internal routine called after frame
 * rendering has occured. if we're here, we assume that the application
 * has attached a post-rendering callback to the pipe, so we'll read the DEPTH
 * buffer into an RMimage object, invoke the application callback with the
 * framebuffer contents as a parameter, then free the temp RMimage upon
 * return from the app callback.
 */
void
private_rmPostRenderDepthBuffer (GLenum whichbuffer_enum,
				 RMpipe *p)
{
    /* if we're here, assume that p->postrenderfunc != NULL */
    int       w, h;
    float    *pixelbuf; 
    RMimage  *img;
    RMenum    rm_enum;

    /* get the image dims */
    rmPipeGetWindowSize(p, &w, &h);

    /* malloc an rmImage */
    img = rmImageNew(2, w, h, 1, RM_IMAGE_DEPTH, RM_FLOAT, RM_COPY_DATA); 
    
    pixelbuf = rmImageGetPixelData(img);
	
    /* call the internal routine to read the framebuffer */
    glReadBuffer(whichbuffer_enum);

    /* need to determine this programmatically - it probably varies as a function of implementation */
    glPixelTransferf(GL_DEPTH_SCALE, 1.0001F);
    
    private_rmReadFloatPixels(pixelbuf, w, h, private_rmImageGetElements(img), GL_DEPTH_COMPONENT);

    glPixelTransferf(GL_DEPTH_SCALE, 1.0F); 
    
    /* call the user function */
    switch (whichbuffer_enum)
        {
        case GL_BACK_LEFT:
        case GL_FRONT_LEFT:
	    rm_enum = RM_LEFT_CHANNEL;
	    break;
	  
        case GL_BACK_RIGHT:
        case GL_FRONT_RIGHT:
	    rm_enum = RM_RIGHT_CHANNEL;
	    break;
	  
        case GL_BACK:
        default:
	    rm_enum = RM_ALL_CHANNELS;
	    break;
        }
    (*p->postrender_depthbufferfunc)(img, rm_enum);

    /* free the local memory */
    rmImageDelete(img);
}


/* PRIVATE
 *
 * private_setBackgroundDepthValue -  internal routine that clears the
 * depth buffer, activated by the presence of a depth value scene parameter.
 */
int
private_setBackgroundDepthValue (const internals_RMfbClear *fbClear,
				 RMstate *rState,
				 RMenum applyGL)
{
    GLclampd d;

    /* future: update RMstate to indicate background depth value */
    rState = NULL;		/* foil compiler warning */

    /* at the present time, we ignore push_attrib_enable and the render state */
    if (applyGL == RM_TRUE)
    {
	d = *(fbClear->depthValue);

	glClearDepth(d);
	glClear(GL_DEPTH_BUFFER_BIT);
    }
    return(0);			/* return value ignored */
}

/*
 * PRIVATE
 */
int
private_manageTextureState(RMtexture *t,
			   RMstate *rState,
			   RMpipe *p,
			   int applyGL,
			   int textureUnitIndex)
{
    RMcacheKey oldTextureIDKey, oldTextureDataKey;
    RMcacheKey pipeIDKey, pipeDataKey;
    int compListIndx;
    GLuint textureName;
    int haveNewTexture=0, haveNewData=0;

    /*
     * first, compare the texture ID cache key with that in the
     * RMpipe. if they differ, we need to generate a new texture.
     * we won't need to generate a new OpenGL texture except during
     * the first encounter with an RMtexture (render-time download,
     * possible overridden by pre-rendering texture loads - to be
     * implemented in the future) or when the app deletes then replaces
     * an RMtexture scene parameter. the latter case will rarely happen.
     */

    if (applyGL == RM_FALSE)
	return 1;

    /* first check to see if we're using an app-supplied texture ID. if so,
       just activate texturing and return. */
    if (t->appTextureID != NULL)
    {
#if (DEBUG_LEVEL & DEBUG_GLERRORCHECK)
	if (glIsTexture(*(t->appTextureID) == GL_FALSE))
	    printf("app supplied textureID is not a valid OpenGL texture. \n");
	else
	    printf("app supplied textureID is OK \n");
#endif

	if (private_rmTextureGetDims(t) == 2)
	{
	    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, t->wrap_mode);
	    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, t->wrap_mode);
	    
	    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, t->mag_filter_mode);
	    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, t->min_filter_mode);
	
	    glEnable(GL_TEXTURE_2D); 
	    glBindTexture(GL_TEXTURE_2D,*(t->appTextureID));
	    rState->texture_mode = GL_TEXTURE_2D;
	}
	else if (private_rmTextureGetDims(t) == 3)
	{
	    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, t->wrap_mode);
	    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, t->wrap_mode);
	    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, t->wrap_mode);
	    
	    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, t->mag_filter_mode);
	    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, t->min_filter_mode);
	
	    glEnable(GL_TEXTURE_3D); 
	    glBindTexture(GL_TEXTURE_3D, *(t->appTextureID));
	    rState->texture_mode = GL_TEXTURE_3D;
	} 
	else
	{
	    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, t->wrap_mode);
	    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, t->mag_filter_mode);
	    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, t->min_filter_mode);
	
	    glEnable(GL_TEXTURE_1D); 
	    glBindTexture(GL_TEXTURE_1D, *(t->appTextureID));
	    rState->texture_mode = GL_TEXTURE_1D;
	}

	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, t->envMode);
	if (t->blendColor != NULL)
	    glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, (GLfloat *)(t->blendColor));
	return 1;
    }


    oldTextureIDKey = private_rmTextureGetIDCacheKey(t);
    compListIndx = t->compListIndx;

    if (compListIndx >= p->contextCache->numTextureIDCacheKeys)
    {
	int newSize;
	int oldSize = p->contextCache->numTextureIDCacheKeys;
	int numNewPages = private_rmCacheComputeNumberNewPages(oldSize, NUM_ITEMS_PER_PAGE, compListIndx);
	int numOldPages = oldSize/NUM_ITEMS_PER_PAGE;

	newSize = numNewPages * NUM_ITEMS_PER_PAGE;
	
	p->contextCache->textureIDCacheKeys = (RMcacheKey *)realloc(p->contextCache->textureIDCacheKeys, newSize * sizeof(RMcacheKey));

	/* initialize new stuff to -1 */
	memset((void *)(p->contextCache->textureIDCacheKeys + oldSize), 0xFF,
	       (numNewPages-numOldPages)*NUM_ITEMS_PER_PAGE*sizeof(RMcacheKey));
	
	p->contextCache->numTextureIDCacheKeys = newSize;
	
#if (DEBUG_LEVEL & DEBUG_REALLOC_TRACE)
	printf("private_manageTextureState() - reallocating contextCache->textureIDCacheKeys; oldSize = %d, newSize = %d \n", oldSize, newSize);
#endif
    }
       
    pipeIDKey = p->contextCache->textureIDCacheKeys[compListIndx];

    if (oldTextureIDKey != pipeIDKey)
	haveNewTexture = 1;
    
    if (haveNewTexture != 0)
    {
	private_rmOGLTextureDelete(t,p);
	
	glGenTextures(1,&textureName);
	/* dispose of the old texture */

	if (compListIndx >= p->contextCache->numTextureIDs)
	{
	    int newSize;
	    int oldSize = p->contextCache->numTextureIDs;
	    int numNewPages = private_rmCacheComputeNumberNewPages(oldSize, NUM_ITEMS_PER_PAGE, compListIndx);
	    int numOldPages = oldSize/NUM_ITEMS_PER_PAGE;

	    newSize = numNewPages * NUM_ITEMS_PER_PAGE;
	
	    p->contextCache->textureIDs = (GLuint *)realloc(p->contextCache->textureIDs, newSize * sizeof(GLuint));

	    /* initialize new stuff to -1 */
	    memset((void *)(p->contextCache->textureIDs + oldSize), 0xFF,
		   (numNewPages-numOldPages)*NUM_ITEMS_PER_PAGE*sizeof(GLuint));
	    p->contextCache->numTextureIDs = newSize;
	    
#if (DEBUG_LEVEL & DEBUG_REALLOC_TRACE)
	    printf("private_manageTextureState() - reallocating contextCache->textureIDs; oldSize = %d, newSize = %d \n", oldSize, newSize);
#endif
	}
	
	p->contextCache->textureIDs[compListIndx] = textureName;
	p->contextCache->textureIDCacheKeys[compListIndx] = oldTextureIDKey;
    }
    else
	textureName = p->contextCache->textureIDs[compListIndx];

    /*
     * check to see if the texture data needs to be pushed down
     * the gfx pipe. this condition depends upon two things: 1)
     * whether the textureData cache key inside the RMtexture is the
     * same as the textureData cache key inside the RMpipe, and 2)
     * if the "texture is resident."
     */

    if (compListIndx >= p->contextCache->numTextureDataCacheKeys)
    {
	int newSize;
	int oldSize = p->contextCache->numTextureDataCacheKeys;
	int numNewPages = private_rmCacheComputeNumberNewPages(oldSize, NUM_ITEMS_PER_PAGE, compListIndx);
	int numOldPages = oldSize/NUM_ITEMS_PER_PAGE;

	newSize = numNewPages * NUM_ITEMS_PER_PAGE;
	
	p->contextCache->textureDataCacheKeys = (RMcacheKey *)realloc(p->contextCache->textureDataCacheKeys, newSize * sizeof(RMcacheKey));

	/* initialize new stuff to -1 */
	memset((void *)(p->contextCache->textureDataCacheKeys + oldSize),
	       0xFF, (numNewPages-numOldPages)*NUM_ITEMS_PER_PAGE*sizeof(RMcacheKey));
	p->contextCache->numTextureDataCacheKeys = newSize;
	
#if (DEBUG_LEVEL & DEBUG_REALLOC_TRACE)
	printf("private_manageTextureState() - reallocating contextCache->textureDataCacheKeys; oldSize = %d, newSize = %d \n", oldSize, newSize);
#endif
	
    }
    
    oldTextureDataKey = private_rmTextureGetDataCacheKey(t);
    pipeDataKey = p->contextCache->textureDataCacheKeys[compListIndx];

    if (oldTextureDataKey != pipeDataKey)
	haveNewData = 1;

    /* 
     * 11/11/04. if requested, activate multitexturing. Note that values
     * of textureUnitIndex between 0..RM_MAX_MULTITEXTURES-1 indicate 
     * multitexturing, whereas a value of RM_MAX_MULTITEXTURES indicates
     * that we want "plain old texturing".
     */
    if (textureUnitIndex < RM_MAX_MULTITEXTURES)
    {
	if (RM_ASSERT(p->caps, "private_manageTextureState error - the input RMpipe does not have a capabilities structure. Please file a bug report. Texturing will proceed using the non-multitextured code path.") == RM_WHACKED)
	    textureUnitIndex = textureUnitIndex; /* compiler fodder */

	else if (p->caps->haveMultiTexturing != RM_TRUE)

	    rmWarning("private_manageTextureState warning - you have requested that an RMtexture be assigned to a multitexturing unit. However, it does not appear that the underlying OpenGL platform supports OpenGL. I will proceed, but the texture will be assigned to the regular OpenGL texturing unit. Rendering errors are likely to result. ");
	else
	    (*(p->caps->activeTextureARB))(GL_TEXTURE0_ARB + textureUnitIndex);
    }

    /*
     * enable texturing and bind the texture object.
     * update the RMstate to reflect that texturing is active.
     */

    if (private_rmTextureGetDims(t) == 2)
    {
	glEnable(GL_TEXTURE_2D); 
	glBindTexture(GL_TEXTURE_2D,textureName);
	rState->texture_mode = GL_TEXTURE_2D;
    }
    else if (private_rmTextureGetDims(t) == 3)
    {
	glEnable(GL_TEXTURE_3D); 
	glBindTexture(GL_TEXTURE_3D, textureName);
	rState->texture_mode = GL_TEXTURE_3D;
    } 
    else
    {
	glEnable(GL_TEXTURE_1D); 
	glBindTexture(GL_TEXTURE_1D, textureName);
	rState->texture_mode = GL_TEXTURE_1D;
    }

    /*
     * set the texture environment attributes:
     * 1. the texture mode (GL_MODULATE, GL_DECAL, GL_BLEND, or GL_REPLACE)
     * 2. texture blend color, if present
     */
/*    fprintf(stderr,"setting GL_TEXTURE_ENV_MODE to %0x \n", t->envMode); */
    
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, t->envMode);
    if (t->blendColor != NULL)
	glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, (GLfloat *)(t->blendColor));

    if (haveNewData != 0)
    {
	
#if (DEBUG_LEVEL & DEBUG_TRACE)
	fprintf(stderr," downloading texture to OpenGL \n");
#endif
	p->contextCache->textureDataCacheKeys[compListIndx] = oldTextureDataKey;
	/* stuff the new texture data down to OpenGL */
	private_rmTextureToOGL(p, t, haveNewTexture);
    }
 
    /* need to add residency check */
    
    rState->texture = t;
    
    return 1;
}


/* PRIVATE
 *
 * private_updateSceneParms - an internal routine used to activate all
 * scene parameters at an RMnode.
 */
int
private_updateSceneParms (const RMnode *r,
			  RMstate *rState,
			  RMenum applyGL,
			  int push_attrib_enable,
			  RMpipe *renderPipe,
			  RMstateCache *rsc)
{
    /*
     * this routine updates the scene parameters in rState to
     * reflect the changes requested by the input RMnode "r".
     *
     * cameras and viewports are processed prior to this routine.
     *
     * the rState parm is always updated, although not all scene
     * parameters cause a change to rState.
     *
     * when "applyGL" is set to RM_TRUE, the appropriate OpenGL
     * calls are made that have the scene parameters take effect.
     *
     * this routine returns a 1 if any changes were made to rState,
     * otherwise a 0 is returned.
     *
     * the routines preCamFunc and perObjFunc are hooks used when
     * rendermode is GL_SELECT to drop bread crumbs so that cameras
     * generate feedback tokens, otherwise they are not used. 
     */
    
    /*
     * do background fill operations after the viewport is set.
     * note that we can't do BOTH background color AND a background image.
     *
     * don't draw tiles when we're in select mode.  although the spec
     * seems to indicate that no select events are generated for
     * drawpixels calls, i've had problems in mesa with them being
     * generated. (don't draw tiles in feedback mode. this is handled
     * as a modeling step when the PS file is created)
     */

    {
	GLuint mask;
	mask = private_rmNodeGetAttribMask(r);
	if ((mask != 0) && (applyGL == RM_TRUE))
	{
	    private_rmGLPushAttrib(renderPipe, r, mask);
	    push_attrib_enable = 1;
	}
    }
    
    if (r->scene_parms && r->scene_parms->textProps != NULL)
    {
	/*
	 * text props don't do anything to OpenGL.
	 * can we copy the pointer from the RMnode over rather
	 * than creating a new one?
	 * let's try doing that: 7/8/99
	 */
	rState->textProps = r->scene_parms->textProps;

	/*
	 * a change of text props is otherwise significant (but not to
	 * OpenGL). if we've not already done a state push, we'll
	 * push something small on the stack.
	 *
	 * the issue is that a change in RMstate is bound to a change
	 * in OpenGL attribute stack depth
	 */
	if (push_attrib_enable != 1)
	{
	    if (applyGL == RM_TRUE)
	    {
		private_rmGLPushAttrib(renderPipe, r, GL_ACCUM_BUFFER_BIT); /* something small */
		push_attrib_enable = 1;
	    }
	}
    }

    /* do the lights */
    if (r->scene_parms != NULL)
	push_attrib_enable |= process_scene_lights(r, push_attrib_enable, rState, applyGL, rsc);

    /* update surface properties if present */
    if (r->sprops != NULL)
	push_attrib_enable |= private_setSurfaceProps(r, push_attrib_enable, rState, applyGL);

    /* update rendering properties if present */
    if (r->rprops != NULL)
	push_attrib_enable |= private_setRenderProps(r, push_attrib_enable, rState, applyGL, rsc);

    /* do clipping planes if present */
    if (r->scene_parms)
	push_attrib_enable |= private_setClipPlanes(r, push_attrib_enable, rState, applyGL);

    /* do fog if present */
    if (r->scene_parms)
	push_attrib_enable |= private_setFog(r, push_attrib_enable, rState, applyGL);
    
    /* do texture if present */
    if ((r->scene_parms != NULL) && (r->scene_parms->haveAnyTextures == RM_TRUE))
    {
	int i;

	/* 11/11/04 - todo - need to figure out a way to accelerate this
	   stage of processing.  */
	for (i=0; i <= RM_MAX_MULTITEXTURES; i++)
	{
	    if ((r->scene_parms->textures[i] != NULL) &&
		(renderPipe->caps->haveMultiTexturing == RM_TRUE))
	    {
		/* only download multitextures if OpenGL supports multitexturing */
		push_attrib_enable |= private_manageTextureState(r->scene_parms->textures[i], rState, renderPipe, applyGL, i);
	    }
	    else if ((r->scene_parms->textures[i] != NULL) &&
		     (i == RM_MAX_MULTITEXTURES))
	    {
		/* the "normal", single-texture/non-multitexture case */
		push_attrib_enable |= private_manageTextureState(r->scene_parms->textures[i], rState, renderPipe, applyGL, i);
	    }
	}
    }

    /* set the "default" color */
#if 0
    if (applyGL == RM_TRUE)
	glColor4fv((GLfloat *)&(rState->unlit_color));
#endif
    
    if (push_attrib_enable)
	rState->attrib_stack_depth += 1;

    return(push_attrib_enable);
}

static void
private_rmPipeMultiStageSerial (RMnode *rootedTree,
				RMpipe *drawToPipe)
{
    if (rmPipeGetInitMatrixStackMode(drawToPipe) == RM_FALSE)
    {
	RMmatrix initModel, initProjection, initTexture;
	
        glGetFloatv(GL_MODELVIEW_MATRIX,&(initModel.m[0][0]));
	glGetFloatv(GL_PROJECTION_MATRIX,&(initProjection.m[0][0]));
	glGetFloatv(GL_TEXTURE_MATRIX,&(initTexture.m[0][0]));
	private_rmView(drawToPipe, rootedTree, drawToPipe->frameNumber, &initModel, NULL, &initProjection, &initTexture); 
    }
    else
	private_rmView(drawToPipe, rootedTree, drawToPipe->frameNumber, NULL, NULL, NULL, NULL); 
    
    
    private_rmSetBackBuffer(drawToPipe); 
    private_rmRender(drawToPipe, drawToPipe->frameNumber);
    
    private_postRenderBarrierFunc(drawToPipe);
    
    if (drawToPipe->timeSyncFunc != NULL)
	(*(drawToPipe->timeSyncFunc))(drawToPipe);
    
    private_postRenderSwapBuffersFunc(drawToPipe);
    
    private_postRenderImageFuncs(drawToPipe, GL_FRONT);
}

static void
private_rmPipeMultiStageViewParallel(RMnode *rootedTree,
				     RMpipe *drawToPipe)
{
    RMmultiStageThreadControl *m;
    RMthreadArgs *ta;
    
    if (drawToPipe->mtControl == NULL) /* init multiple rendering threads */
    {
	int i;
	RMenum stat;
	
	m = (RMmultiStageThreadControl *)malloc(sizeof(RMmultiStageThreadControl));
	drawToPipe->mtControl = (void *)m;
	
	memset(m,0,sizeof(RMmultiStageThreadControl));
	
	m->nThreads = 1;	/* view ONLY */
	
	m->threadIDs = (RMthread *)malloc(sizeof(RMthread)*(m->nThreads));
	m->args = (RMthreadArgs *)malloc(sizeof(RMthreadArgs)*(m->nThreads));
	
	/* for each thread, set up barriers and launch threads */
	for (i=0;i<m->nThreads;i++)
	{
	    ta = &(m->args[i]);
	    
	    ta->p = drawToPipe;
	    ta->n = rootedTree;

	    ta->initModel = rmMatrixNew();
	    ta->initView = NULL;
	    ta->initProjection = rmMatrixNew();
	    ta->initTexture = rmMatrixNew();

	    ta->one = (barrier_t *)malloc(sizeof(barrier_t));
	    ta->two = (barrier_t *)malloc(sizeof(barrier_t));
	    barrier_init (ta->one, 2);
	    barrier_init (ta->two, 2);
	}

	/* create each thread. these func will block trying to get
	 past mutex A, which is owned by the invoking thread */

	stat = rmThreadCreate(m->threadIDs+0, private_rmViewThreadFunc,
			      (void *)&(m->args[0]));
#if 0
				/* no usleep on win32 */
	usleep(10);
#endif
    }

    m = (RMmultiStageThreadControl *)(drawToPipe->mtControl);
    ta = m->args;
    
    /* dispatch view for current frame */
    ta[0].p = drawToPipe;
    ta[0].n = rootedTree;
    ta[0].commandOpcode = THREAD_WORK;
    ta[0].frameNumber = drawToPipe->frameNumber;

    glGetFloatv(GL_MODELVIEW_MATRIX,&(ta[0].initModel->m[0][0]));
    glGetFloatv(GL_PROJECTION_MATRIX,&(ta[0].initProjection->m[0][0]));
    glGetFloatv(GL_TEXTURE_MATRIX,&(ta[0].initTexture->m[0][0]));
    
    barrier_wait(ta[0].one);	/* make sure view thread starts */

    /* dispatch render for last frame */
    if (drawToPipe->frameNumber > 0)
    {
	private_rmRender (drawToPipe, drawToPipe->frameNumber-1);
	
	private_postRenderBarrierFunc(drawToPipe);
	
	if (drawToPipe->timeSyncFunc != NULL)
	    (*(drawToPipe->timeSyncFunc))(drawToPipe);
    
	private_postRenderSwapBuffersFunc(drawToPipe);

	private_postRenderImageFuncs(drawToPipe, GL_FRONT);
    }

    barrier_wait(ta[0].two);
    
    /* thread finished command xeq */
#if (DEBUG_LEVEL & DEBUG_TRACE)
    fprintf(stderr," threads finished \n");
    fflush(stderr);
#endif

}

static void
private_rmPipeMultiStageParallel (RMnode *rootedTree,
				  RMpipe *drawToPipe)
{
    RMmultiStageThreadControl *m;
    RMthreadArgs *ta;
    
    if (drawToPipe->mtControl == NULL) /* init multiple rendering threads */
    {
	RMenum stat;
	int i;
	
	m = (RMmultiStageThreadControl *)malloc(sizeof(RMmultiStageThreadControl));
	drawToPipe->mtControl = (void *)m;
	
	memset(m,0,sizeof(RMmultiStageThreadControl));
	
	m->nThreads = 2;	/* view, render */
	
	m->threadIDs = (RMthread *)malloc(sizeof(RMthread)*(m->nThreads));
	m->args = (RMthreadArgs *)malloc(sizeof(RMthreadArgs)*(m->nThreads));
	
	/* for each thread, set up barriers and launch threads */
	for (i=0;i<m->nThreads;i++)
	{
	    ta = &(m->args[i]);
	    
	    ta->p = drawToPipe;
	    ta->n = rootedTree;

	    /* no OpenGL matrix stack model loading for full parallel multistage */
	    ta->initModel = ta->initView = ta->initProjection = ta->initTexture = NULL;

	    ta->one = (barrier_t *)malloc(sizeof(barrier_t));
	    ta->two = (barrier_t *)malloc(sizeof(barrier_t));
	    barrier_init (ta->one, 2);
	    barrier_init (ta->two, 2);
	}

#ifdef RM_X
	/* release the current context prior to launching the
	 detached rendering thread. */

	if ( (glXMakeCurrent(rmxPipeGetDisplay(ta->p), None, NULL)) == False)
	    rmError("Error deassigning OpenGL context prior to creating worker threads. ");

#endif

#ifdef RM_WIN
	stat = wglMakeCurrent(ta->p->hdc,NULL);
#endif
	
	/* create each thread. these func will block trying to get
	 past mutex A, which is owned by the invoking thread */

	stat = rmThreadCreate(m->threadIDs+0, private_rmViewThreadFunc,
			      (void *)&(m->args[0]));

	stat = rmThreadCreate(m->threadIDs+1, private_rmRenderThreadFunc,
			      (void *)&(m->args[1]));
#if 0
				/* no usleep on win32 */
	usleep(10);
#endif
    }

    m = (RMmultiStageThreadControl *)(drawToPipe->mtControl);
    ta = m->args;
    
    /* set up args */
    
    /* dispatch render for last frame */
    ta[1].p = drawToPipe;
    ta[1].n = rootedTree;
    ta[1].commandOpcode = THREAD_WORK;
    ta[1].frameNumber = drawToPipe->frameNumber-1;
    
    barrier_wait(ta[1].one);

    /* now dispatch view for current frame */
    ta[0].p = drawToPipe;
    ta[0].n = rootedTree;
    ta[0].commandOpcode = THREAD_WORK;
    ta[0].frameNumber = drawToPipe->frameNumber;

    barrier_wait(ta[0].one);

    barrier_wait(ta[0].two);
    barrier_wait(ta[1].two);

    /* thread finished command xeq */
#if (DEBUG_LEVEL & DEBUG_TRACE)
    fprintf(stderr," threads finished \n");
    fflush(stderr);
#endif
}

void
private_fbClear(internals_RMfbClear *fbClear,
		RMstate *rState,
		RMenum applyGL,
		RMenum fbClearEnable)
{
    /* in this routine, assume that fbClear != NULL */
    if ((fbClear->bgImageTile) && (fbClearEnable)
	&& (rState->rendermode != GL_SELECT) && (rState->rendermode != GL_FEEDBACK))
	private_setBackgroundTile(fbClear, rState, RM_FALSE, applyGL);
    else if ((fbClear->bgColor) && (fbClearEnable))
	private_setBackgroundColor(fbClear, rState, RM_FALSE, applyGL);

    /* check depth value or image parms */
    if ((fbClear->depthValue != NULL) && (fbClearEnable))
	private_setBackgroundDepthValue(fbClear, rState, applyGL);
    
    if ((fbClear->depthImage != NULL) && (fbClearEnable))
	private_setBackgroundDepthImage(fbClear, rState, RM_FALSE, applyGL);
}

/* EOF */
