/* The documentation in here is platform-specific versions of function-
   -descriptions for functions in bapipew.c */

/*
 * Manipulating world object custom pipelines (platform-specific)
 *
 * Copyright (c) Criterion Software Limited
 */

/**
 * \ingroup rpworldp2sky2
 * \page rpworldp2sky2overview PS2 RpWorld Overview
 *
 * The default RpAtomic, RpWorldSector and RpMaterial pipelines provided for
 * PlayStation2 are implemented using the PS2All, PS2All and PS2AllMat nodes
 * respectively.  Each pipeline contains just a single node on the EE core
 * and dispatches to microcode running on VU1.  To support customization
 * of these pipelines, the PS2All and PS2AllMat nodes have many API functions
 * and expose several overloadable callback functions.  An application can
 * use these callbacks to arrange for application code to be called during
 * the pipeline execution. (This technique is therefore functionally equivalent
 * to a series of nodes.) For details, refer to the user guide chapter
 * 'PS2All Overview' and the API reference documentation for
 * \ref RxNodeDefinitionGetPS2All and \ref RxNodeDefinitionGetPS2AllMat.
 *  The PS2Manager, PS2ObjAllInOne, PS2MatInstance and PS2MatBridge nodes
 * are still available for application use, but RenderWare no longer uses
 * these internally and they will be removed in a future revision of the
 * library.
 *
*/

#include <rwcore.h>

#include "bapipew.h"

#include "nodeAtomicInstance.h"
#include "nodeWorldSectorInstance.h"
#include "nodeMaterialScatter.h"
#include "nodeAtomicEnumerateLights.h"
#include "nodeWorldSectorEnumerateLights.h"
#include "nodePreLight.h"
#include "nodeLight.h"
#include "nodePostLight.h"

#include "matputil.h"
#include "p2stdclsw.h"
#include "nodeps2manager.h"
#include "nodeps2objallinone.h"
#include "nodeps2matinstance.h"
#include "nodeps2matbridge.h"

#include "nodeps2all.h"
#include "ps2allatomic.h"
#include "ps2allsector.h"
#include "ps2allim3d.h"

#include "wrldpipe.h"


#if (!defined(DOXYGEN))
static const char rcsid[] __RWUNUSED__ =
    "@@@@(#)$Id: wrldpipe.c,v 1.129 2001/09/27 19:06:50 iestynb Exp $";
#endif /* (!defined(DOXYGEN)) */


/****************************************************************************
 globals across program
 */

/* open architecture stuff / SDM 00-02-22 */
#define _PS2ATTRIBUTESET RxPS2AttributeSet

static RwChar _PS2DMAChain_csl[] = RWSTRING("PS2DMAChain.csl");
static RwChar _PS2LightsBlock_csl[] = RWSTRING("PS2LightsBlock.csl");
RwChar RxPS2AttributeSet[] = RWSTRING("PS2");
static RwChar _PS2DMASessionRecord_csl[] = RWSTRING("PS2DMASessionRecord.csl");
static RwChar _PS2Mesh_csl[] = RWSTRING("PS2Mesh.csl");
static RwChar _PS2xyz_csl[] = RWSTRING("PS2xyz.csl");
static RwChar _PS2uv_csl[] = RWSTRING("PS2uv.csl");
static RwChar _PS2uv2_csl[] = RWSTRING("PS2uv2.csl");
static RwChar _PS2rgba_csl[] = RWSTRING("PS2rgba.csl");
static RwChar _PS2normal_csl[] = RWSTRING("PS2normal.csl");
static RwChar _PS2user1_csl[] = RWSTRING("PS2user1.csl");
static RwChar _PS2user2_csl[] = RWSTRING("PS2user2.csl");
static RwChar _PS2user3_csl[] = RWSTRING("PS2user3.csl");
static RwChar _PS2user4_csl[] = RWSTRING("PS2user4.csl");

RxClusterDefinition RxClPS2DMAChain =
{
    _PS2DMAChain_csl,
    _DEFAULTSTRIDE,
    _DEFAULTATTRIBUTES,
    _ATTRIBUTESET
};

RxClusterDefinition RxClPS2LightsBlock =
{
    _PS2LightsBlock_csl,
    _DEFAULTSTRIDE,
    _DEFAULTATTRIBUTES,
    _ATTRIBUTESET
};

RxClusterDefinition RxClPS2DMASessionRecord =
{
    _PS2DMASessionRecord_csl,
    _DEFAULTSTRIDE,
    _DEFAULTATTRIBUTES,
    _PS2ATTRIBUTESET
};

RxClusterDefinition RxClPS2Mesh =
{
    _PS2Mesh_csl,
    _DEFAULTSTRIDE,
    _DEFAULTATTRIBUTES,
    _PS2ATTRIBUTESET
};

RxClusterDefinition RxClPS2xyz =
{
    _PS2xyz_csl,
    _DEFAULTSTRIDE,
        CL_ATTRIB_REQUIRED |
     /* CL_ATTRIB_READ | 
      * CL_ATTRIB_WRITE | 
      * CL_ATTRIB_DONT_FILL | 
      * CL_ATTRIB_PLACEHOLDER | */
        CL_ATTRIB_OPAQUE |
     /* CL_ATTRIB_STATIC | 
      * CL_S32 | 
      * CL_V2_32 | 
      * CL_V2_16 | */
        CL_V3_32 |
     /* CL_V4_32 | 
      * CL_V4_16 | 
      * CL_V4_8  | 
      * CL_USN   | */
        0,
    _PS2ATTRIBUTESET
};

RxClusterDefinition RxClPS2uv =
{
    _PS2uv_csl,
    _DEFAULTSTRIDE,
        CL_ATTRIB_REQUIRED |
     /* CL_ATTRIB_READ | 
      * CL_ATTRIB_WRITE | 
      * CL_ATTRIB_DONT_FILL | 
      * CL_ATTRIB_PLACEHOLDER | */
        CL_ATTRIB_OPAQUE |
     /* CL_ATTRIB_STATIC | 
      * CL_S32 | */
        CL_V2_32 |
     /* CL_V2_16 | 
      * CL_V3_32 | 
      * CL_V4_32 | 
      * CL_V4_16 | 
      * CL_V4_8  | 
      * CL_USN   | */
        0,
    _PS2ATTRIBUTESET
};

RxClusterDefinition RxClPS2uv2 =
{
    _PS2uv2_csl,
    _DEFAULTSTRIDE,
        CL_ATTRIB_REQUIRED |
     /* CL_ATTRIB_READ | 
      * CL_ATTRIB_WRITE | 
      * CL_ATTRIB_DONT_FILL | 
      * CL_ATTRIB_PLACEHOLDER | */
        CL_ATTRIB_OPAQUE |
     /* CL_ATTRIB_STATIC | 
      * CL_S32 |
      * CL_V2_32 |
      * CL_V2_16 | 
      * CL_V3_32 | */
        CL_V4_32 |
     /* CL_V4_16 | 
      * CL_V4_8  | 
      * CL_USN   | */
        0,
    _PS2ATTRIBUTESET
};

RxClusterDefinition RxClPS2rgba =
{
    _PS2rgba_csl,
    _DEFAULTSTRIDE,
        CL_ATTRIB_REQUIRED |
     /* CL_ATTRIB_READ | 
      * CL_ATTRIB_WRITE | 
      * CL_ATTRIB_DONT_FILL | 
      * CL_ATTRIB_PLACEHOLDER | */
        CL_ATTRIB_OPAQUE |
     /* CL_ATTRIB_STATIC | 
      * CL_S32 | 
      * CL_V2_32 | 
      * CL_V2_16 | 
      * CL_V3_32 | 
      * CL_V4_32 | 
      * CL_V4_16 | */
        CL_V4_8  |
        CL_USN   |
        0,
    _PS2ATTRIBUTESET
};

RxClusterDefinition RxClPS2normal =
{
    _PS2normal_csl,
    _DEFAULTSTRIDE,
        CL_ATTRIB_REQUIRED |
     /* CL_ATTRIB_READ | 
      * CL_ATTRIB_WRITE | 
      * CL_ATTRIB_DONT_FILL | 
      * CL_ATTRIB_PLACEHOLDER | */
        CL_ATTRIB_OPAQUE |
     /* CL_ATTRIB_STATIC | 
      * CL_S32 | 
      * CL_V2_32 | 
      * CL_V2_16 | 
      * CL_V3_32 | 
      * CL_V4_32 | 
      * CL_V4_16 | */
        CL_V4_8  |
     /* CL_USN   | */
        0,
    _PS2ATTRIBUTESET
};

RxClusterDefinition RxClPS2user1 =
{
    _PS2user1_csl,
    _DEFAULTSTRIDE,
        CL_ATTRIB_REQUIRED |
/* This should be visible from nodes in the pipe since
   it needs to be filled by *someone*! */
        CL_ATTRIB_READ | 
        CL_ATTRIB_WRITE | 
     /* CL_ATTRIB_DONT_FILL | 
      * CL_ATTRIB_PLACEHOLDER | 
      * CL_ATTRIB_OPAQUE | 
      * CL_ATTRIB_STATIC | 
      * CL_S32 | 
      * CL_V2_32 | 
      * CL_V2_16 | 
      * CL_V3_32 | */
        CL_V4_32 |
     /* CL_V4_16 | 
      * CL_V4_8  | 
      * CL_USN   | */
        0,
    _PS2ATTRIBUTESET
};

RxClusterDefinition RxClPS2user2 =
{
    _PS2user2_csl,
    _DEFAULTSTRIDE,
        CL_ATTRIB_REQUIRED |
/* This should be visible from nodes in the pipe since
   it needs to be filled by *someone*! */
        CL_ATTRIB_READ | 
        CL_ATTRIB_WRITE | 
     /* CL_ATTRIB_DONT_FILL | 
      * CL_ATTRIB_PLACEHOLDER | 
      * CL_ATTRIB_OPAQUE | 
      * CL_ATTRIB_STATIC | 
      * CL_S32 | 
      * CL_V2_32 | 
      * CL_V2_16 | 
      * CL_V3_32 | */
        CL_V4_32 |
     /* CL_V4_16 | 
      * CL_V4_8  | 
      * CL_USN   | */
        0,
    _PS2ATTRIBUTESET
};

RxClusterDefinition RxClPS2user3 =
{
    _PS2user3_csl,
    _DEFAULTSTRIDE,
        CL_ATTRIB_REQUIRED |
/* This should be visible from nodes in the pipe since
   it needs to be filled by *someone*! */
        CL_ATTRIB_READ | 
        CL_ATTRIB_WRITE | 
     /* CL_ATTRIB_DONT_FILL | 
      * CL_ATTRIB_PLACEHOLDER | 
      * CL_ATTRIB_OPAQUE | 
      * CL_ATTRIB_STATIC | 
      * CL_S32 | 
      * CL_V2_32 | 
      * CL_V2_16 | 
      * CL_V3_32 | */
        CL_V4_32 |
     /* CL_V4_16 | 
      * CL_V4_8  | 
      * CL_USN   | */
        0,
    _PS2ATTRIBUTESET
};

RxClusterDefinition RxClPS2user4 =
{
    _PS2user4_csl,
    _DEFAULTSTRIDE,
        CL_ATTRIB_REQUIRED |
/* This should be visible from nodes in the pipe since
   it needs to be filled by *someone*! */
        CL_ATTRIB_READ | 
        CL_ATTRIB_WRITE | 
     /* CL_ATTRIB_DONT_FILL | 
      * CL_ATTRIB_PLACEHOLDER | 
      * CL_ATTRIB_OPAQUE | 
      * CL_ATTRIB_STATIC | 
      * CL_S32 | 
      * CL_V2_32 | 
      * CL_V2_16 | 
      * CL_V3_32 | */
        CL_V4_32 |
     /* CL_V4_16 | 
      * CL_V4_8  | 
      * CL_USN   | */
        0,
    _PS2ATTRIBUTESET
};

/****************************************************************************
 local (static) globals
 */

RwInt32 rwPip2GeometryOffset, rwPip2AtomicOffset, rwPip2WorldSectorOffset;



/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   Functions

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

/****************************************************************************
 _pluginObjectCreate()
 */

static void        *
_pluginObjectCreate(void *object,
                    RwInt32 offsetInObject,
                    RwInt32 __RWUNUSED__ sizeInObject)
{
    RWFUNCTION(RWSTRING("_pluginObjectCreate"));

    *(RwMeshCache * *) (((RwUInt8 *) object) + offsetInObject) = (RwMeshCache *)NULL;

    RWRETURN(object);
}

/****************************************************************************
 _pluginObjectDestroy()
 */

static void        *
_pluginObjectDestroy(void *object,
                     RwInt32 offsetInObject,
                     RwInt32 __RWUNUSED__ sizeInObject)
{
    RWFUNCTION(RWSTRING("_pluginObjectDestroy"));

    {
        RwMeshCache * p =
            *(RwMeshCache * *) (((RwUInt8 *) object) + offsetInObject);

        if (p != NULL)
        {
            /* Destroy the resource entries within and the array of refs */
            rpObjectDestroyMeshCache(p);
        }
    }

    RWRETURN(object);
}

/****************************************************************************
 _pluginGeometryCopy()
 */

static void *
_pluginGeometryCopy(void *dstObject,
                    const void * __RWUNUSED__ srcObject,
                    RwInt32 offsetInObject,
                    RwInt32 __RWUNUSED__ sizeInObject)
{
    const RpGeometry *srcGeom __RWUNUSEDRELEASE__=
        (const RpGeometry *)srcObject;
    RpGeometry *dstGeom = (RpGeometry *)dstObject;

    RWFUNCTION(RWSTRING("_pluginGeometryCopy"));

    if (NULL != dstGeom->mesh)
    {
        RWASSERT(NULL != srcGeom);
        RWASSERT(NULL != srcGeom->mesh);
        RWASSERT(dstGeom->mesh->numMeshes == srcGeom->mesh->numMeshes);

        if (NULL == rpGeometryGetMeshCache(dstGeom, dstGeom->mesh->numMeshes))
        {
            /* Argh, failed to created destination geometry mesh cache! */
            RWRETURN(NULL);
        }
    }
    else
    {
        *(RwMeshCache * *)(((RwUInt8 *)dstGeom) + offsetInObject) = (RwMeshCache *)NULL;
    }

    RWRETURN(dstObject);
}

/****************************************************************************
 _pluginAtomicCopy()
 */

static void        *
_pluginAtomicCopy(void *dstObject,
                  const void * __RWUNUSED__ srcObject,
                  RwInt32 offsetInObject,
                  RwInt32 __RWUNUSED__ sizeInObject)
{
    const RpAtomic *src = (const RpAtomic *)srcObject;
    RpAtomic *dst = (RpAtomic *)dstObject;
    RpGeometry *srcGeom, *dstGeom;

    RWFUNCTION(RWSTRING("_pluginAtomicCopy"));

    dstGeom = RpAtomicGetGeometry(dst);
    RWASSERT(dstGeom != NULL);

    if (NULL != dstGeom->mesh)
    {
        srcGeom = RpAtomicGetGeometry(src);
        RWASSERT(NULL != srcGeom);
        RWASSERT(NULL != srcGeom->mesh);
        RWASSERT(dstGeom->mesh->numMeshes == srcGeom->mesh->numMeshes);

        if (NULL == rpAtomicGetMeshCache(dst, dstGeom->mesh->numMeshes))
        {
            /* Argh, failed to created destination atomic mesh cache! */
            RWRETURN(NULL);
        }
    }
    else
    {
        *(RwMeshCache * *) (((RwUInt8 *) dst) + offsetInObject) = (RwMeshCache *)NULL;
    }

    RWRETURN(dstObject);
}

/****************************************************************************
 _pluginSectorCopy()
 */

static void        *
_pluginSectorCopy(void *dstObject,
                  const void *srcObject,
                  RwInt32 offsetInObject,
                  RwInt32 __RWUNUSED__ sizeInObject)
{
    const RpWorldSector *src  __RWUNUSEDRELEASE__  =
        (const RpWorldSector *)srcObject;
    RpWorldSector *dst = (RpWorldSector *)dstObject;

    RWFUNCTION(RWSTRING("_pluginSectorCopy"));

    /* WTF? We're copying a sector?? Alriiight... */

    if (NULL != dst->mesh)
    {
        RWASSERT(NULL != src->mesh);
        RWASSERT(dst->mesh->numMeshes == src->mesh->numMeshes);

        if (NULL == rpWorldSectorGetMeshCache(dst, dst->mesh->numMeshes))
        {
            /* Argh, failed to created destination sector mesh cache! */
            RWRETURN(NULL);
        }
    }
    else
    {
        *(RwMeshCache * *) (((RwUInt8 *) dst) + offsetInObject) = (RwMeshCache *)NULL;
    }

    RWRETURN(dstObject);
}

/* Copied from baworobj.c, with modifications to add mesh cache *************/

/****************************************************************************
 _initAtomicMeshCache

 Initializes an atomic's meshcache just after it's been read from a stream

 On entry   : Object (atomic)
            : Plugin data offset (not used)
            : Plugin data size (not used)
 On exit    : Size of mesh when serialised (in bytes); zero
 */

static              RwBool
_initAtomicMeshCache(void *object,
                    RwInt32 __RWUNUSED__ offsetInObject,
                    RwInt32 __RWUNUSED__ sizeInObject)
{
    RpAtomic *atomic = (RpAtomic *)object;
    RpGeometry *geom;

    RWFUNCTION(RWSTRING("_initAtomicMeshCache"));

    RWASSERT(NULL != atomic);
    geom = RpAtomicGetGeometry(atomic);
    RWASSERT(NULL != geom);

    if (NULL != geom->mesh)
    {
        if (RpGeometryGetNumMorphTargets(geom) == 1)
        {
            if (NULL == rpGeometryGetMeshCache(geom, geom->mesh->numMeshes))
            {
                RWRETURN(FALSE);
            }
        }
        else
        {
            if (NULL == rpAtomicGetMeshCache(atomic, geom->mesh->numMeshes))
            {
                RWRETURN(FALSE);
            }
        }
    }

    RWRETURN(TRUE);
}

/****************************************************************************
 _initSectorMeshCache

 Initializes a sector's meshcache just after it's been read from a stream

 On entry   : Object (sector)
            : Plugin data offset (not used)
            : Plugin data size (not used)
 On exit    : Size of mesh when serialised (in bytes); zero
 */

static              RwBool
_initSectorMeshCache(void *object,
                    RwInt32 __RWUNUSED__ offsetInObject,
                    RwInt32 __RWUNUSED__ sizeInObject)
{
    RpWorldSector *sector = (RpWorldSector *)object;

    RWFUNCTION(RWSTRING("_initSectorMeshCache"));

    if (NULL != sector->mesh)
    {
        if (NULL == rpWorldSectorGetMeshCache(sector, sector->mesh->numMeshes))
        {
            /* Argh, failed to created destination sector mesh cache! */
            RWRETURN(FALSE);
        }
    }

    RWRETURN(TRUE);
}

/****************************************************************************
 _rxWorldDevicePluginAttach()
 */

RwBool
_rxWorldDevicePluginAttach(void)
{
    RwBool              result = FALSE; /* fail, unless explicitly set TRUE */
    RwInt32             status = 0;

    RWFUNCTION(RWSTRING("_rxWorldDevicePluginAttach"));

    /* device-specific set-up... */

    rwPip2GeometryOffset = RpGeometryRegisterPlugin(sizeof(RwMeshCache *),
                                                    rwID_RXWORLDDEVICEMODULE,
                                                    _pluginObjectCreate,
                                                    _pluginObjectDestroy,
                                                    _pluginGeometryCopy);

    rwPip2AtomicOffset = RpAtomicRegisterPlugin(sizeof(RwMeshCache *),
                                                rwID_RXWORLDDEVICEMODULE,
                                                _pluginObjectCreate,
                                                _pluginObjectDestroy,
                                                _pluginAtomicCopy);

    rwPip2WorldSectorOffset =
        RpWorldSectorRegisterPlugin(sizeof(RwMeshCache *),
                                    rwID_RXWORLDDEVICEMODULE,
                                    _pluginObjectCreate,
                                    _pluginObjectDestroy,
                                    _pluginSectorCopy);

    /* We make sure that meshcaches are set up (this does a malloc and
     * we're trying to avoid fragmentation caused by allocation when
     * objects are first rendered) for atomics and sectors
     *  o when they are read from a stream (below)
     *  o when they are copied (above)
     * ...both of which likely happen at app startup so fragmentation
     * is minimised. We also need to have normal constructors/destructors
     * for the meshcaches. */
    status |= RpAtomicSetStreamAlwaysCallBack(
                  rwID_RXWORLDDEVICEMODULE, _initAtomicMeshCache);
    status |= RpWorldSectorSetStreamAlwaysCallBack(
                  rwID_RXWORLDDEVICEMODULE, _initSectorMeshCache);

    if (!((rwPip2GeometryOffset | rwPip2AtomicOffset |
           rwPip2WorldSectorOffset | status) < 0))
    {
        result = TRUE;
    }

    RWRETURN(result);
}

/* The four flavours of WorldSector object pipe
 *  - PS2All, PS2Manager, AllInOne and vanilla */

static RxPipeline *
CreatePS2AllWorldSectorPipeline(void)
{
    RxPipeline         *pipe;

    RWFUNCTION(RWSTRING("CreatePS2AllWorldSectorPipeline"));

    pipe = RxPipelineCreate();
    if (pipe)
    {
        RxLockedPipe       *lpipe;

        lpipe = RxPipelineLock(pipe);

        if (NULL != lpipe)
        {
            RxNodeDefinition   *ps2all = RxNodeDefinitionGetPS2All();
            RxPipelineNode     *plnode;

            lpipe = RxLockedPipeAddFragment(lpipe,
                                            (RwUInt32 *)NULL,
                                            ps2all,
                                            (RxNodeDefinition *)NULL);

            RWASSERT(lpipe != NULL);

            lpipe = RxLockedPipeUnlock(lpipe);

            if (lpipe != NULL)
            {
                plnode = RxPipelineFindNodeByName(lpipe,
                                                  ps2all->name, 
                                                  (RxPipelineNode *)NULL, 
                                                  (RwInt32 *)NULL);
                RWASSERT(plnode!=NULL);

                /* Set up the necessary callbacks */
                RxPipelineNodePS2AllSetCallBack(
                    plnode, rxPS2ALLCALLBACKOBJECTSETUP,
                    RpWorldSectorPS2AllObjectSetupCallBack);
                RxPipelineNodePS2AllSetCallBack(
                    plnode, rxPS2ALLCALLBACKOBJECTFINALIZE,
                    NULL);

                RWRETURN(pipe);
            }
        }

        RxPipelineDestroy(pipe);
    }

    RWERROR((E_RW_DEFAULTPIPELINECREATION, "PS2AllWorldSector"));

    RWRETURN((RxPipeline *)NULL);
}

static RxPipeline *
CreatePS2ManagerWorldSectorPipeline(void)
{
    RxPipeline         *pipe;

    RWFUNCTION(RWSTRING("CreatePS2ManagerWorldSectorPipeline"));

    pipe = RxPipelineCreate();
    if (pipe)
    {
        RxLockedPipe       *lpipe;

        lpipe = RxPipelineLock(pipe);

        if (NULL != lpipe)
        {
            RxNodeDefinition   *manager =
                RxNodeDefinitionGetPS2Manager(rxOBJTYPE_WORLDSECTOR);
            RxPipelineNode     *plnode, *result;

            lpipe = RxLockedPipeAddFragment(lpipe,
                                            (RwUInt32 *)NULL,
                                            manager,
                                            (RxNodeDefinition *)NULL);

            RWASSERT(lpipe != NULL);

            plnode = RxPipelineFindNodeByName(lpipe,
                                              manager->name,
                                              (RxPipelineNode *)NULL,
                                              (RwInt32 *)NULL);
            RWASSERT(plnode != NULL);
            /* get manager node to generate cluster "xyz" */
            result = RxPipelineNodePS2ManagerGenerateCluster(
                         plnode, &RxClPS2xyz, CL_XYZ);
            RWASSERT(result != NULL);

            /* get manager node to generate cluster "uv" */
            result = RxPipelineNodePS2ManagerGenerateCluster(
                         plnode, &RxClPS2uv, CL_UV);
            RWASSERT(result != NULL);

            /* get manager node to generate cluster "rgba" */
            result = RxPipelineNodePS2ManagerGenerateCluster(
                         plnode, &RxClPS2rgba, CL_RGBA);
            RWASSERT(result != NULL);

            /* get manager node to generate cluster "normal" */
            result = RxPipelineNodePS2ManagerGenerateCluster(
                          plnode, &RxClPS2normal, CL_NORMAL);
            RWASSERT(result != NULL);

            RxPipelineNodePS2ManagerSetVUBufferSizes(plnode,
                                              _rwskyStrideOfInputCluster,
                                              _rwskyTSVertexCount,
                                              _rwskyTLTriCount);

            lpipe = RxLockedPipeUnlock(lpipe);

            if (lpipe != NULL)
            {
                plnode = RxPipelineFindNodeByName(lpipe,
                                                  manager->name,
                                                  (RxPipelineNode *)NULL,
                                                  (RwInt32 *)NULL);
                RWASSERT(plnode!=NULL);

                RxPipelineNodePS2ManagerSetVIFOffset(plnode,
                                                     _rwskyVIFOffset);

                /* Hack to change VU1 code array index calculation - due
                 * to our new default VU1 code array being smaller since
                 * putting in backface culling support. Deliberately no
                 * wrapper function for this. All other pipes should
                 * work exactly as before. */
                ((RxPipelineNodePS2ObjAllInOneData *)plnode->privateData)
                    ->genericVU1Index = TRUE;

                RWRETURN(pipe);
            }
        }

        RxPipelineDestroy(pipe);
    }

    RWERROR((E_RW_DEFAULTPIPELINECREATION, "PS2ManagerWorldSector"));

    RWRETURN((RxPipeline *)NULL);
}

static RxPipeline *
CreateAllInOneWorldSectorPipeline(void)
{
    RxPipeline         *pipe;

    RWFUNCTION(RWSTRING("CreateAllInOneWorldSectorPipeline"));

    pipe = RxPipelineCreate();
    if (pipe)
    {
        RxLockedPipe       *lpipe;

        lpipe = RxPipelineLock(pipe);

        if (NULL != lpipe)
        {
            RxNodeDefinition   *objallinone =
                RxNodeDefinitionGetPS2ObjAllInOne(rxOBJTYPE_WORLDSECTOR);
            RxNodeDefinition   *instance =
                RxNodeDefinitionGetPS2MatInstance();
            RxNodeDefinition   *bridge =
                RxNodeDefinitionGetPS2MatBridge();
            RxPipelineNode     *plnode;
            RxPipelineNode     *result;

            lpipe = RxLockedPipeAddFragment(lpipe,
                                            (RwUInt32 *)NULL,
                                            objallinone,
                                            instance,
                                            bridge,
                                            (RxNodeDefinition *)NULL);

            RWASSERT(lpipe != NULL);

            /* get instance node to generate cluster "xyz" */
            plnode = RxPipelineFindNodeByName(lpipe,
                                              instance->name,
                                              (RxPipelineNode *)NULL,
                                              (RwInt32 *)NULL);
            RWASSERT(plnode != NULL);
            result = RxPipelineNodePS2MatInstanceGenerateCluster(
                         plnode, &RxClPS2xyz, CL_XYZ);
            RWASSERT(result != NULL);

            /* get instance node to generate cluster "uv" */
            result = RxPipelineNodePS2MatInstanceGenerateCluster(
                         plnode, &RxClPS2uv, CL_UV);
            RWASSERT(result != NULL);

            /* get instance node to generate cluster "rgba" */
            result = RxPipelineNodePS2MatInstanceGenerateCluster(
                         plnode, &RxClPS2rgba, CL_RGBA);
            RWASSERT(result != NULL);

            /* get instance node to generate cluster "normal" */
            result = RxPipelineNodePS2MatInstanceGenerateCluster(
                         plnode, &RxClPS2normal, CL_NORMAL);
            RWASSERT(result != NULL);

            RxPipelineNodePS2MatInstanceNodeSetVUBufferSizes(plnode,
                                         _rwskyStrideOfInputCluster,
                                         _rwskyTSVertexCount,
                                         _rwskyTLTriCount);

            lpipe = RxLockedPipeUnlock(lpipe);

            if (lpipe != NULL)
            {

                plnode = RxPipelineFindNodeByName(lpipe,
                                                  bridge->name,
                                                  (RxPipelineNode *)NULL,
                                                  (RwInt32 *)NULL);
                RWASSERT(plnode!=NULL);

                RxBridgeNodeSetVIFOffset(plnode, _rwskyVIFOffset);

                /* Get the instance node to dispatch per-mesh packets to the
                 * pipelines specified by their associated materials */
                plnode = RxPipelineFindNodeByName(lpipe,
                                                  objallinone->name,
                                                  (RxPipelineNode *)NULL,
                                                  (RwInt32 *)NULL);
                RWASSERT(plnode != NULL);
                result =
                    RxPipelineNodePS2ObjAllInOneSetGrouping(plnode, TRUE);
                RWASSERT(result != NULL);

                RWRETURN(pipe);
            }
        }

        RxPipelineDestroy(pipe);
    }

    RWERROR((E_RW_DEFAULTPIPELINECREATION, "AllInOneWorldSector"));

    RWRETURN((RxPipeline *)NULL);
}

static RxPipeline *
CreateVanillaWorldSectorPipeline(void)
{
    RxPipeline         *pipe;

    RWFUNCTION(RWSTRING("CreateVanillaWorldSectorPipeline"));

    pipe = RxPipelineCreate();
    if (pipe)
    {
        RxLockedPipe       *lpipe;

        lpipe = RxPipelineLock(pipe);

        if (NULL != lpipe)
        {
            RxNodeDefinition *objallinone =
                RxNodeDefinitionGetPS2ObjAllInOne(rxOBJTYPE_WORLDSECTOR);

            lpipe = RxLockedPipeAddFragment(lpipe,
                                            (RwUInt32 *)NULL,
                                            objallinone,
                                            (RxNodeDefinition *)NULL);

            RWASSERT(lpipe != NULL);

            lpipe = RxLockedPipeUnlock(lpipe);

            if (lpipe != NULL)
            {
                RWRETURN(pipe);
            }
        }

        RxPipelineDestroy(pipe);
    }

    RWERROR((E_RW_DEFAULTPIPELINECREATION, "VanillaWorldSector"));

    RWRETURN((RxPipeline *)NULL);
}

/**
 * \ingroup rpworldsectorsky2
 * \ref RpWorldSectorSkyGetPS2AllPipeline returns a
 * pointer to the default world-sector PS2All pipeline.
 *
 * The world-sector pipeline based on the PS2All.csl node renders
 * world-sectors using just this node, in a similar fashion to
 * PS2Manager.csl. The differences are that PS2All.csl does not
 * ignore material pipelines (with the restriction that they
 * must be constructed from the PS2AllMat.csl node; see
 * \ref RxNodeDefinitionGetPS2All and \ref RxNodeDefinitionGetPS2AllMat
 * for details) and PS2All.csl is far more customisable with
 * more available callbacks than PS2Manager.csl. The aim here is
 * not so much to optimize the default RW RpAtomic and RpWorldSector
 * pipelines but rather to allow developers to optimize pipelines
 * based on the specifics of objects and vector code used in their
 * game.
 *
 * By default, sectors are rendered using this PS2All-based pipeline.
 *
 * If you wish to submit 2D triangles through Submit.csl (i.e you want to
 * do transformation and lighting and whatever else CPU-side - which will
 * of course be very slow) then rather than use this world sector object
 * pipeline, you should use a generic (non-PS2-specific) one.
 *
 * The world plugin must be attached before using this function.
 *
 * The PS2All world-sector object pipeline:
 *
 *   \li PS2All.csl
 *
 * \return Returns pointer to the pipeline if successful or NULL if there
 * is an error.
 *
 * \see RxNodeDefinitionGetPS2All
 * \see RxNodeDefinitionGetPS2AllMat
 * \see RxNodeDefinitionGetPS2Manager
 * \see RxPipelineNodePS2ManagerGenerateCluster
 * \see RxPipelineNodePS2ManagerSetVUBufferSizes
 * \see RxPipelineNodePS2ManagerSetPointListVUBufferSize
 * \see RxPipelineNodePS2ManagerSetVIFOffset
 * \see RxPipelineNodePS2ManagerSetVU1CodeArray
 * \see RxPipelineNodePS2ManagerSetLighting
 * \see RpWorldSectorSkyGetPS2AllPipeline
 * \see RpWorldSectorSkyGetPS2ManagerPipeline
 * \see RpWorldSectorSkyGetAllInOnePipeline
 * \see RpWorldSectorSkyGetVanillaPipeline
 * \see RpWorldGetDefaultSectorPipeline
 * \see \ref RpWorldGetDefaultSectorPipelineplatform
 * \see RpWorldSetDefaultSectorPipeline
 * \see RpWorldGetSectorPipeline
 * \see RpWorldSetSectorPipeline
 * \see RpWorldSectorGetPipeline
 * \see RpWorldSectorSetPipeline
 * \see RpAtomicGetDefaultPipeline
 * \see \ref RpAtomicGetDefaultPipelineplatform
 * \see RpAtomicSetDefaultPipeline
 * \see RpAtomicSkyGetPS2AllPipeline
 * \see RpAtomicSkyGetPS2ManagerPipeline
 * \see RpAtomicSkyGetAllInOnePipeline
 * \see RpAtomicSkyGetVanillaPipeline
 * \see RpAtomicGetPipeline
 * \see RpAtomicSetPipeline
 * \see RpMaterialGetDefaultPipeline
 * \see \ref RpMaterialGetDefaultPipelineplatform
 * \see RpMaterialSetDefaultPipeline
 * \see RpMaterialSkyGetPS2AllMatPipeline
 * \see RpMaterialSkyGetDefaultPS2AllMatPipeline
 * \see RpMaterialSkySetDefaultPS2AllMatPipeline
 * \see RpMaterialGetPipeline
 * \see RpMaterialSetPipeline
 */
RxPipeline *
RpWorldSectorSkyGetPS2AllPipeline(void)
{
    RWAPIFUNCTION(RWSTRING("RpWorldSectorSkyGetPS2AllPipeline"));

    RWRETURN(RXPIPELINEGLOBAL(platformWorldSectorPipeline));
}

/**
 * \ingroup rpworldsectorsky2
 * \ref RpWorldSectorSkyGetPS2ManagerPipeline returns a
 * pointer to the default world-sector PS2Manager pipeline.
 *
 * The world-sector pipeline based on the PS2Manager node renders
 * world-sectors using just this node (shrinking the pipe to a single
 * node is a significant speed win on PS2), ignoring material pipelines,
 * so all meshes in a sector will be rendered in the same style (that
 * defined by the sector pipeline). This node will be made much more
 * customisable in future releases such that it will be all you need
 * on PS2 (bar nodes like PVSWorldSector.csl).
 *
 * By default, sectors are rendered using the PS2All-based pipeline
 * (see \ref RpWorldSectorSkyGetPS2AllPipeline).
 *
 * If you wish to submit 2D triangles through Submit.csl (i.e you want to
 * do transformation and lighting and whatever else CPU-side - which will
 * of course be very slow) then rather than use this world sector object
 * pipeline, you should use a generic (non-PS2-specific) one.
 *
 * The world plugin must be attached before using this function.
 *
 * The PS2Manager world-sector object pipeline:
 *
 *   \li PS2Manager.csl
 *
 * \return Returns pointer to the pipeline if successful or NULL if there
 * is an error.
 *
 * \see RxNodeDefinitionGetPS2Manager
 * \see RxPipelineNodePS2ManagerGenerateCluster
 * \see RxPipelineNodePS2ManagerSetVUBufferSizes
 * \see RxPipelineNodePS2ManagerSetPointListVUBufferSize
 * \see RxPipelineNodePS2ManagerSetVIFOffset
 * \see RxPipelineNodePS2ManagerSetVU1CodeArray
 * \see RxPipelineNodePS2ManagerSetLighting
 * \see RpWorldSectorSkyGetPS2AllPipeline
 * \see RpWorldSectorSkyGetAllInOnePipeline
 * \see RpWorldSectorSkyGetVanillaPipeline
 * \see RpWorldGetDefaultSectorPipeline
 * \see \ref RpWorldGetDefaultSectorPipelineplatform
 * \see RpWorldSetDefaultSectorPipeline
 * \see RpWorldGetSectorPipeline
 * \see RpWorldSetSectorPipeline
 * \see RpWorldSectorGetPipeline
 * \see RpWorldSectorSetPipeline
 * \see RpAtomicGetDefaultPipeline
 * \see \ref RpAtomicGetDefaultPipelineplatform
 * \see RpAtomicSetDefaultPipeline
 * \see RpAtomicSkyGetPS2AllPipeline
 * \see RpAtomicSkyGetPS2ManagerPipeline
 * \see RpAtomicSkyGetAllInOnePipeline
 * \see RpAtomicSkyGetVanillaPipeline
 * \see RpAtomicGetPipeline
 * \see RpAtomicSetPipeline
 * \see RpMaterialGetDefaultPipeline
 * \see \ref RpMaterialGetDefaultPipelineplatform
 * \see RpMaterialSetDefaultPipeline
 * \see RpMaterialSkyGetPS2AllMatPipeline
 * \see RpMaterialSkyGetDefaultPS2AllMatPipeline
 * \see RpMaterialSkySetDefaultPS2AllMatPipeline
 * \see RpMaterialGetPipeline
 * \see RpMaterialSetPipeline
 */
RxPipeline *
RpWorldSectorSkyGetPS2ManagerPipeline(void)
{
    RWAPIFUNCTION(RWSTRING("RpWorldSectorSkyGetPS2ManagerPipeline"));

    RWRETURN(RXPIPELINEGLOBAL(ps2ManagerWorldSectorPipeline));
}

/**
 * \ingroup rpworldsectorsky2
 * \ref RpWorldSectorSkyGetAllInOnePipeline
 * returns a pointer to the default world-sector AllInOne pipeline.
 *
 * This world-sector object pipeline is effectively the vanilla instance
 * pipeline concatenated with the default material pipeline. This
 * pipeline is somewhat less flexible than the split object/material
 * pipelines, since the whole world-sector is rendered the same way -
 * material pipelines are ignored. However, it executes faster because
 * passing packets between pipelines is quite slow. Any rendering effect
 * can still be achieved (uniformly across the whole object) by simply
 * concatenating any custom material pipeline onto the PS2ObjAllInOne.csl
 * node and using the \ref RxPipelineNodePS2ObjAllInOneSetGrouping node API
 * function to tell this node to send all meshes in the object down the rest
 * of the object pipeline rather than to pipelines specified by the
 * materials of the object.
 *
 * By default, sectors are rendered using the PS2All-based pipeline
 * (see \ref RpWorldSectorSkyGetPS2AllPipeline).
 *
 * If you wish to submit 2D triangles through Submit.csl (i.e you want to
 * do transformation and lighting and whatever else CPU-side - which will
 * of course be very slow) then rather than use this world sector object
 * pipeline, you should use a generic (non-PS2-specific) one.
 *
 * The world plugin must be attached before using this function.
 *
 * The PS2 all-in-one world-sector object pipeline:
 * \verbatim
     PS2ObjAllInOne.csl
      v
     PS2MatInstance.csl
      v
     PS2MatBridge.csl
   \endverbatim
 * \return Returns pointer to the pipeline if successful or NULL if there
 * is an error.
 *
 * \see RxNodeDefinitionGetPS2ObjAllInOne
 * \see RxPipelineNodePS2ObjAllInOneSetGrouping
 * \see RxNodeDefinitionGetPS2MatInstance
 * \see RxNodeDefinitionGetPS2MatBridge
 * \see RpWorldSectorSkyGetPS2AllPipeline
 * \see RpWorldSectorSkyGetPS2ManagerPipeline
 * \see RpWorldSectorSkyGetVanillaPipeline
 * \see RpWorldGetDefaultSectorPipeline
 * \see \ref RpWorldGetDefaultSectorPipelineplatform
 * \see RpWorldSetDefaultSectorPipeline
 * \see RpWorldGetSectorPipeline
 * \see RpWorldSetSectorPipeline
 * \see RpWorldSectorGetPipeline
 * \see RpWorldSectorSetPipeline
 * \see RpAtomicGetDefaultPipeline
 * \see \ref RpAtomicGetDefaultPipelineplatform
 * \see RpAtomicSetDefaultPipeline
 * \see RpAtomicSkyGetPS2AllPipeline
 * \see RpAtomicSkyGetPS2ManagerPipeline
 * \see RpAtomicSkyGetAllInOnePipeline
 * \see RpAtomicSkyGetVanillaPipeline
 * \see RpAtomicGetPipeline
 * \see RpAtomicSetPipeline
 * \see RpMaterialGetDefaultPipeline
 * \see \ref RpMaterialGetDefaultPipelineplatform
 * \see RpMaterialSetDefaultPipeline
 * \see RpMaterialSkyGetPS2AllMatPipeline
 * \see RpMaterialSkyGetDefaultPS2AllMatPipeline
 * \see RpMaterialSkySetDefaultPS2AllMatPipeline
 * \see RpMaterialGetPipeline
 * \see RpMaterialSetPipeline
 */
RxPipeline *
RpWorldSectorSkyGetAllInOnePipeline(void)
{
    RxPipeline *result;
    RWAPIFUNCTION(RWSTRING("RpWorldSectorSkyGetAllInOnePipeline"));

    result =  RXPIPELINEGLOBAL(allInOneWorldSectorPipeline);

    RWRETURN(result);
}

/**
 * \ingroup rpworldsectorsky2
 * \ref RpWorldSectorSkyGetVanillaPipeline
 * returns a pointer to the default world-sector vanilla pipeline.
 *
 * This world-sector object pipeline is the 'vanilla' one (i.e no
 * tricks have been used to trade off flexibility and speed - see
 * \ref RpWorldSectorSkyGetPS2ManagerPipeline and
 * \ref RpWorldSectorSkyGetAllInOnePipeline) and it deals with per-object
 * data (transformation matrix, lights, etc) before passing one packet
 * per mesh to RpMaterial-specified material pipelines.
 *
 * By default, sectors are rendered using the PS2All-based pipeline
 * (see \ref RpWorldSectorSkyGetPS2AllPipeline).
 *
 * If you wish to submit 2D triangles through Submit.csl (i.e you want to
 * do transformation and lighting and whatever else CPU-side - which will
 * of course be very slow) then rather than use this world sector object
 * pipeline, you should use a generic (non-PS2-specific) one.
 *
 * The world plugin must be attached before using this function.
 *
 * \verbatim
   The PS2 vanilla world-sector object pipeline:

     PS2ObjAllInOne.csl
   \endverbatim
 *
 * \return Returns pointer to the pipeline if successful or NULL if there
 * is an error.
 *
 * \see RxNodeDefinitionGetPS2ObjAllInOne
 * \see RpWorldSectorSkyGetPS2AllPipeline
 * \see RpWorldSectorSkyGetPS2ManagerPipeline
 * \see RpWorldSectorSkyGetAllInOnePipeline
 * \see RpWorldGetDefaultSectorPipeline
 * \see \ref RpWorldGetDefaultSectorPipelineplatform
 * \see RpWorldSetDefaultSectorPipeline
 * \see RpWorldGetSectorPipeline
 * \see RpWorldSetSectorPipeline
 * \see RpWorldSectorGetPipeline
 * \see RpWorldSectorSetPipeline
 * \see RpAtomicGetDefaultPipeline
 * \see \ref RpAtomicGetDefaultPipelineplatform
 * \see RpAtomicSetDefaultPipeline
 * \see RpAtomicSkyGetPS2AllPipeline
 * \see RpAtomicSkyGetPS2ManagerPipeline
 * \see RpAtomicSkyGetAllInOnePipeline
 * \see RpAtomicSkyGetVanillaPipeline
 * \see RpAtomicGetPipeline
 * \see RpAtomicSetPipeline
 * \see RpMaterialGetDefaultPipeline
 * \see \ref RpMaterialGetDefaultPipelineplatform
 * \see RpMaterialSetDefaultPipeline
 * \see RpMaterialSkyGetPS2AllMatPipeline
 * \see RpMaterialSkyGetDefaultPS2AllMatPipeline
 * \see RpMaterialSkySetDefaultPS2AllMatPipeline
 * \see RpMaterialGetPipeline
 * \see RpMaterialSetPipeline
 */
RxPipeline *
RpWorldSectorSkyGetVanillaPipeline(void)
{
    RWAPIFUNCTION(RWSTRING("RpWorldSectorSkyGetVanillaPipeline"));

    RWRETURN(RXPIPELINEGLOBAL(vanillaWorldSectorPipeline));
}

/* The four flavours of Atomic object pipe
 *  - PS2All, PS2Manager, AllInOne and vanilla */

static RxPipeline *
CreatePS2AllAtomicPipeline(void)
{
    RxPipeline         *pipe;

    RWFUNCTION(RWSTRING("CreatePS2AllAtomicPipeline"));

    pipe = RxPipelineCreate();
    if (pipe)
    {
        RxLockedPipe       *lpipe;

        lpipe = RxPipelineLock(pipe);

        if (NULL != lpipe)
        {
            RxNodeDefinition   *ps2all = RxNodeDefinitionGetPS2All();
            RxPipelineNode     *plnode;

            lpipe = RxLockedPipeAddFragment(lpipe,
                                            (RwUInt32 *)NULL,
                                            ps2all,
                                            (RxNodeDefinition *)NULL);

            RWASSERT(lpipe != NULL);

            lpipe = RxLockedPipeUnlock(lpipe);

            if (lpipe != NULL)
            {
                plnode = RxPipelineFindNodeByName(lpipe,
                                                  ps2all->name, 
                                                  (RxPipelineNode *)NULL, 
                                                  (RwInt32 *)NULL);
                RWASSERT(plnode!=NULL);

                /* Set up the necessary callbacks */
                RxPipelineNodePS2AllSetCallBack(
                    plnode, rxPS2ALLCALLBACKOBJECTSETUP,
                    RpAtomicPS2AllObjectSetupCallBack);
                RxPipelineNodePS2AllSetCallBack(
                    plnode, rxPS2ALLCALLBACKOBJECTFINALIZE,
                    NULL);

                RWRETURN(pipe);
            }
        }

        RxPipelineDestroy(pipe);
    }

    RWERROR((E_RW_DEFAULTPIPELINECREATION, "PS2AllAtomic"));

    RWRETURN((RxPipeline *)NULL);
}

static RxPipeline         *
CreatePS2ManagerAtomicPipeline(void)
{
    RxPipeline         *pipe;

    RWFUNCTION(RWSTRING("CreatePS2ManagerAtomicPipeline"));

    pipe = RxPipelineCreate();
    if (pipe)
    {
        RxLockedPipe       *lpipe;

        lpipe = RxPipelineLock(pipe);

        if (NULL != lpipe)
        {
            RxNodeDefinition *manager =
                RxNodeDefinitionGetPS2Manager(rxOBJTYPE_ATOMIC);
            RxPipelineNode   *plnode, *result;

            lpipe = RxLockedPipeAddFragment(lpipe,
                                            (RwUInt32 *)NULL,
                                            manager,
                                            (RxNodeDefinition *)NULL);

            RWASSERT(lpipe != NULL);

            plnode = RxPipelineFindNodeByName(lpipe,
                                              manager->name,
                                              (RxPipelineNode *)NULL,
                                              (RwInt32 *)NULL);
            RWASSERT(plnode != NULL);
            /* get manager node to generate cluster "xyz" */
            result = RxPipelineNodePS2ManagerGenerateCluster(
                         plnode, &RxClPS2xyz, CL_XYZ);
            RWASSERT(result != NULL);

            /* get manager node to generate cluster "uv" */
            result = RxPipelineNodePS2ManagerGenerateCluster(
                         plnode, &RxClPS2uv, CL_UV);
            RWASSERT(result != NULL);

            /* get manager node to generate cluster "rgba" */
            result = RxPipelineNodePS2ManagerGenerateCluster(
                         plnode, &RxClPS2rgba, CL_RGBA);
            RWASSERT(result != NULL);

            /* get manager node to generate cluster "normal" */
            result = RxPipelineNodePS2ManagerGenerateCluster(
                         plnode, &RxClPS2normal, CL_NORMAL);
            RWASSERT(result != NULL);

            RxPipelineNodePS2ManagerSetVUBufferSizes(plnode,
                                         _rwskyStrideOfInputCluster,
                                         _rwskyTSVertexCount,
                                         _rwskyTLTriCount);

            lpipe = RxLockedPipeUnlock(lpipe);

            if (lpipe != NULL)
            {
                plnode = RxPipelineFindNodeByName(lpipe,
                                                  manager->name,
                                                  (RxPipelineNode *)NULL,
                                                  (RwInt32 *)NULL);
                RWASSERT(plnode!=NULL);

                RxPipelineNodePS2ManagerSetVIFOffset(plnode,
                                                     _rwskyVIFOffset);

                /* Hack to change VU1 code array index calculation - due
                 * to our new default VU1 code array being smaller since
                 * putting in backface culling support. Deliberately no
                 * wrapper function for this. All other pipes should
                 * work exactly as before. */
                ((RxPipelineNodePS2ObjAllInOneData *)plnode->privateData)
                    ->genericVU1Index = TRUE;

                RWRETURN(pipe);
             }
        }

        RxPipelineDestroy(pipe);
    }

    RWERROR((E_RW_DEFAULTPIPELINECREATION, "PS2ManagerAtomic"));

    RWRETURN((RxPipeline *)NULL);
}

static RxPipeline         *
CreateAllInOneAtomicPipeline(void)
{
    RxPipeline         *pipe;

    RWFUNCTION(RWSTRING("CreateAllInOneAtomicPipeline"));

    pipe = RxPipelineCreate();
    if (pipe)
    {
        RxLockedPipe       *lpipe;

        lpipe = RxPipelineLock(pipe);

        if (NULL != lpipe)
        {
            RxNodeDefinition   *objallinone =
                RxNodeDefinitionGetPS2ObjAllInOne(rxOBJTYPE_ATOMIC);
            RxNodeDefinition   *instance =
                RxNodeDefinitionGetPS2MatInstance();
            RxNodeDefinition   *bridge =
                RxNodeDefinitionGetPS2MatBridge();
            RxPipelineNode     *plnode;
            RxPipelineNode     *result;

            lpipe = RxLockedPipeAddFragment(lpipe,
                                            (RwUInt32 *)NULL,
                                            objallinone,
                                            instance,
                                            bridge,
                                            (RxNodeDefinition *)NULL);

            RWASSERT(lpipe != NULL);

            /* get instance node to generate cluster "xyz" */
            plnode = RxPipelineFindNodeByName(lpipe,
                                              instance->name,
                                              (RxPipelineNode *)NULL,
                                              (RwInt32 *)NULL);
            RWASSERT(plnode != NULL);
            result = RxPipelineNodePS2MatInstanceGenerateCluster(
                         plnode, &RxClPS2xyz, CL_XYZ);
            RWASSERT(result != NULL);

            /* get instance node to generate cluster "uv" */
            result = RxPipelineNodePS2MatInstanceGenerateCluster(
                         plnode, &RxClPS2uv, CL_UV);
            RWASSERT(result != NULL);

            /* get instance node to generate cluster "rgba" */
            result = RxPipelineNodePS2MatInstanceGenerateCluster(
                         plnode, &RxClPS2rgba, CL_RGBA);
            RWASSERT(result != NULL);

            /* get instance node to generate cluster "normal" */
            result = RxPipelineNodePS2MatInstanceGenerateCluster(
                         plnode, &RxClPS2normal, CL_NORMAL);
            RWASSERT(result != NULL);

            RxPipelineNodePS2MatInstanceNodeSetVUBufferSizes(plnode,
                                         _rwskyStrideOfInputCluster,
                                         _rwskyTSVertexCount,
                                         _rwskyTLTriCount);

            lpipe = RxLockedPipeUnlock(lpipe);

            if (lpipe != NULL)
            {
                plnode = RxPipelineFindNodeByName(lpipe,
                                                  bridge->name,
                                                  (RxPipelineNode *)NULL,
                                                  (RwInt32 *)NULL);
                RWASSERT(plnode!=NULL);

                RxBridgeNodeSetVIFOffset(plnode, _rwskyVIFOffset);

                /* Get the instance node to dispatch per-mesh packets to the
                 * pipelines specified by their associated materials */
                plnode = RxPipelineFindNodeByName(lpipe,
                                                  objallinone->name,
                                                  (RxPipelineNode *)NULL,
                                                  (RwInt32 *)NULL);
                RWASSERT(plnode != NULL);
                result =
                    RxPipelineNodePS2ObjAllInOneSetGrouping(plnode, TRUE);
                RWASSERT(result != NULL);

                RWRETURN(pipe);
            }

        }

        RxPipelineDestroy(pipe);
    }

    RWERROR((E_RW_DEFAULTPIPELINECREATION, "AllInOneAtomic"));

    RWRETURN((RxPipeline *)NULL);
}

static RxPipeline         *
CreateVanillaAtomicPipeline(void)
{
    RxPipeline         *pipe;

    RWFUNCTION(RWSTRING("CreateVanillaAtomicPipeline"));

    pipe = RxPipelineCreate();
    if (pipe)
    {
        RxLockedPipe       *lpipe;

        lpipe = RxPipelineLock(pipe);

        if (NULL != lpipe)
        {
            RxNodeDefinition *objallinone =
                RxNodeDefinitionGetPS2ObjAllInOne(rxOBJTYPE_ATOMIC);

            lpipe = RxLockedPipeAddFragment(lpipe,
                                             (RwUInt32 *)NULL,
                                            objallinone,
                                            (RxNodeDefinition *)NULL);

            RWASSERT(lpipe != NULL);

            lpipe = RxLockedPipeUnlock(lpipe);

            if (lpipe != NULL)
            {
                RWRETURN(pipe);
            }
        }

        RxPipelineDestroy(pipe);
    }

    RWERROR((E_RW_DEFAULTPIPELINECREATION, "VanillaAtomic"));

    RWRETURN((RxPipeline *)NULL);
}

/**
 * \ingroup rpatomicsky2
 * \ref RpAtomicSkyGetPS2AllPipeline
 * returns a pointer to the default atomic PS2All pipeline.
 *
 * The atomic pipeline based on the PS2All.csl node renders
 * atomics using just this node, in a similar fashion to
 * PS2Manager.csl. The differences are that PS2All.csl does not
 * ignore material pipelines (with the restriction that they
 * must be constructed from the PS2AllMat.csl node; see
 * \ref RxNodeDefinitionGetPS2All and \ref RxNodeDefinitionGetPS2AllMat
 * for details) and PS2All.csl is far more customisable with
 * more available callbacks than PS2Manager.csl. The aim here is
 * not so much to optimize the default RW RpAtomic and RpWorldSector
 * pipelines but rather to allow developers to optimize pipelines
 * based on the specifics of objects and vector code used in their
 * game.
 *
 * By default, atomics are rendered using this PS2All-based pipeline.
 *
 * If you wish to submit 2D triangles through Submit.csl (i.e you want to
 * do transformation and lighting and whatever else CPU-side - which will
 * of course be very slow) then rather than use this atomic object
 * pipeline, you should use a generic (non-PS2-specific) one.
 *
 * The world plugin must be attached before using this function.
 *
 * The PS2Atomic atomic object pipeline:
 *
 *   PS2All.csl
 *
 * \return Returns pointer to the pipeline if successful or NULL if there
 * is an error.
 *
 * \see RxNodeDefinitionGetPS2All
 * \see RxNodeDefinitionGetPS2AllMat
 * \see RxNodeDefinitionGetPS2Manager
 * \see RxPipelineNodePS2ManagerGenerateCluster
 * \see RxPipelineNodePS2ManagerSetVUBufferSizes
 * \see RxPipelineNodePS2ManagerSetPointListVUBufferSize
 * \see RxPipelineNodePS2ManagerSetVIFOffset
 * \see RxPipelineNodePS2ManagerSetVU1CodeArray
 * \see RxPipelineNodePS2ManagerSetLighting
 * \see RpAtomicGetDefaultPipeline
 * \see \ref RpAtomicGetDefaultPipelineplatform
 * \see RpAtomicSetDefaultPipeline
 * \see RpAtomicSkyGetPS2ManagerPipeline
 * \see RpAtomicSkyGetAllInOnePipeline
 * \see RpAtomicSkyGetVanillaPipeline
 * \see RpAtomicGetPipeline
 * \see RpAtomicSetPipeline
 * \see RpWorldSectorSkyGetPS2AllPipeline
 * \see RpWorldSectorSkyGetPS2ManagerPipeline
 * \see RpWorldSectorSkyGetAllInOnePipeline
 * \see RpWorldSectorSkyGetVanillaPipeline
 * \see RpWorldGetDefaultSectorPipeline
 * \see \ref RpWorldGetDefaultSectorPipelineplatform
 * \see RpWorldSetDefaultSectorPipeline
 * \see RpWorldGetSectorPipeline
 * \see RpWorldSetSectorPipeline
 * \see RpWorldSectorGetPipeline
 * \see RpWorldSectorSetPipeline
 * \see RpMaterialGetDefaultPipeline
 * \see \ref RpMaterialGetDefaultPipelineplatform
 * \see RpMaterialSetDefaultPipeline
 * \see RpMaterialSkyGetPS2AllMatPipeline
 * \see RpMaterialSkyGetDefaultPS2AllMatPipeline
 * \see RpMaterialSkySetDefaultPS2AllMatPipeline
 * \see RpMaterialGetPipeline
 * \see RpMaterialSetPipeline
 */
RxPipeline *
RpAtomicSkyGetPS2AllPipeline(void)
{
    RWAPIFUNCTION(RWSTRING("RpAtomicSkyGetPS2AllPipeline"));

    RWRETURN(RXPIPELINEGLOBAL(platformAtomicPipeline));
}

/**
 * \ingroup rpatomicsky2
 * \ref RpAtomicSkyGetPS2ManagerPipeline
 * returns a pointer to the default atomic PS2Manager pipeline.
 *
 * The atomic pipeline based on the PS2Manager node renders
 * atomics using just this node (shrinking the pipe to a single
 * node is a significant speed win on PS2), ignoring material pipelines,
 * so all meshes in an atomic will be rendered in the same style (that
 * defined by the atomic pipeline). This node will be made much more
 * customisable in future releases such that it will be all you need
 * on PS2 (bar nodes like PVSWorldSector.csl).
 *
 * By default, atomics are rendered using the PS2All-based pipeline
 * (see \ref RpAtomicSkyGetPS2AllPipeline).
 *
 * If you wish to submit 2D triangles through Submit.csl (i.e you want to
 * do transformation and lighting and whatever else CPU-side - which will
 * of course be very slow) then rather than use this atomic object
 * pipeline, you should use a generic (non-PS2-specific) one.
 *
 * The world plugin must be attached before using this function.
 *
 * The PS2Manager atomic object pipeline:
 *
 *   PS2Manager.csl
 *
 * \return Returns pointer to the pipeline if successful or NULL if there
 * is an error.
 *
 * \see RxNodeDefinitionGetPS2Manager
 * \see RxPipelineNodePS2ManagerGenerateCluster
 * \see RxPipelineNodePS2ManagerSetVUBufferSizes
 * \see RxPipelineNodePS2ManagerSetPointListVUBufferSize
 * \see RxPipelineNodePS2ManagerSetVIFOffset
 * \see RxPipelineNodePS2ManagerSetVU1CodeArray
 * \see RxPipelineNodePS2ManagerSetLighting
 * \see RpAtomicGetDefaultPipeline
 * \see \ref RpAtomicGetDefaultPipelineplatform
 * \see RpAtomicSetDefaultPipeline
 * \see RpAtomicSkyGetPS2AllPipeline
 * \see RpAtomicSkyGetAllInOnePipeline
 * \see RpAtomicSkyGetVanillaPipeline
 * \see RpAtomicGetPipeline
 * \see RpAtomicSetPipeline
 * \see RpWorldSectorSkyGetPS2AllPipeline
 * \see RpWorldSectorSkyGetPS2ManagerPipeline
 * \see RpWorldSectorSkyGetAllInOnePipeline
 * \see RpWorldSectorSkyGetVanillaPipeline
 * \see RpWorldGetDefaultSectorPipeline
 * \see \ref RpWorldGetDefaultSectorPipelineplatform
 * \see RpWorldSetDefaultSectorPipeline
 * \see RpWorldGetSectorPipeline
 * \see RpWorldSetSectorPipeline
 * \see RpWorldSectorGetPipeline
 * \see RpWorldSectorSetPipeline
 * \see RpMaterialGetDefaultPipeline
 * \see \ref RpMaterialGetDefaultPipelineplatform
 * \see RpMaterialSetDefaultPipeline
 * \see RpMaterialSkyGetPS2AllMatPipeline
 * \see RpMaterialSkyGetDefaultPS2AllMatPipeline
 * \see RpMaterialSkySetDefaultPS2AllMatPipeline
 * \see RpMaterialGetPipeline
 * \see RpMaterialSetPipeline
 */
RxPipeline *
RpAtomicSkyGetPS2ManagerPipeline(void)
{
    RWAPIFUNCTION(RWSTRING("RpAtomicSkyGetPS2ManagerPipeline"));

    RWRETURN(RXPIPELINEGLOBAL(ps2ManagerAtomicPipeline));
}

/**
 * \ingroup rpatomicsky2
 * \ref RpAtomicSkyGetAllInOnePipeline
 * returns a pointer to the default atomic AllInOne pipeline.
 *
 * This atomic object pipeline is effectively the vanilla instance
 * pipeline concatenated with the default material pipeline. This
 * pipeline is somewhat less flexible than the split object/material
 * pipelines, since the whole atomic is rendered the same way - material
 * pipelines are ignored. However, it executes faster because passing
 * packets between pipelines is quite slow. Any rendering effect can still
 * be achieved (uniformly across the whole object) by simply concatenating
 * any custom material pipeline onto the PS2ObjAllInOne.csl node and using
 * the \ref RxPipelineNodePS2ObjAllInOneSetGrouping node API function to
 * tell this node to send all meshes in the object down the rest of the
 * object pipeline rather than to pipelines specified by the materials of
 * the object.
 *
 * By default, atomics are rendered using the PS2All-based pipeline
 * (see \ref RpAtomicSkyGetPS2AllPipeline).
 *
 * If you wish to submit 2D triangles through Submit.csl (i.e you want to
 * do transformation and lighting and whatever else CPU-side - which will
 * of course be very slow) then rather than use this atomic object
 * pipeline, you should use a generic (non-PS2-specific) one.
 *
 * The world plugin must be attached before using this function.
 *
 * The PS2 all-in-one atomic object pipeline:
 * \verbatim
     PS2ObjAllInOne.csl
      v
     PS2MatInstance.csl
      v
     PS2MatBridge.csl
   \endverbatim
 * \return Returns pointer to the pipeline if successful or NULL if there
 * is an error.
 *
 * \see RxNodeDefinitionGetPS2ObjAllInOne
 * \see RxNodeDefinitionGetPS2MatInstance
 * \see RxNodeDefinitionGetPS2MatBridge
 * \see RpAtomicSkyGetPS2AllPipeline
 * \see RpAtomicSkyGetPS2ManagerPipeline
 * \see RpAtomicSkyGetVanillaPipeline
 * \see RpAtomicGetDefaultPipeline
 * \see \ref RpAtomicGetDefaultPipelineplatform
 * \see RpAtomicSetDefaultPipeline
 * \see RpAtomicGetPipeline
 * \see RpAtomicSetPipeline
 * \see RpWorldGetDefaultSectorPipeline
 * \see \ref RpWorldGetDefaultSectorPipelineplatform
 * \see RpWorldSetDefaultSectorPipeline
 * \see RpWorldSectorSkyGetPS2AllPipeline
 * \see RpWorldSectorSkyGetPS2ManagerPipeline
 * \see RpWorldSectorSkyGetAllInOnePipeline
 * \see RpWorldSectorSkyGetVanillaPipeline
 * \see RpWorldGetSectorPipeline
 * \see RpWorldSetSectorPipeline
 * \see RpWorldSectorGetPipeline
 * \see RpWorldSectorSetPipeline
 * \see RpMaterialGetDefaultPipeline
 * \see \ref RpMaterialGetDefaultPipelineplatform
 * \see RpMaterialSetDefaultPipeline
 * \see RpMaterialSkyGetPS2AllMatPipeline
 * \see RpMaterialSkyGetDefaultPS2AllMatPipeline
 * \see RpMaterialSkySetDefaultPS2AllMatPipeline
 * \see RpMaterialGetPipeline
 * \see RpMaterialSetPipeline
 */
RxPipeline *
RpAtomicSkyGetAllInOnePipeline(void)
{
    RWAPIFUNCTION(RWSTRING("RpAtomicSkyGetAllInOnePipeline"));

    RWRETURN(RXPIPELINEGLOBAL(allInOneAtomicPipeline));
}

/**
 * \ingroup rpatomicsky2
 * \ref RpAtomicSkyGetVanillaPipeline
 * returns a pointer to the default atomic Vanilla pipeline.
 *
 * This atomic object pipeline is the 'vanilla' one (i.e no
 * tricks have been used to trade off flexibility and speed - see
 * \ref RpWorldSectorSkyGetPS2ManagerPipeline and
 * \ref RpWorldSectorSkyGetAllInOnePipeline) and it deals with per-object
 * data (transformation matrix, lights, etc) before passing one packet
 * per mesh to RpMaterial-specified material pipelines.
 *
 * By default, atomics are rendered using the PS2All-based pipeline
 * (see \ref RpAtomicSkyGetPS2AllPipeline).
 *
 * If you wish to submit 2D triangles through Submit.csl (i.e you want to
 * do transformation and lighting and whatever else CPU-side - which will
 * of course be very slow) then rather than use this atomic object
 * pipeline, you should use a generic (non-PS2-specific) one.
 *
 * The world plugin must be attached before using this function.
 *
 * The PS2 vanilla atomic object pipeline:
 *
 *  PS2ObjAllInOne.csl
 *
 * \return Returns pointer to the pipeline if successful or NULL if there
 * is an error.
 *
 * \see RxNodeDefinitionGetPS2ObjAllInOne
 * \see RpAtomicSkyGetPS2AllPipeline
 * \see RpAtomicSkyGetPS2ManagerPipeline
 * \see RpAtomicSkyGetAllInOnePipeline
 * \see RpAtomicGetDefaultPipeline
 * \see \ref RpAtomicGetDefaultPipelineplatform
 * \see RpAtomicSetDefaultPipeline
 * \see RpAtomicGetPipeline
 * \see RpAtomicSetPipeline
 * \see RpWorldGetDefaultSectorPipeline
 * \see \ref RpWorldGetDefaultSectorPipelineplatform
 * \see RpWorldSetDefaultSectorPipeline
 * \see RpWorldSectorSkyGetPS2AllPipeline
 * \see RpWorldSectorSkyGetPS2ManagerPipeline
 * \see RpWorldSectorSkyGetAllInOnePipeline
 * \see RpWorldSectorSkyGetVanillaPipeline
 * \see RpWorldGetSectorPipeline
 * \see RpWorldSetSectorPipeline
 * \see RpWorldSectorGetPipeline
 * \see RpWorldSectorSetPipeline
 * \see RpMaterialGetDefaultPipeline
 * \see \ref RpMaterialGetDefaultPipelineplatform
 * \see RpMaterialSetDefaultPipeline
 * \see RpMaterialSkyGetPS2AllMatPipeline
 * \see RpMaterialSkyGetDefaultPS2AllMatPipeline
 * \see RpMaterialSkySetDefaultPS2AllMatPipeline
 * \see RpMaterialGetPipeline
 * \see RpMaterialSetPipeline
 */
RxPipeline *
RpAtomicSkyGetVanillaPipeline(void)
{
    RWAPIFUNCTION(RWSTRING("RpAtomicSkyGetVanillaPipeline"));

    RWRETURN(RXPIPELINEGLOBAL(vanillaAtomicPipeline));
}


/* The two flavours of material pipe
 *  - PS2AllMat, PS2MatInstance->PS2MatBridge */
static RxPipeline *
CreatePS2AllMatMaterialPipeline(void)
{
    RxPipeline         *pipe;

    RWFUNCTION(RWSTRING("CreatePS2AllMatMaterialPipeline"));

    pipe = RxPipelineCreate();
    if (pipe)
    {
        RxLockedPipe       *lpipe;

        lpipe = RxPipelineLock(pipe);

        if (NULL != lpipe)
        {
            RxNodeDefinition   *ps2allmat = RxNodeDefinitionGetPS2AllMat();
            RxPipelineNode     *plnode, *result;

            lpipe = RxLockedPipeAddFragment(lpipe,
                                            (RwUInt32 *)NULL,
                                            ps2allmat,
                                            (RxNodeDefinition *)NULL);

            RWASSERT(lpipe != NULL);

            plnode = RxPipelineFindNodeByName(lpipe, 
                                              ps2allmat->name, 
                                              (RxPipelineNode *)NULL, 
                                              (RwInt32 *)NULL);
            RWASSERT(plnode != NULL);

            /* get manager node to generate cluster "xyz" */
            result = RxPipelineNodePS2AllMatGenerateCluster(
                         plnode, &RxClPS2xyz, CL_XYZ);
            RWASSERT(result != NULL);

            /* get manager node to generate cluster "uv" */
            result = RxPipelineNodePS2AllMatGenerateCluster(
                         plnode, &RxClPS2uv, CL_UV);
            RWASSERT(result != NULL);

            /* get manager node to generate cluster "rgba" */
            result = RxPipelineNodePS2AllMatGenerateCluster(
                         plnode, &RxClPS2rgba, CL_RGBA);
            RWASSERT(result != NULL);

            /* get manager node to generate cluster "normal" */
            result = RxPipelineNodePS2AllMatGenerateCluster(
                          plnode, &RxClPS2normal, CL_NORMAL);
            RWASSERT(result != NULL);


            /* set ps2all buffer sizes */
            result = RxPipelineNodePS2AllMatSetVUBufferSizes(plnode,
                                              _rwskyStrideOfInputCluster,
                                              _rwskyTSVertexCount,
                                              _rwskyTLTriCount);
            RWASSERT(result != NULL);

            lpipe = RxLockedPipeUnlock(lpipe);

            if (lpipe != NULL)
            {
                plnode = RxPipelineFindNodeByName(lpipe,
                                                  ps2allmat->name, 
                                                  (RxPipelineNode *)NULL, 
                                                  (RwInt32 *)NULL);
                RWASSERT(plnode!=NULL);

                plnode = RxPipelineNodePS2AllMatSetVIFOffset(plnode,
                                                 _rwskyVIFOffset);
                RWASSERT(NULL != plnode);

                /* Default VU1 code array is OK */

                /* Set up the necessary callbacks */
                RxPipelineNodePS2AllMatSetCallBack(
                    plnode, rxPS2ALLMATCALLBACKIM3DPREMESH,
                    NULL);
                RxPipelineNodePS2AllMatSetCallBack(
                    plnode, rxPS2ALLMATCALLBACKMESHINSTANCETEST,
                    RpMeshPS2AllMeshInstanceTestCallBack);
                RxPipelineNodePS2AllMatSetCallBack(
                    plnode, rxPS2ALLMATCALLBACKRESENTRYALLOC,
                    RpMeshPS2AllResEntryAllocCallBack);
                RxPipelineNodePS2AllMatSetCallBack(
                    plnode, rxPS2ALLMATCALLBACKINSTANCE,
                    RpMeshPS2AllInstanceCallBack);
                RxPipelineNodePS2AllMatSetCallBack(
                    plnode, rxPS2ALLMATCALLBACKBRIDGE,
                    RpMeshPS2AllBridgeCallBack);
                RxPipelineNodePS2AllMatSetCallBack(
                    plnode, rxPS2ALLMATCALLBACKIM3DPOSTMESH,
                    NULL);
                RxPipelineNodePS2AllMatSetCallBack(
                    plnode, rxPS2ALLMATCALLBACKIM3DMESHSPLIT,
                    NULL);
#if (defined(RWMETRICS))
                RxPipelineNodePS2AllMatSetCallBack(
                    plnode, rxPS2ALLMATCALLBACKPOSTMESH,
                    RpMeshPS2AllPostMeshCallBack);
#endif /* (defined(RWMETRICS)) */

                RWRETURN(pipe);
            }
        }

        RxPipelineDestroy(pipe);
    }

    RWERROR((E_RW_DEFAULTPIPELINECREATION, "PS2AllMatMaterial"));

    RWRETURN((RxPipeline *)NULL);
}

static RxPipeline *
CreateVanillaMaterialPipeline(void)
{
    RxPipeline         *pipe;

    RWFUNCTION(RWSTRING("CreateVanillaMaterialPipeline"));

    pipe = RxPipelineCreate();
    if (pipe)
    {
        RxLockedPipe       *lpipe;

        lpipe = RxPipelineLock(pipe);

        if (NULL != lpipe)
        {
            RxNodeDefinition   *instance =
                RxNodeDefinitionGetPS2MatInstance();
            RxNodeDefinition   *bridge =
                RxNodeDefinitionGetPS2MatBridge();
            RxPipelineNode     *plnode;
            RxPipelineNode     *result;

            lpipe = RxLockedPipeAddFragment(lpipe,
                                            (RwUInt32 *)NULL,
                                            instance,
                                            bridge,
                                            (RxNodeDefinition *)NULL);

            /* get instance node to generate cluster "xyz" */
            plnode = RxPipelineFindNodeByName(lpipe,
                                              instance->name,
                                              (RxPipelineNode *)NULL,
                                              (RwInt32 *)NULL);
            RWASSERT(plnode != NULL);
            result = RxPipelineNodePS2MatInstanceGenerateCluster(
                         plnode, &RxClPS2xyz, CL_XYZ);
            RWASSERT(result != NULL);

            /* get instance node to generate cluster "uv" */
            result = RxPipelineNodePS2MatInstanceGenerateCluster(
                         plnode, &RxClPS2uv, CL_UV);
            RWASSERT(result != NULL);

            /* get instance node to generate cluster "rgba" */
            result = RxPipelineNodePS2MatInstanceGenerateCluster(
                         plnode, &RxClPS2rgba, CL_RGBA);
            RWASSERT(result != NULL);

            /* get instance node to generate cluster "normal" */
            result = RxPipelineNodePS2MatInstanceGenerateCluster(
                         plnode, &RxClPS2normal, CL_NORMAL);
            RWASSERT(result != NULL);

#if 0
            /* Example for adding user clusters; get instance
             * node to generate cluster "user1": */
            result = RxPipelineNodePS2MatInstanceGenerateCluster(
                         plnode, &clusterPS2user1, CL_USER1);
            RWASSERT(result != NULL);
#endif

            RxPipelineNodePS2MatInstanceNodeSetVUBufferSizes(plnode,
                                         _rwskyStrideOfInputCluster,
                                         _rwskyTSVertexCount,
                                         _rwskyTLTriCount);

            lpipe = RxLockedPipeUnlock(lpipe);

            RWASSERT(pipe == (RxPipeline *)lpipe);

            if (lpipe != NULL)
            {
                plnode = RxPipelineFindNodeByName(lpipe,
                                                  bridge->name,
                                                  (RxPipelineNode *)NULL,
                                                  (RwInt32 *)NULL);
                RWASSERT(plnode!=NULL);

                RxBridgeNodeSetVIFOffset(plnode,_rwskyVIFOffset);


                RWRETURN(pipe);
            }
        }

        RxPipelineDestroy(pipe);
    }

    RWERROR((E_RW_DEFAULTPIPELINECREATION, "VanillaMaterial"));

    RWRETURN((RxPipeline *)NULL);
}

/**
 * \ingroup rpmaterialsky2
 * \ref RpMaterialSkyGetPS2AllMatPipeline
 * returns a pointer to the standard material PS2AllMat pipeline.
 *
 * PS2AllMat.csl is the material pipeline node counterpart
 * to (the object pipeline node) PS2All.csl. Objects for
 * which this material pipeline is used must have their instance
 * pipeline constructed from PS2All.csl (see
 * \ref RxNodeDefinitionGetPS2All and \ref RxNodeDefinitionGetPS2AllMat
 * for details).
 *
 * Note that you may write your own VU1 code and attach it to a material
 * pipeline so that you can perform whatever special effects you like on
 * VU1 (see the PS2AllMat.csl API for details).
 *
 * By default, materials are rendered using this PS2AllMat-based pipeline.
 *
 * If you wish to submit 2D triangles through Submit.csl (i.e you want to
 * do transformation and lighting and whatever else CPU-side - which will
 * of course be very slow) then rather than use this material pipeline,
 * you should use a generic (non-PS2-specific) one.
 *
 * The world plugin must be attached before using this function.
 *
 * The PS2AllMat material pipeline:
 *
 *   PS2Mat.csl
 *
 * \return Returns pointer to the pipeline if successful or NULL if there
 * is an error.
 *
 * \see RxNodeDefinitionGetPS2AllMat
 * \see RxNodeDefinitionGetPS2All
 * \see RpMaterialGetDefaultPipeline
 * \see RpMaterialSetDefaultPipeline
 * \see RpMaterialSkyGetDefaultPS2AllMatPipeline
 * \see RpMaterialSkySetDefaultPS2AllMatPipeline
 * \see RpMaterialGetPipeline
 * \see RpMaterialSetPipeline
 * \see RpAtomicGetDefaultPipeline
 * \see \ref RpAtomicGetDefaultPipelineplatform
 * \see RpAtomicSetDefaultPipeline
 * \see RpAtomicSkyGetPS2AllPipeline
 * \see RpAtomicSkyGetPS2ManagerPipeline
 * \see RpAtomicSkyGetAllInOnePipeline
 * \see RpAtomicSkyGetVanillaPipeline
 * \see RpAtomicGetPipeline
 * \see RpAtomicSetPipeline
 * \see RpWorldGetDefaultSectorPipeline
 * \see \ref RpWorldGetDefaultSectorPipelineplatform
 * \see RpWorldSetDefaultSectorPipeline
 * \see RpWorldSectorSkyGetPS2AllPipeline
 * \see RpWorldSectorSkyGetPS2ManagerPipeline
 * \see RpWorldSectorSkyGetAllInOnePipeline
 * \see RpWorldSectorSkyGetVanillaPipeline
 * \see RpWorldGetSectorPipeline
 * \see RpWorldSetSectorPipeline
 * \see RpWorldSectorGetPipeline
 * \see RpWorldSectorSetPipeline
 */
RxPipeline *
RpMaterialSkyGetPS2AllMatPipeline(void)
{
    RWAPIFUNCTION(RWSTRING("RpMaterialSkyGetPS2AllMatPipeline"));

    RWRETURN(RXPIPELINEGLOBAL(platformMaterialPipeline));
}

/**
 * \ingroup rpmaterialsky2
 * \ref RpMaterialSkyGetVanillaPipeline
 * returns a pointer to the standard vanilla PS2 material pipeline.
 *
 * The vanilla material pipeline for PS2 is shown below.
 * Note that this is designed specifically to use VU1 to perform
 * transformation and lighting (it passes 3D triangles, through
 * PS2MatBridge.csl, to VU1). If you wish to submit 2D triangles through
 * Submit.csl (i.e you want to do transformation and lighting and whatever
 * else CPU-side - which will of course be very slow) then you should
 * replace this default material pipeline with something based on the
 * generic material pipeline. You should also replace the associated
 * world-sector/atomic object pipelines, which by default create
 * data in a PS2-specific format appropriate for this material pipeline,
 * with something based on the generic object pipelines.
 *
 * Note that you may write your own VU1 code and attach it to a material
 * pipeline so that you can perform whatever special effects you like on
 * VU1 (see the PS2MatBridge.csl API for details).
 *
 * By default, materials are rendered using the PS2AllMat-based pipeline
 * (see \ref RpMaterialSkyGetPS2AllMatPipeline). A vanilla material
 * pipeline *cannot* be used with a PS2All-based object pipeline, it
 * should be paired with a vanilla object pipeline (see
 * \ref RpAtomicSkyGetVanillaPipeline or
 * \ref RpWorldSectorSkyGetVanillaPipeline).
 *
 * If you wish to submit 2D triangles through Submit.csl (i.e you want to
 * do transformation and lighting and whatever else CPU-side - which will
 * of course be very slow) then rather than use this material pipeline,
 * you should use a generic (non-PS2-specific) one.
 *
 * The world plugin must be attached before using this function.
 *
 * The standard vanilla PS2 material pipeline:
 * \verbatim
     PS2MatInstance.csl
      v
     PS2MatBridge.csl
   \endverbatim
 *
 * \return Returns pointer to the pipeline if successful or NULL if there
 * is an error.
 *
 * \see RxNodeDefinitionGetPS2MatInstance
 * \see RxNodeDefinitionGetPS2MatBridge
 * \see RpMaterialGetDefaultPipeline
 * \see RpMaterialSetDefaultPipeline
 * \see RpMaterialSkyGetDefaultPS2AllMatPipeline
 * \see RpMaterialSkySetDefaultPS2AllMatPipeline
 * \see RpMaterialGetPipeline
 * \see RpMaterialSetPipeline
 * \see RpAtomicGetDefaultPipeline
 * \see \ref RpAtomicGetDefaultPipelineplatform
 * \see RpAtomicSetDefaultPipeline
 * \see RpAtomicSkyGetPS2AllPipeline
 * \see RpAtomicSkyGetPS2ManagerPipeline
 * \see RpAtomicSkyGetAllInOnePipeline
 * \see RpAtomicSkyGetVanillaPipeline
 * \see RpAtomicGetPipeline
 * \see RpAtomicSetPipeline
 * \see RpWorldGetDefaultSectorPipeline
 * \see \ref RpWorldGetDefaultSectorPipelineplatform
 * \see RpWorldSetDefaultSectorPipeline
 * \see RpWorldSectorSkyGetPS2AllPipeline
 * \see RpWorldSectorSkyGetPS2ManagerPipeline
 * \see RpWorldSectorSkyGetAllInOnePipeline
 * \see RpWorldSectorSkyGetVanillaPipeline
 * \see RpWorldGetSectorPipeline
 * \see RpWorldSetSectorPipeline
 * \see RpWorldSectorGetPipeline
 * \see RpWorldSectorSetPipeline
 */
RxPipeline *
RpMaterialSkyGetVanillaPipeline(void)
{
    RWAPIFUNCTION(RWSTRING("RpMaterialSkyGetVanillaPipeline"));

    RWRETURN(RXPIPELINEGLOBAL(vanillaMaterialPipeline));
}

/**
 * \ingroup rpmaterialsky2
 * \page RpMaterialGetDefaultPipelineplatform RpMaterialGetDefaultPipeline (platform-specific)
 * returns a pointer to the current default RpMaterial pipeline.
 *
 * The default material pipeline for PS2 is shown below. Note that
 * this is designed specifically to use VU1 to perform transformation
 * and lighting (it passes 3D triangles to VU1). If you wish to submit
 * 2D triangles through Submit.csl (i.e you want to do transformation
 * and lighting and whatever else CPU-side - which will of course be
 * very slow) then you should replace this default material pipeline
 * with something based on the generic material pipeline. You should
 * also replace the associated world-sector/atomic object pipelines,
 * which by default create data in a PS2-specific format appropriate
 * for this material pipeline, with something based on the generic
 * object pipelines.
 *
 * Currently, the default atomic/world-sector object pipelines use
 * the PS2All.csl node, which renders objects using just that node
 * (for speed). PS2All-based object pipelines will only validly
 * connect to PS2AllMat-based material pipelines and vice versa.
 * See
 * \ref RpAtomicSkyGetPS2AllPipeline,
 * \ref RpWorldSectorSkyGetPS2AllPipeline and
 * \ref RpMaterialSkyGetPS2AllMatPipeline for further details.
 *
 * Note that you may write your own VU1 code and attach it to a material
 * pipeline so that you can perform whatever special effects you like on
 * VU1 (see the PS2AllMat.csl API for details).
 *
 * The world plugin must be attached before using this function.
 *
 * The default material pipeline:
 *
 *   PS2AllMat.csl
 *
 * \return Returns pointer to the pipeline if successful or NULL if there
 * is an error.
 *
 * \see RxNodeDefinitionGetPS2All
 * \see RxNodeDefinitionGetPS2AllMat
 * \see RpMaterialGetDefaultPipeline
 * \see RpMaterialSetDefaultPipeline
 * \see RpMaterialSkyGetPS2AllMatPipeline
 * \see RpMaterialSkyGetDefaultPS2AllMatPipeline
 * \see RpMaterialSkySetDefaultPS2AllMatPipeline
 * \see RpMaterialGetPipeline
 * \see RpMaterialSetPipeline
 * \see RpAtomicGetDefaultPipeline
 * \see \ref RpAtomicGetDefaultPipelineplatform
 * \see RpAtomicSetDefaultPipeline
 * \see RpAtomicSkyGetPS2AllPipeline
 * \see RpAtomicSkyGetPS2ManagerPipeline
 * \see RpAtomicSkyGetAllInOnePipeline
 * \see RpAtomicSkyGetVanillaPipeline
 * \see RpAtomicGetPipeline
 * \see RpAtomicSetPipeline
 * \see RpWorldGetDefaultSectorPipeline
 * \see \ref RpWorldGetDefaultSectorPipelineplatform
 * \see RpWorldSetDefaultSectorPipeline
 * \see RpWorldSectorSkyGetPS2AllPipeline
 * \see RpWorldSectorSkyGetPS2ManagerPipeline
 * \see RpWorldSectorSkyGetAllInOnePipeline
 * \see RpWorldSectorSkyGetVanillaPipeline
 * \see RpWorldGetSectorPipeline
 * \see RpWorldSetSectorPipeline
 * \see RpWorldSectorGetPipeline
 * \see RpWorldSectorSetPipeline
 */
void
_rpDestroyPlatformMaterialPipelines(void)
{
    RWFUNCTION(RWSTRING("_rpDestroyPlatformMaterialPipelines"));

    /* Restore the generic pipe as default */
    RpMaterialSetDefaultPipeline((RxPipeline *)NULL);

    if (NULL != RXPIPELINEGLOBAL(platformMaterialPipeline))
    {
        RxPipelineDestroy(RXPIPELINEGLOBAL(platformMaterialPipeline));
        RXPIPELINEGLOBAL(platformMaterialPipeline) = (RxPipeline *)NULL;
    }

    if (NULL != RXPIPELINEGLOBAL(vanillaMaterialPipeline))
    {
        RxPipelineDestroy(RXPIPELINEGLOBAL(vanillaMaterialPipeline));
        RXPIPELINEGLOBAL(vanillaMaterialPipeline) = (RxPipeline *)NULL;
    }

    RWRETURNVOID();
}

RwBool
_rpCreatePlatformMaterialPipelines(void)
{
    RWFUNCTION(RWSTRING("_rpCreatePlatformMaterialPipelines"));

    /* NOTE: The default material pipeline is always the same, but if the
     *       PS2Manager or AllInOne atomic/worldsector object pipelines
     *       are used then they will ignore material pipelines. To use
     *       overloaded material pipelines under these circumstances,
     *       you must either incorporate the material pipeline into a
     *       AllInOne-style object pipe, or replace the instance
     *       pipe with with a non-AllInOne and non-PS2Manager one.
     *       Our final paradigm will be:
     *           all pipelines are material pipelines, which act on lists
     *           of meshes. Each mesh can have (standard/plugin) data
     *           (e.g texture(s)) to differentiate it from other meshes
     *           rendered with the same pipeline. Atomics/WorldSectors
     *           have render callbacks that group meshes together based
     *           on their material pipelines (and pass in to the pipe a
     *           pointer to the parent object). */

    if ((RXPIPELINEGLOBAL(vanillaMaterialPipeline) =
             CreateVanillaMaterialPipeline()) != NULL)
    {
        if ((RXPIPELINEGLOBAL(platformMaterialPipeline) =
                 CreatePS2AllMatMaterialPipeline()) != NULL)
        {
            RpMaterialSetDefaultPipeline(
                RXPIPELINEGLOBAL(platformMaterialPipeline));

            RWRETURN(TRUE);
        }
    }

    /* This should clean up safely */
    _rpDestroyPlatformMaterialPipelines();

    RWERROR((E_RW_DEFAULTPIPELINECREATION, "PS2 material pipes"));

    RWRETURN(FALSE);
}

/**
 * \ingroup rpworldsubsky2
 * \page RpWorldGetDefaultSectorPipelineplatform RpWorldGetDefaultSectorPipeline (platform-specific)
 * returns a pointer to the current default RpWorldSector pipeline.
 *
 * Currently, the default default (yes, that's right :) )
 * RpWorldSector pipeline for PS2 uses the PS2All.csl node
 * for speed - see
 * \ref RpWorldSectorSkyGetPS2AllPipeline and
 * \ref RpMaterialSkyGetPS2AllMatPipeline for details.
 *
 * The world plugin must be attached before using this function.
 *
 * \return Returns pointer to the pipeline if successful or NULL if there
 * is an error.
 *
 * \see RpWorldGetDefaultSectorPipeline
 * \see RpWorldSetDefaultSectorPipeline
 * \see RpWorldSectorSkyGetPS2AllPipeline
 * \see RpWorldSectorSkyGetPS2ManagerPipeline
 * \see RpWorldSectorSkyGetAllInOnePipeline
 * \see RpWorldSectorSkyGetVanillaPipeline
 * \see RpWorldGetSectorPipeline
 * \see RpWorldSetSectorPipeline
 * \see RpWorldSectorGetPipeline
 * \see RpWorldSectorSetPipeline
 * \see RpAtomicGetDefaultPipeline
 * \see \ref RpAtomicGetDefaultPipelineplatform
 * \see RpAtomicSetDefaultPipeline
 * \see RpAtomicSkyGetPS2AllPipeline
 * \see RpAtomicSkyGetPS2ManagerPipeline
 * \see RpAtomicSkyGetAllInOnePipeline
 * \see RpAtomicSkyGetVanillaPipeline
 * \see RpAtomicGetPipeline
 * \see RpAtomicSetPipeline
 * \see RpMaterialGetDefaultPipeline
 * \see \ref RpMaterialGetDefaultPipelineplatform
 * \see RpMaterialSetDefaultPipeline
 * \see RpMaterialSkyGetPS2AllMatPipeline
 * \see RpMaterialSkyGetDefaultPS2AllMatPipeline
 * \see RpMaterialSkySetDefaultPS2AllMatPipeline
 * \see RpMaterialGetPipeline
 * \see RpMaterialSetPipeline
 */
void
_rpDestroyPlatformWorldSectorPipelines(void)
{
    RWFUNCTION(RWSTRING("_rpDestroyPlatformWorldSectorPipelines"));

    /* Restore the generic pipe as default */
    RpWorldSetDefaultSectorPipeline((RxPipeline *)NULL);

    if (NULL != RXPIPELINEGLOBAL(platformWorldSectorPipeline))
    {
        RxPipelineDestroy(RXPIPELINEGLOBAL(platformWorldSectorPipeline));
        RXPIPELINEGLOBAL(platformWorldSectorPipeline) = (RxPipeline *)NULL;
    }

    if (NULL != RXPIPELINEGLOBAL(ps2ManagerWorldSectorPipeline))
    {
        RxPipelineDestroy(RXPIPELINEGLOBAL(ps2ManagerWorldSectorPipeline));
        RXPIPELINEGLOBAL(ps2ManagerWorldSectorPipeline) = (RxPipeline *)NULL;
    }

    if (NULL != RXPIPELINEGLOBAL(allInOneWorldSectorPipeline))
    {
        RxPipelineDestroy(RXPIPELINEGLOBAL(allInOneWorldSectorPipeline));
        RXPIPELINEGLOBAL(allInOneWorldSectorPipeline)= (RxPipeline *)NULL;
    }

    if (NULL != RXPIPELINEGLOBAL(vanillaWorldSectorPipeline))
    {
        RxPipelineDestroy(RXPIPELINEGLOBAL(vanillaWorldSectorPipeline));
        RXPIPELINEGLOBAL(vanillaWorldSectorPipeline)= (RxPipeline *)NULL;
    }

    RWRETURNVOID();
}

RwBool
_rpCreatePlatformWorldSectorPipelines(void)
{
    RWFUNCTION(RWSTRING("_rpCreatePlatformWorldSectorPipelines"));

    if ((RXPIPELINEGLOBAL(vanillaWorldSectorPipeline) =
             CreateVanillaWorldSectorPipeline()) != NULL)
    {
        if ((RXPIPELINEGLOBAL(allInOneWorldSectorPipeline) =
                 CreateAllInOneWorldSectorPipeline()) != NULL)
        {
            if ((RXPIPELINEGLOBAL(ps2ManagerWorldSectorPipeline) =
                     CreatePS2ManagerWorldSectorPipeline()) != NULL)
            {
                if ((RXPIPELINEGLOBAL(platformWorldSectorPipeline) =
                         CreatePS2AllWorldSectorPipeline()) != NULL)
                {
                    RpWorldSetDefaultSectorPipeline(
                        RXPIPELINEGLOBAL(platformWorldSectorPipeline));

                    RWRETURN(TRUE);
                }
            }
        }
    }

    /* This should clean up safely */
    _rpDestroyPlatformWorldSectorPipelines();

    RWERROR((E_RW_DEFAULTPIPELINECREATION, "PS2 sector pipes"));

    RWRETURN(FALSE);
}

/**
 * \ingroup rpatomicsky2
 * \page RpAtomicGetDefaultPipelineplatform RpAtomicGetDefaultPipeline (platform-specific)
 * returns a pointer to the current default RpAtomic pipeline.
 *
 * Currently, the default default (yes, that's right :) )
 * RpAtomic pipeline for PS2 uses the PS2All.csl node
 * for speed - see
 * \ref RpAtomicSkyGetPS2AllPipeline and
 * \ref RpMaterialSkyGetPS2AllMatPipeline for details.
 *
 * The world plugin must be attached before using this function.
 *
 * \return Returns pointer to the pipeline if successful or NULL if there
 * is an error.
 *
 * \see RpAtomicGetDefaultPipeline
 * \see RpAtomicSetDefaultPipeline
 * \see RpAtomicSkyGetPS2AllPipeline
 * \see RpAtomicSkyGetPS2ManagerPipeline
 * \see RpAtomicSkyGetAllInOnePipeline
 * \see RpAtomicSkyGetVanillaPipeline
 * \see RpAtomicGetPipeline
 * \see RpAtomicSetPipeline
 * \see RpMaterialGetDefaultPipeline
 * \see \ref RpMaterialGetDefaultPipelineplatform
 * \see RpMaterialSetDefaultPipeline
 * \see RpMaterialSkyGetPS2AllMatPipeline
 * \see RpMaterialSkyGetDefaultPS2AllMatPipeline
 * \see RpMaterialSkySetDefaultPS2AllMatPipeline
 * \see RpMaterialGetPipeline
 * \see RpMaterialSetPipeline
 * \see RpWorldGetDefaultSectorPipeline
 * \see \ref RpWorldGetDefaultSectorPipelineplatform
 * \see RpWorldSetDefaultSectorPipeline
 * \see RpWorldSectorSkyGetPS2AllPipeline
 * \see RpWorldSectorSkyGetPS2ManagerPipeline
 * \see RpWorldSectorSkyGetAllInOnePipeline
 * \see RpWorldSectorSkyGetVanillaPipeline
 * \see RpWorldGetSectorPipeline
 * \see RpWorldSetSectorPipeline
 * \see RpWorldSectorGetPipeline
 * \see RpWorldSectorSetPipeline
 */
void
_rpDestroyPlatformAtomicPipelines(void)
{
    RWFUNCTION(RWSTRING("_rpDestroyPlatformAtomicPipelines"));

    /* Restore the generic pipe as default */
    RpAtomicSetDefaultPipeline((RxPipeline *)NULL);

    if (NULL != RXPIPELINEGLOBAL(platformAtomicPipeline))
    {
        RxPipelineDestroy(RXPIPELINEGLOBAL(platformAtomicPipeline));
        RXPIPELINEGLOBAL(platformAtomicPipeline) = (RxPipeline *)NULL;
    }

    if (NULL != RXPIPELINEGLOBAL(ps2ManagerAtomicPipeline))
    {
        RxPipelineDestroy(RXPIPELINEGLOBAL(ps2ManagerAtomicPipeline));
        RXPIPELINEGLOBAL(ps2ManagerAtomicPipeline) = (RxPipeline *)NULL;
    }

    if (NULL != RXPIPELINEGLOBAL(allInOneAtomicPipeline))
    {
        RxPipelineDestroy(RXPIPELINEGLOBAL(allInOneAtomicPipeline));
        RXPIPELINEGLOBAL(allInOneAtomicPipeline)= (RxPipeline *)NULL;
    }

    if (NULL != RXPIPELINEGLOBAL(vanillaAtomicPipeline))
    {
        RxPipelineDestroy(RXPIPELINEGLOBAL(vanillaAtomicPipeline));
        RXPIPELINEGLOBAL(vanillaAtomicPipeline)= (RxPipeline *)NULL;
    }

    RWRETURNVOID();
}

RwBool
_rpCreatePlatformAtomicPipelines(void)
{
    RWFUNCTION(RWSTRING("_rpCreatePlatformAtomicPipelines"));

    if ((RXPIPELINEGLOBAL(vanillaAtomicPipeline) =
             CreateVanillaAtomicPipeline()) != NULL)
    {
        if ((RXPIPELINEGLOBAL(allInOneAtomicPipeline) =
                 CreateAllInOneAtomicPipeline()) != NULL)
        {
            if ((RXPIPELINEGLOBAL(ps2ManagerAtomicPipeline) =
                     CreatePS2ManagerAtomicPipeline()) != NULL)
            {
                if ((RXPIPELINEGLOBAL(platformAtomicPipeline) =
                         CreatePS2AllAtomicPipeline()) != NULL)
                {
                    RpAtomicSetDefaultPipeline(
                        RXPIPELINEGLOBAL(platformAtomicPipeline));

                    RWRETURN(TRUE);
                }
            }
        }
    }

    /* This should safely clean up */
    _rpDestroyPlatformAtomicPipelines();

    RWERROR((E_RW_DEFAULTPIPELINECREATION, "PS2 stomic pipes"));

    RWRETURN(FALSE);
}

