/* *INDENT-OFF* */

/*
 * nodePS2All
 * A PS2-specific node to render complete objects in one go 
 * 
 * Copyright (c) Criterion Software Limited
 */

/****************************************************************************
 *                                                                          *
 * module : nodePS2All.c                                                    *
 *                                                                          *
 * purpose: A node to fulfill all needs on PS2 (hence 'all')                *
 *                                                                          *
 ****************************************************************************/

/*
#### SYNCHRONISATION
####
#### UP TO DATE WITH VERSION 1.76 OF nodePS2Manager.c
#### UP TO DATE WITH VERSION 1.86 OF nodePS2ObjAllInOne.c
#### UP TO DATE WITH VERSION 1.174 OF nodePS2MatInstance.c
#### UP TO DATE WITH VERSION 1.113 OF nodePS2MatBridge.c
####
#### SYNCHRONISATION
*/

/* TODO CLASSIFICATION SYSTEM:
 o "TODO[1]": stuff to do now to get it to compile/work in the first pass
 o "TODO[2]": not needed to work(1) but TBD before moving on to flexibility(4)
 o "TODO[3]": questions to be considered and deleted or made into a proper TODO
 o "TODO[4]": stuff that is needed for it to be flexible, details to be finalised later
 o "TODO[5]": serious optimisations (memory/speed)
 o "TODO[6]": not *critical*, but I'd really like it sorted before PS2All is released
 o "TODO[7]": non-PS2All-specific stuff that needs to be done (like the Im3D splitting mess)
 o "TODO[8]": stuff that might be nice to do but could safely be left by the wayside
 o "TODO[9]": just comments to be removed once PS2All works or code to otherwise tidy
 o "TODOCS":  docs to be written once it's going in to the SDK or when I change
              functionality/can be bothered
 */

//TODO[6]: REMOVE ALL PLATFORM-SPECIFIC TYPES (int, etc) AND REPLACE WITH RW TYPES (DO WE HAVE RwUInt128 TO USE?)

//TODO[6]: INLINE OBJALLINONE, MATINSTANCE & MATBRIDGE CODE IN HERE

//TODO[3]: PVS... CAN PROBABLY BE PUT IN ONE OF THE CBs IF WE CHAIN THEM? (CHAINING???)

//TODO[6][9]: RENAME THIS FILE TO BE ALL LOWER-CASE

/****************************************************************************
 includes
 */

#include "rwcore.h"

#include "bapipew.h"
#include "wrldpipe.h"

#include "matputil.h"

#include "objallinone.h"
#include "matinstance.h"
#include "matbridge.h"
#include "ps2allim3d.h"
#include "ps2allsector.h"
#include "ps2alldbg.h"

#include "ps2allmat.h"
#include "nodePS2All.h"


/****************************************************************************
 Local defines
 */

#define PRIVATEDATATYPE rxNodePS2AllPvtData

#define MESSAGE(_string) \
    RwDebugSendMessage(rwDEBUGMESSAGE, "PS2All.csl", (_string))

#define PRIMTYPEMAX \
    (rwPRIMTYPELINELIST | rwPRIMTYPEPOLYLINE | rwPRIMTYPETRILIST | \
     rwPRIMTYPETRISTRIP | rwPRIMTYPETRIFAN   | rwPRIMTYPEPOINTLIST)


/****************************************************************************
 Global variables
*/

#if (defined(GSB))
extern unsigned long skyGCVTValue;
extern RwInt32 skyNumSeqFrms;
extern RwInt32 skyRenderSlot;
#endif /* (defined(GSB)) */

extern const void  *skyUploadedCode;
extern void        *skyVU1Transforms[VU1CODEARRAYSIZE];

/* From objallinone.c */
extern RwReal * _skyLightFillPos;
extern RwUInt32 _skyLightQWordsWritten;
extern u_long128 lightBuffer[];


/****************************************************************************
 global prototypes
 */

/* steal this from basky.c */
extern RwBool _rwSkySetRenderState(RwRenderState nState, void *param);


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

   Functions

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


/****************************************************************************
 PS2AllPipelineNodeInit

 Defaults to setting up CBs for sectors.
 All per-mesh CBs are shared between sectors and atomics.
*/
static RwBool
PS2AllPipelineNodeInit(RxPipelineNode *self)
{
    PRIVATEDATATYPE *pvtData;

    RWFUNCTION(RWSTRING("PS2AllPipelineNodeInit"));

    pvtData = (PRIVATEDATATYPE *)self->privateData;
    RWASSERT(NULL != pvtData);

    /* Sets the node to not group meshes by default (so they all get
     * sent to the pipeline specified in their associated material). */
    pvtData->groupPipe = NULL;

    /* Initialize CBs to NULL so people have to set theirs explicitly */
    pvtData->objectSetupCB    = NULL;
    pvtData->objectFinalizeCB = NULL;

    /* Set up LUTs to deal with the different primtypes */
    pvtData->primLUT.vertToIndRatio[rwPRIMTYPELINELIST ] = 1;
    pvtData->primLUT.vertToIndRatio[rwPRIMTYPEPOLYLINE ] = 2;
    pvtData->primLUT.vertToIndRatio[rwPRIMTYPETRILIST  ] = 1;
    pvtData->primLUT.vertToIndRatio[rwPRIMTYPETRISTRIP ] = 1;
    pvtData->primLUT.vertToIndRatio[rwPRIMTYPETRIFAN   ] = 3;
    pvtData->primLUT.vertToIndRatio[rwPRIMTYPEPOINTLIST] = 1;

    pvtData->primLUT.vertToIndOffset[rwPRIMTYPELINELIST ] = 0;
    pvtData->primLUT.vertToIndOffset[rwPRIMTYPEPOLYLINE ] = 1;
    pvtData->primLUT.vertToIndOffset[rwPRIMTYPETRILIST  ] = 0;
    pvtData->primLUT.vertToIndOffset[rwPRIMTYPETRISTRIP ] = 2;
    pvtData->primLUT.vertToIndOffset[rwPRIMTYPETRIFAN   ] = 2;
    pvtData->primLUT.vertToIndOffset[rwPRIMTYPEPOINTLIST] = 0;

    RWRETURN(TRUE);
}

/****************************************************************************
 PS2All
 */
static RwBool
PS2AllNodeBody(RxPipelineNodeInstance *self,
               const RxPipelineNodeParam * params)
{
    PRIVATEDATATYPE        *pvtData;
//TODO[3]: MAKE ps2AllPipeData STATIC SO STATE THINGS LIKE matModulate PERSIST?
    RxPS2AllPipeData        ps2AllPipeData;
    RxPipeline             *defaultMatPipe;
//static RxPipeline *defaultMatPipe = NULL;
    RwReal                  blankExtra = 0;
    RwUInt32                blankIdentifier = 0;
    RwUInt8                 blankTransType = 0; /* NoFog|NoClip|Strip|Persp|Tri|NoCull */
    RwUInt8                 blankMatModulate = FALSE; /* Default assumes white materials */
    RwUInt8                 blankPrimType = -1;
    RpMeshHeader           *blankMeshHeader = (RpMeshHeader *)NULL;
    RwMeshCache            *blankMeshCache = (RwMeshCache *)NULL;
    void                   *data;

    rxNodePS2AllMatPvtData *curMatPvtData;
    RxPipeline             *curMatPipe;
    RpMeshHeader           *meshHeader = NULL;
    RwMeshCache            *meshCache  = NULL;
    const RpMesh           *mesh;
    RwUInt32                n;
    RwBool                  success;

    RWFUNCTION(RWSTRING("PS2AllNodeBody"));

/*TODO[4]:
IF IM3D CAN'T PASS PIPE-CREATED DATA BETWEEN CALLBACKS THEN MIGHT OTHER CALLBACKS HAVE THAT TROUBLE?
 - HEHE, THAT'S WHAT POWERPIPE WAS GOOD AT... *SIGH* THE IRONY WILL LAST FOR ALMOST AS LONG AS THE PAIN...
 - MORPHTARGET 'SCALE' VALUE, AN OBVIOUS EXAMPLE... PER-OBJECT-CALC'D VALUES REQUIRED FOR ALL MESH UPLOADS
 - WANT STATIC DATA RATHER THAN FORCING USING RxHeapAlloc, MAKE PART OF THE CONTRUCTION-TIME API.
   HOW TO GET THE OFFSET VALUES BACK INTO THE PLUGIN CALLBACKS? LEAVE IT AS THE APP'S PROBLEM? WELL, NO...
   COS THE SAME CALLBACK MUST BE USABLE FOR MULTIPLE PIPES... THEY CAN'T HAVE A UNIQUE SLOT EACH :O/
   I THINK MAKE IT AN ORDERING THING JUST LIKE CLUSTERS - YOU KNOW THE ORDER OF THE CALLBACKS IN THE
   PIPELINE (AND IF YOU CHAIN CALLBACKS, YOU STILL KNOW), SO HAVE THE ONE RUN-TIME MACRO:
   "#define getnextstaticmemblock(ps2AllPipeData) \
     (ps2AllPipeData.dataIdx++, \
      &(ps2AllPipeData.data[ps2AllPipeData.dataOffsets[ps2AllPipeData.dataIdx - 1]]) )"
   IT'S A FUNC IN DEBUG WITH AN ASSERT ON MAXINDEX.
   ##NB## - IF YOU'RE ALLOWING A BLOCK OF CALLBACKS TO BE SWAPPED AROUND ON A PER-MESH BASIS,
            YOU NEED TO TAKE THIS INTO ACCOUNT. MAYBE SEPARATE PER-OBJECT AND PER-MESH CALLBACKS
            AT API LEVEL AND IN THE STATICALLY ALLOCATED DATA TOO.

  OK, OK, FORGET IT - IF PEOPLE WANT TO PASS DATA AROUND, HAVE THEM HANG IT OFF
  A POINTER IN OBJECT PLUGIN DATA, OK? THIS IS ONLY FOR ADVANCED USERS ANYWAY... */


    /* OBJALLINONE */

#if (defined(GSB))
    if (skyGCVTValue % skyNumSeqFrms != skyRenderSlot)
    {
        /* Don't render if it's not our turn */
        RWRETURN(TRUE);
    }
#endif /* (defined(GSB)) */

    RWASSERT(NULL != self);
    pvtData = (PRIVATEDATATYPE *)(self->privateData);
    RWASSERT(NULL != pvtData);
    ps2AllPipeData.objPvtData = pvtData;
    ps2AllPipeData.matPvtData = NULL;
    /* Set this up once per object, assume it doesn't change within callbacks :) */
    defaultMatPipe = RpMaterialSkyGetDefaultPS2AllMatPipeline();
    RWASSERT(NULL != defaultMatPipe);

    RWASSERT(NULL != params);
    data = RxPipelineNodeParamGetData(params);
    RWASSERT(NULL != data);
    ps2AllPipeData.sourceObject = data;

    /* By default, we won't call the matInstanceCode - the
     * objectSetupCB can override this if it decides the object
     * does (set rxINSTANCE[CONGRUENT|FULL]INSTANCE flag) or
     * could (clear the rxINSTANCEDONTINSTANCE flag) need fully
     * or congruently reinstancing */
    ps2AllPipeData.objInstance = rxINSTANCEDONTINSTANCE;

    /* These defaults needn't necessarily be changed */
//TODO[5]: WOULD A BLOCK SET OF THESE AT THE TOP BE QUICKER? (INITIALISE ON DECLARE)
    ps2AllPipeData.objIdentifier = blankIdentifier;
    ps2AllPipeData.spExtra       = blankExtra;
    ps2AllPipeData.transType     = blankTransType;
    ps2AllPipeData.matModulate   = blankMatModulate;
#if (defined(FASTMORPH))
//TODO[4][7]: YES THIS IS TEMPORARY (FINAL 'FASTMORPH2' SHOULD JUST SWAP BROKEN-OUT CLUSTERS AROUND)
    ps2AllPipeData.numMorphTargets = 1;
    ps2AllPipeData.fastMorphing = 0;
#endif /* (defined(FASTMORPH)) */

    /* People MUST set these up */
    ps2AllPipeData.meshHeader = blankMeshHeader;
    ps2AllPipeData.meshCache = blankMeshCache;
    ps2AllPipeData.primType = blankPrimType;

    /* Inlined ObjAllInOne */
    {
        RwMatrix      xForm;
        RwMatrix     *pxForm;
        RWASSERT(RWMATRIXALIGNMENT(&xForm));

        /* We open a local packet & write object data (matrix & lightblock) */

        /* We will need to capture some lights */
        _skyLightFillPos = (RwReal *)lightBuffer;
        /* Make space for the matrix */
        _skyLightFillPos += 4;
        _skyLightQWordsWritten = 0;

        /* Does all per-object setup (matrix, lighting), sets up
         * ps2AllPipeData with the data the rest of the pipe requires
         * (meshHeader, transType, primType) and does per-object reinstance
         * tests (which can block per-mesh tests and instancing) */
        RWASSERT(NULL != ps2AllPipeData.objPvtData->objectSetupCB);
        {
            pxForm = &xForm;
            RWASSERT(RWMATRIXALIGNMENT(pxForm));

            success = ps2AllPipeData.objPvtData->objectSetupCB(
                         &ps2AllPipeData, &pxForm, PS2AllSkyWorldApplyLight);

            /* Allow quitting the pipe prematurely */
            if (FALSE == success) RWRETURN(TRUE);
        }
        /* Check that the objectSetupCB's done everything it should have */
        if (ps2AllPipeData.objInstance != rxINSTANCEDONTINSTANCE)
        {
            RWASSERT(blankMeshHeader != ps2AllPipeData.meshHeader);
            RWASSERT(blankMeshCache != ps2AllPipeData.meshCache);
        }
        RWASSERT(blankPrimType != ps2AllPipeData.primType);

        /* Do this here 'cos it's well-defined and'd look weird to the user */
        if (ps2AllPipeData.numMorphTargets > 1) ps2AllPipeData.fastMorphing = 2;

        /* Finalise lights */
        if (_skyLightQWordsWritten)
        {
            long tmp, tmp1;
            u_long128 ltmp = 0;

            /* Terminate and finalise the light buffer */
            ((u_long128 *)_skyLightFillPos)[0] = nullLightBlock[1];
            _skyLightQWordsWritten++;

            _skyLightFillPos = (RwReal *)lightBuffer;

            tmp = (1l<<28) | (_skyLightQWordsWritten);
            tmp1 = (((0x6cl<<24)|((RwUInt64)_skyLightQWordsWritten<<16)
                   | (VU1LIGHTOFFSET))
                   << 32) |
                   ((1l<<24)|(4<<8)|(4));
            MAKE128(ltmp, tmp1, tmp);
            _skyLightQWordsWritten++;

            /* Header for return (with VIF TAG to upload to output buffer) */
            ((u_long128 *)_skyLightFillPos)[0] = ltmp;

        }
        else
        {
            /* This is what we will upload */
            _skyLightFillPos = (RwReal *)nullLightBlock;
            _skyLightQWordsWritten = sizeof(nullLightBlock)/sizeof(u_long128);
        }

        RWASSERT(RWMATRIXALIGNMENT(pxForm));

        /* Upload matrix, GIFTag (redone in matBridge - FIX) and light data */
//TODO[5]: MAKE IT NOT DO THE GIFTag (REMOVES ABSOLUTE REQUIREMENT FOR primType SETUP),
//        ALLOW IT TO TAKE A NULL MATRIX (E.G FOR WORLDSECTORS, LET THE MATRIX PERSIST)
//TODO[3]: FIRST COMMENT ABOVE SAYS WE OPEN A LOCAL PACKET... WHAT IF
//        THE objectSetupCB OPENS ONE? DOES THAT BREAK THIS?
        success = openVU1SetupPkt(ps2AllPipeData.primType,
                                  NULL, /* VU Code pointer */
                                  (const RwMatrix *)pxForm,
                                  (const u_long128 *)_skyLightFillPos,
                                  _skyLightQWordsWritten);
        RWASSERT(FALSE != success);
    }

//TODO[9]:    /* NULL is a valid value for the current/previous pipeline */
//    curMatPipe = (RxPipeline *)0xFFFFFFFF;
    /* Make sure the pipe 'changes' for the first mesh */
    curMatPipe = (RxPipeline *)NULL;
    curMatPvtData = (rxNodePS2AllMatPvtData *)NULL;

    meshHeader = ps2AllPipeData.meshHeader;
    RWASSERT(NULL != meshHeader);
    meshCache = ps2AllPipeData.meshCache;
    RWASSERT(NULL != meshCache);

    mesh = (const RpMesh *) (meshHeader + 1);

    /* Mesh loop, contains matInstance + matBridge */
    for (n = 0; n < meshHeader->numMeshes; n++)
    {
        RwRGBA      blankMatCol = {255, 255, 255, 255};
        RxPipeline *srcPipe;

/*        srcPipe = pvtData->groupPipe;
        if (NULL == srcPipe)
        {
            /* We're not grouping the meshes, sending 'em all to
             * material-specified pipes /
            srcPipe = mesh->material->pipeline;
            if (NULL == srcPipe) srcPipe = defaultMatPipe;
        }
        else
        {
//TODO[9]: don't get this code.... makes no sense 'a me
            curMatPipe = NULL;
        }*/

        srcPipe = pvtData->groupPipe;
        if (NULL == srcPipe)
        {
            /* We're not grouping the meshes, sending 'em all to
             * material-specified pipes */
            srcPipe = mesh->material->pipeline;
            if (NULL == srcPipe)
            {
                srcPipe = defaultMatPipe;
            }
        }

        /* Update the material pipeline if it has changed */
        if (srcPipe != curMatPipe)
        {
            RxPipelineNode *node;

            /* PS2All material pipes are NOT real, the pipe should
             * contain one and only one node, the PS2AllMat node.
             * PVS can ####ing well modify the object pipe. */
            RWASSERT(1 == srcPipe->numNodes);
            node = &(srcPipe->nodes[0]);
            RWASSERT(NULL != node);

            /* More paranoia */
            RWASSERT(NULL != node->nodeDef);
            RWASSERT(PS2ALLMATPRIVATEDATASIZE ==
                     node->nodeDef->pipelineNodePrivateDataSize);
            RWASSERT(NULL != node->privateData);
            curMatPvtData = (rxNodePS2AllMatPvtData *)node->privateData;
            RWASSERT(PS2ALLMATMAGICVALUE == curMatPvtData->magicValue);

            curMatPipe = srcPipe;
            ps2AllPipeData.matPvtData = curMatPvtData;
        }
        /* We went through first time round, riiiight? */
        RWASSERT(NULL != ps2AllPipeData.matPvtData);

        /* Initialise ps2AllPipeData per-mesh values */
        ps2AllPipeData.mesh = mesh;
        ps2AllPipeData.cacheEntryRef =
            rwMeshCacheGetEntryRef(meshCache, n);
        /* Initialise per-mesh reinstance flag to objectSetupCB's decision.
         * This is what is eventually tested and can be ORed into by the
         * meshReInstanceTestCB. */
        ps2AllPipeData.meshInstance = ps2AllPipeData.objInstance;
        ps2AllPipeData.meshIdentifier = blankIdentifier;
        /* Set up the VU1CodeArray index which can be overridden per mesh.
         * The new cull flag is omitted. */
        ps2AllPipeData.vu1CodeIndex = ps2AllPipeData.transType &
            (rxSKYTRANSTYPEFOG | rxSKYTRANSTYPECLIP | rxSKYTRANSTYPELIST |
             rxSKYTRANSTYPEISO | rxSKYTRANSTYPELINE);
        ps2AllPipeData.surfProps = &(mesh->material->surfaceProps);
        ps2AllPipeData.texture = mesh->material->texture;
        ps2AllPipeData.matCol = blankMatCol;
        if (FALSE != ps2AllPipeData.matModulate)
        {
            ps2AllPipeData.matCol = mesh->material->color;
        }

        /* "ps2AllPipeData.meshNum = n;" from nodePS2Manager.c is omitted
         * because we have the meshHeader under PS2All, and can do:
         * "if (mesh == meshHeader->firstMesh)",
         * instead of: "if (meshNum == 0)" */


/* TODOCS: 
 DONE: macroise remaining bits of objallinone, matinstance and matbridge
 DONE: throughout the ps2all headers, rwpubl!c out function docs (not for structures), they're cluttering rwcore/rpworld.h
 DONE: then do docs (link to helper docs and mention order in the callback docs)
 DONE: need to mention that helper macros use RWASSERT, so need to use in funcs with RWFUNCTION() at the top
 DONE: go through Katherine's docs amendments
 - final necessary TOD0CS/TOD0s :o/
  DONE: decide how to differentiate between atomics and sectors in the default material pipeline (messy, roll on RW4 :o/ )
  DONE: decide if/how to allow in-place reinstancing, update callback definitions and docs and instance flags typedef
  DONE: inline matinstance in ps2all node body
  o handful of misc things that shouldn't take > 5 mins
   DONE: test everything used in macros is actually exposed (use the macros in dungeon code, test debug and release)
   DONE: replace RWASSERT with PS2ALLMACROASSERT in the helper macros - defined as RWASSERT if it exists already
   DONE: put #doxygen-predicated duplicates of callback types in nodeps2all.c and ps2allmat.c w/ comments saying
        look before change
   DONE: rename rxINSTANCEPARTIAL to rxINSTANCECONGRUENT [or something else meaning same shape/size]
   - verify that persistent data instancing/rendering works
   - verify/dismiss flag-testing error in worldsector/atomic/blah instancing code
   - 
 - check up-to-date with ps2manager et al, check in (change file names? move files from subdir? make TODOs presentable),
 - describe to Rabin
 - profile (only debug that's jerky?)
 - Zalud wants a test harness they can use in my absence (a modified Dungeon I reckonne, w/ skinning)
  that should show up problems if something breaks */


//TODO[7]: Temporary, will be removed
        if (NULL != curMatPvtData->im3DPreMeshCB)
        {
            success = curMatPvtData->im3DPreMeshCB(&ps2AllPipeData);
            RWASSERT(FALSE != success);
        }

        /* MATINSTANCE */

im3dsplitloop:
        success = TRUE;

//TODOCS[2][3]: CHECK THAT PERSISTENT DATA INSTANCING/RENDERING WORKS (SKIN DEMO)
//             (SEE rpGeometryInstance()). WE CAN DO BETTER HERE THAN UNDER
//             PS2Manager BECAUSE WE COULD ALLOW THE bridgeCB RETURNING FALSE
//             TO EARLY-OUT AFTER SyncDCache BUT BEFORE TEXTURE SETUP AND DISPATCH
//TODO[5]: ASSUMING NON-PERSISTENT DATA AND NO REINSTANCE, YOU HAVE:
//        ONE TEST FOR PERSISTENT DATA, ONE TEST FOR NULL RESENTRY, ONE FOR NULL TESTCB AND ONE FOR
//        FINAL MESHINSTANCE FLAGS - THAT'S 4 TESTS IN THE FASTEST NON-PERSISTENT PATH! TOO MANY...
        if (rxINSTANCEDONTINSTANCE != ps2AllPipeData.objInstance)
        {
/*TODO[4]: INSTANCING FEATURES - Need to be able to handle:
     DONE: test for reinstancing, return something generic
     DONE: recalculate size of resentry
     DONE: allocate/free it using a custom allocator (ala im3d)
     DONE: fill in dma tags
     o reinstance standard and user data (opaque or broken-out)
     o replace broken-out data (must support current fastmorph and
      future fastest-morph) and fixup a contiguous (totally-nonopaque)
      tag chain, keeping track of lots of separate resentries somehow
      (or instancing all morphtargets into one and blah)
     o instance extra data eventually, to cache extra bits of dma chain
      or static data uploads (ease-of-use macros for small uploads and
      fitting them into a ready-made, efficient architecture) */
            rxNodePS2AllMatPvtData *pvtData;
            RwResEntry **repEntry = (RwResEntry **)NULL;
            rwPS2AllResEntryHeader *ps2ResHeader =
                (rwPS2AllResEntryHeader *)NULL;
            RwBool success;

//TODO[6]: NON-RW TYPES NEED CHANGING (NOTE: CASTS (E.G (INT)) WILL NEED CHANGING, LOTS IN HERE AND MATINSTANCE.C)
            RwUInt32 size = 0;
            RwUInt32 batchSize = 0 ;
            RwUInt32 batchesPerTag = 0;
//TODO[5]: MOVE INITIALIZATION OF VARS AS FAR DOWN AS POSS WHERE NOT NECESSARY ON ALL PATHS...
            RwUInt32 numBatches = 0;
            RwUInt32 numVerts = 0;
//TODO[5]: SHOULD NOT BE NECESSARY TO CACHE WHOLE FIELDRECS -> STORE IN PS2ALLMAT VERTEX FORMAT DESCRIPTORs
            rwPS2AllFieldRec fieldRec[CL_MAXCL + FMADD];
            RwUInt32 i;

            /* We calculate ResEntry size and layout with object-independent
             * code and the instance code is object-specific. */

            pvtData =
                (rxNodePS2AllMatPvtData *)ps2AllPipeData.matPvtData;
            RWASSERT(NULL != pvtData);
            repEntry = ps2AllPipeData.cacheEntryRef;
            RWASSERT(NULL != repEntry);

            REDEBUGPrintf(("Entered PS2All MatInstance code. *(ps2AllPipeData.cacheEntryRef) = %p\n",
                           *repEntry));

/*TODO[3]: IMPORTANT STUFF! READ TO THE END (POSSIBLE GOOD ANSWER THERE)
 #### ==> these (below test to make sure using a line/tri pipe for lines/tris and point for pointlists)
      are temporary, work out a better way with the setVUBufferSizes funcs rolled into one somehow
      and DMA-chain construction data generalised beyond trilist/tristrip... ?
      maybe you should extend the VUCodeArray thing to 5*16 pointers? Or is that not the issue?
      Maybe you should restrict one pipe to one primitive type. This is probably sensible and
      reasonable. You don't attach orange-rendering pipes to apples and expect it to work... plus
      you don't mix multiple primitives within a geometry/atomic/sector (*yet*!)
      Rabin points out that if you tristrip an originally trilist atomic (why do this at runtime???),
      you expect the default pipelines to just work... you expect to be able to apply the default
      pipelines to ordinary atomics (stripped or not) and have it just work. And we want to avoid
      nasty swapping of pipelines behind the scenes. As soon as users start using their own pipes
      then it's up to them, they're on their own - they need to attach the right pipes to the right
      types of object. So... I guess:
       o concentrate on encapsulating the preprocess data (vertex/DMA structures) for each primitive
        type for each pipeline such that the appropriate one can be chosen from a selection *within
        the pipeline*... -> vertex format descriptor
         The objectSetupCB can choose the appropriate one in the RW default code and we can also
        allow them to be swapped per-mesh if users want that and have appropriate plugin data.
       o make the code common, remove predication (->data) or at least gather it all together in
        one place (likewise deal with all cluster types in a uniform, data-driven manner)
       o the vu code array... dunno, separate issue dealt with in another TODO
       o I think that if you want to swap between lines and triangles, these are sufficiently
        different to warrant a change of pipeline. Ditto pointlists.
         For strips/trilists, treat that as parameterisation of *triangles*... though you should
        prolly have a separate vertex format descriptor for each... not sure (mixing them increases
        predication and makes things messy, but it's the most likely swap to occur... is swapping
        vert format descriptor disadvantageous in some way?)
         BUT... changing primtype is a sub-Object change so it should cause a change of material
        pipeline only, not object pipeline. I'm certain of that. Just maybe we should make the
        default RW material pipeline magically handle all primTypes (inc pointlists?). Hmm... we
        need this to work simply in a practical sense... it needs to 'just work' if people do
        reasonable things and don't interfere... maybe we have default vertex format descriptors
        for each primtype and people can set a descriptor explicitly on a given material (or rather
        overload the descriptor for any/all of the primtypes). We choose the appropriate one in the
        objectSetupCB or people can set one explicitly in the meshReInstanceCB if they wish.
         Also... primType support is just one part of vertex format descriptors... maybe we pack
        data for all primtypes into the descriptor, maybe we store is separately as global LUTs?
        We'll only ever support 5 types and pointlists can be used for 'new' types. We won't force
        these LUTs down people's throats for reinstancing... maybe we can use them purely for our
        default code and merely expose them as useful optional LUTs? No need to put them in vert
        descriptor things at all? It's up to user reinstance callbacks to be able to handle all
        the primtypes that their material pipes are applied to - yes! */
#if (defined(RWDEBUG))
            if ((pvtData->pipeType & ps2AllPipeData.meshHeader->flags) !=
                (rpMESHHEADERPRIMMASK & ps2AllPipeData.meshHeader->flags))
            {
                RwDebugSendMessage(
                    rwDEBUGMESSAGE,
                    "_rwPS2AllRabinsMatInstanceCode",
                    "Incorrect primitive type for the current pipeline: use RxPipelineNodePS2AllSetVUBufferSizes for constructing trilist/tristrip/trifan/linelist/polyline pipelines and RxPipelineNodePS2AllSetVUPointListBufferSize for point list objects");
                /*RWRETURN(FALSE);*/
            }
#endif /* (defined(RWDEBUG)) */

            /* Check validity of unindexed meshes */
            RWASSERT(((NULL == ps2AllPipeData.mesh->indices) &&
                      (  ps2AllPipeData.meshHeader->flags &
                         rpMESHHEADERUNINDEXED )) ||
                     ((NULL != ps2AllPipeData.mesh->indices) &&
                      (!(ps2AllPipeData.meshHeader->flags &
                         rpMESHHEADERUNINDEXED)))   );

            /* We add rxINSTANCEDONTINSTANCE here so meshes default
             * to not being instanced if (objInstance == 0) */
            ps2AllPipeData.meshInstance |= rxINSTANCEDONTINSTANCE;

            /* Work out if we need to instance */
            if (*repEntry)
            {
                ps2ResHeader = RWPS2ALLRESENTRYHEADERFROMRESENTRY(*repEntry);

                /* We allow a callback to extract all the data it needs
                 * from the mesh to test what reinstancing is required */
                if (NULL != pvtData->meshInstanceTestCB)
                {
                    /* Test/update meshIdentifier and set final reinstance flags */
                    success = pvtData->meshInstanceTestCB(&ps2AllPipeData);
                    RWASSERT(FALSE != success);
                }
                else
                {
                    /* reInstance flags remain as they were set in the objectSetupCB */
                }
            }
            else
            {
                /* All your base are NULL for us!
                 * What you say??
                 * Reinstance! For great justice! */
                ps2AllPipeData.meshInstance |=
                    (rxINSTANCEFULLINSTANCE|rxINSTANCEALL);
                /* Important to set this flag, so SyncDCache is called */
            }

            /* Instance to whatever extent is necessary */
            if (ps2AllPipeData.meshInstance & rxINSTANCETYPEMASK)
            {
                void *dataPtrs[CL_MAXCL + /*1 + */FMADD];
                RwUInt32 numStripes;

                if (ps2AllPipeData.meshInstance&(rxINSTANCECONGRUENTINSTANCE|
                                                 rxINSTANCEFULLINSTANCE)     )
                {
                    u_long128 *data;

                    /* Congruent and full both reinstance ALL clusters */
                    ps2AllPipeData.meshInstance |= rxINSTANCEALL;

//TODOCS[2]: FIX THE CONGRUENT(PARTIAL) REINSTANCE BUG, THEN REVERT THIS (TRY IT AND SEE IF IT'S FIXED. THINK ABOUT IT IF NOT)
//                    if (!(ps2AllPipeData.meshInstance & rwINSTANCEFULLINSTANCE))
                    if (0)
                    {
                        /* Cache sizes and offsets to save recalculation */
                        size = (*repEntry)->size;
                        numVerts = ps2ResHeader->numVerts;
                        batchSize = ps2ResHeader->batchSize;
                        numBatches = ps2ResHeader->numBatches;
                        batchesPerTag = ps2ResHeader->batchesPerTag;

                        /* How nasty */
#if (!defined(FASTMORPH))
                        for (i=0;i<CL_MAXCL;i++)
#else /* (!defined(FASTMORPH)) */
                        for (i=0;i<(CL_MAXCL+ps2AllPipeData.fastMorphing);i++)
#endif /* (!defined(FASTMORPH)) */
                        {
                            fieldRec[i] = ps2ResHeader->fieldRec[i];
                        }

                        REDEBUGCacheResEntryStructureMacro();
                    }
                    else
                    {
                        REDEBUGPrintf(("We can't cache\n"));

/*TODO[3][4][5]: The numVerts calculation macro:
 - if people want to deal with fans in-VU1, they can upload frickin pointlists
 - polylines might be a reasonable thing to ask for, so we should
  make sure that the vertex format descriptor block can support
  polylines being used directly (not expanded to linelists)...
  [cool point: linelists could be uploaded as polylines with ADC set
   on alternate points! all the verts would otherwise be the same!]
  [downside: you'd need to do vert duplication ala triStrips
    - no no, let's remove it from all primitive types and have
    in-VU duplication... we'd lose SO much CPU-side code and data!!!!!
    Though Rabin wants to generalise this overlap thing, maybe it's
    best to simply give in... so it's data-driven for all primtypes,
    the overlap is zero for most, 2 for strips and 1 for polylines?] */
                        /* Calculate numVerts (necessary for full reinstances and initial instances) */
                        RPMESHPS2ALLCALCNUMVERTS(&ps2AllPipeData, &numVerts);

//TODO[3]: SHOULD HAVE EARLIED-OUT BEFORE THIS? GUESS THAT'S THE OBJECTSETUPCB'S JOB?
//         WHAT ABOUT EMPTY *MESHES* THOUGH?
                        RWASSERT(numVerts > 0);
                        REDEBUGPrintf(("numVerts %d\n", numVerts));

                        /* Recalculate the DMA data size/layout (occurs either
                         * for full reinstance or initial instance) */
                        size = DMADataSizeRecalc(&ps2AllPipeData, numVerts,
                                                 fieldRec, &batchSize,
                                                 &batchesPerTag, &numBatches);
                    }

                    /* Disconnect an existing resource entry to be
                     * garbage-collected once DMA is done with it */
                    if (*repEntry)
                    {
                        (*repEntry)->ownerRef = (RwResEntry **)NULL;
                        *repEntry = (RwResEntry *)NULL;
                    }

                        /* Allocate and Create the structure of the new resEntry - DMA tags and wotnot */
/*TODO[4][5]: IN THE ALL-NON-OPAQUE CASE (COPY+FIXUP) THE OLD DMA CHAIN OR RECREATE FROM SCRATCH? HOW MUCH FIXUP?
   - TO FIX UP A DMA CHAIN WITHOUT HAVING TO FLUSH CACHE, YOU CAN DO UNCACHED WRITES.
    TO DO THAT, OR "2" INTO THE UPPER NIBBLE OF THE ADDRESS WRITTEN TO. THE TOP NIBBLE
    IS FLAGS (SO ONLY 28 BITS OF ADDRESS SPACE), 0 IS NORMAL, 2 UNCACHED, 3 UNCACHED
    OPTIMISED (READS/WRITES IN BLOCKS OR SUMMAT), ETC. IN EEKERNEL.H PROBABLY.*/

                    REDEBUGPrintf(("Allocing res entry of size %d bytes\n", size));

                    /* If we need to alloc, we need a callback to do it fow us */
                    RWASSERT(NULL != pvtData->resEntryAllocCB);
                   *repEntry = pvtData->resEntryAllocCB(
                       &ps2AllPipeData, repEntry, size,
                        reDestroyCallBack);

                    if (NULL == *repEntry)
                    {
                        /* Might as well exit safely, this isn't the fast-path.
                         * NOTE: this should only actually *fail* if "size" is bigger than
                         * the whole resource arena! Otherwise, it just spins until enough
                         * space can be freed up (naaasty thrashing! METRICS-ho!) */
                        MESSAGE("_rwPS2AllRabinsMatInstanceCode: ResEntry couldn't be alloc'd. Exiting...\n\n");
                        RWRETURN(FALSE);
                    }

                    REDEBUGPrintf(("ResEntry at %p\n", *repEntry));

                    CLEARMEMResEntryClearMacro();

                    ps2ResHeader =
                        RWPS2ALLRESENTRYHEADERFROMRESENTRY(*repEntry);

                    /* No reference counts yet (clrCnt allows multiple derefs to occur in one go; 'sqwika) */
                    ps2ResHeader->refCnt = 0;
                    ps2ResHeader->clrCnt = 0;

//TODO[5]:
//WOULD BE MORE EFFICIENT (AND TIDY) HERE TO BE ABLE TO GO "*ps2ResHeader = *(ps2AllPipeData.ps2ResHeader);"
//  OR AT LEAST "ps2ResHeader->subBlock = ps2AllPipeData.ps2ResHeaderSubBlock;"

//TODO[3]:
//   HMM, WE SHOULD STORE PIPELINE POINTERS IN INSTANCE DATA
//  (MATERIAL PIPE POINTERS - HRMM, WHAT ABOUT PS2MANAGER?)
//  SO STUFF GETS REINSTANCED IF PIPES CHANGE

                    ps2ResHeader->objIdentifier =
                        ps2AllPipeData.objIdentifier;
                    ps2ResHeader->meshIdentifier =
                        ps2AllPipeData.meshIdentifier;
                    ps2ResHeader->numVerts = numVerts;

//TODO[5]: THESE PROBABLY GONE ONCE GOT VERTEX FORMAT DESCRIPTORS/INSTANCING DATA BLOCKS...
#if (!defined(FASTMORPH))
                    for (i=0; i<CL_MAXCL; i++)
#else /* (!defined(FASTMORPH)) */
                    for (i=0; i<CL_MAXCL + ps2AllPipeData.fastMorphing; i++)
#endif /* (!defined(FASTMORPH)) */
                    {
                        ps2ResHeader->fieldRec[i] = fieldRec[i];
                    }


//TODO[4]: NEW VERTEX FORMAT DESCRIPTOR/REINSTANCE DATA NEEDN'T BE STORED WITH RESHEADER
//        SO WE DON'T NEED TO DO MUCH OF THIS CACHING. ASSERT ON CHANGING DESCRIPTORS IF POSS.
/*TODO[5]: THE ELEMENTS OF THE RESHEADER WHICH DO NEED TO BE CACHED CAN BE COPIED STRAIGHT FROM
      THE OLD RESHEADER - KEEP IT AROUND TILL THE END OF THIS FUNC RATHER THAN COPYING IT:
       - RABINS SAYS THAT ALLOC'ING THE NEW RESENTRY MAY THROW THE OLD ONE OUT
        OF THE RESOURCEARENA. IN THIS CASE, PERFORMANCE WILL BE AWFUL (WE SPIN ON
        THE OLD RESENTRY, WAITING FOR UP TO 4 FRAMES FOR IT TO BE DEREF'D BY DMA)
        SO WE SHOULD GO THROUGH THE UNCACHED ARM OF THE CODE (DMADataSizeRecalc()).
        IF IT'S STILL AROUND AFTER WE ALLOCATE, THEN USE IT - RESOURCE ALLOC'S DON'T
        HAPPEN ASYNCHRONOUSLY SO IT WON'T DIE WHILE WE'RE USING IT (IT MAY BE 'FREED'
        BUT THE DATA WON'T BE OVERWRITTEN BECAUSE ANOTHER ALLOC CAN'T HAPPEN TO
        REUSE ITS SPACE). */
                    ps2ResHeader->batchSize = batchSize;
                    ps2ResHeader->numBatches = numBatches;
                    ps2ResHeader->batchesPerTag = batchesPerTag;
//TODO[9]:           ps2ResHeader->material = ps2AllPipeData.mesh->material;

#if (defined(FASTMORPH))
                    /* These get overwritten by atomicreinstance but let's
                     * at least initialise them for worldsectors/im3d */
//TODO[3]: SHOULD WE INITIALISE MORPHNUM TO ZERO FOR THEM? TO SIGNIFY NOT ATOMICS/MORPHABLE?
                    ps2ResHeader->morphStart = 0;
                    ps2ResHeader->morphFinish = 0;
                    ps2ResHeader->morphNum = 1;
#endif /* (defined(FASTMORPH)) */


                    /* Cache-line-align the start of data in all circumstances */
{
//    RwUInt8 *origData = (RwUInt8 *)(ps2ResHeader + 1);
                    data = (u_long128 *)
                        (((RwUInt32)(ps2ResHeader + 1) + 63) & ~63);
#if (defined(DMAALIGN))
                    if (pvtData->totallyOpaque)
                    {
                        data = (u_long128*)
                            (((RwUInt32)data + 127) & ~0x7f);
                    }
#endif /* (defined(DMAALIGN)) */
//        printf("disalignment of data: %d\n", ((RwUInt32)(RwUInt8 *)data) - (RwUInt32)origData);
}
                    ps2ResHeader->data = data;

                    /* For all broken out clusters, set up the offset (from the base
                     * of the instance data) so we don't have to access large data
                     * structs during the fast-path [in envisionClusters()] */
//TODO[4]: THIS INTO THE NEW REINSTANCE DATABLOCK PART OF THE RESHEADER
//        USED ONLY FOR MESH REINSTANCE CALLBACKS
                    numStripes = 0;
#if (!defined(FASTMORPH))
                    for (i = 0;i < CL_MAXCL;i++)
#else /* (!defined(FASTMORPH)) */
                    for (i = 0;i < CL_MAXCL + ps2AllPipeData.fastMorphing;i++)
#endif /* (!defined(FASTMORPH)) */
                    {
                        rwPS2AllClusterInstanceInfo *clinfo
                            = &(pvtData->clinfo[i]);

                        if ((  clinfo->attrib & CL_ATTRIB_REQUIRED) &&
                            (!(clinfo->attrib & CL_ATTRIB_OPAQUE ))   )
                        {
                            int offset = ps2ResHeader->fieldRec[i].dataoffset;
#if (defined(FASTMORPH))
                            if (ps2AllPipeData.fastMorphing)
                            {
                                offset =
                                    ps2ResHeader->fieldRec[i].morphDataoffset;
                            }
#endif /* (defined(FASTMORPH)) */
                            ps2ResHeader->clquickinfo[numStripes].data =
                                ps2ResHeader->data + offset;

                            ps2ResHeader->clquickinfo[numStripes].stride =
                                clinfo->stride << 2;
                            numStripes++;
                        }
                    }
                    RWASSERT(numStripes == pvtData->numStripes);

                    REDEBUGResHeaderMacro();

                    /* Fill in the DMA tags in the chain */
                    success = DMADataFillTags(&ps2AllPipeData);
                    RWASSERT(FALSE != success);
                }
                else
                {
                    /* In-place instancing - the current resEntry persists. */
                }

                /* Instance the data. In this path, we require
                 * there to be at a callback to instance the data! */
                RWASSERT(NULL != pvtData->instanceCB);

                numStripes = pvtData->numStripes;

                /* Note: we still execute even if there are no visible
                 * clusters - a plugin may want to upload only static data. */
                for (i = 0;i < numStripes;i++)
                {
//TODO[4]: QUICKINFO -> NEW INSTANCE DATA BLOCK
                    dataPtrs[i/*+1*/] = ps2ResHeader->clquickinfo[i].data;
                }

                /* For point-lists, we set up a triStrip transform and GSPrim in
                 * ObjectSetup code, but a user instance callback/node can
                 * override this by editing DMASessionRecord->transType and/or
                 * uploading their own GIF tag(s) */
                success = pvtData->instanceCB(
                              &ps2AllPipeData, dataPtrs, numStripes);
                RWASSERT(FALSE != success);

                /* Sync d-cache here since we modified instance data.
                 * Sync from the start of the data (end of the RwResEntry and
                 * rwPS2AllResEntryHeader headers) to its end. We don't need to
                 * use SCESYNCDCACHEROUNDUP cos our data is QW-aligned and
                 * completely contained within the resEntry */
                SyncDCache(ps2ResHeader->data,
                           (RwUInt8 *) (*(ps2AllPipeData.cacheEntryRef)) +
                           sizeof(RwResEntry) +
                           (*(ps2AllPipeData.cacheEntryRef))->size +
                           195);
                /* 195 is Rabin's paranoia - add 128 and 64 and round up doubly-paranoidly
                 * To do with now aligning DMA data to 128-byte boundary and making sure
                 * a full cache line (64) at the end gets flushed. */
            }
            else
            {
                /* Yay, no reinstancing */
            }

            /* Make sure instance data now does exist */
            RWASSERT(NULL != *(ps2AllPipeData.cacheEntryRef));
        }

        /* MATBRIDGE */

        /* Does everything - user static uploads, texture setup, GIFTag upload,
         * matcol, surfprops/extra, clipping info and VUCode-selection/upload.
         * To skip rendering the mesh, the matBridgeCB can early-out */
        RWASSERT(NULL != ps2AllPipeData.matPvtData->bridgeCB);
/* TODO[3][4][5]: W.R.T THE HELPER FUNCS IN THE BRIDGECB... MAKE SURE OMISSION RETAINS PRIOR STATE...? */
        success = ps2AllPipeData.matPvtData->bridgeCB(&ps2AllPipeData);
        RWASSERT(FALSE != success);

//TODO[3][6]: METRICS GO IN HERE? (CULLED BY ABOVE INSTANCE CB RETURNING NULL?
//           OBJECT-SPECIFIC METRICS CBs? JUST GOES IN EXISTING (e.g POSTMESH) CB?)

        //Temporary, will be removed
        if (NULL != curMatPvtData->im3DPostMeshCB)
        {
            success = curMatPvtData->im3DPostMeshCB(&ps2AllPipeData);
            RWASSERT(FALSE != success);
        }

        mesh++;

        //Temporary, will be removed
        if ((curMatPvtData->im3DMeshSplitCB != NULL) &&
            (curMatPvtData->im3DMeshSplitCB(&ps2AllPipeData)))
        {
            /* Using goto reduces nesting (80 char wrap) and this *is* temporary... */
            goto im3dsplitloop;
        }

        if (curMatPvtData->postMeshCB != NULL)
        {
            success = curMatPvtData->postMeshCB(&ps2AllPipeData);
            RWASSERT(FALSE != success);
        }
    }

    if (NULL != pvtData->objectFinalizeCB)
    {
        success = pvtData->objectFinalizeCB(&ps2AllPipeData);
        RWASSERT(FALSE != success);
    }

    RWRETURN(TRUE);
}

/* These callback definitions are duplicated here (for DOXYGEN)
 * so that huge wodges of docs don't end up in rpworld.h */
#if (defined(DOXYGEN))

/**
 * \ingroup rxpipelinenodeps2all
 * \ref RxPipelineNodePS2AllObjectSetupCallBack is the callback to be called,
 * for the owning PS2All.csl pipeline node, to perform per-object
 * setup tasks prior to rendering.
 *
 * Since it is the first callback called and since the objects being
 * rendered are arbitrary (they need not be RpAtomics or RpWorldSectors
 * but they must contain RpMeshHeaders and RpMeshes), this callback
 * needs to initialize several fields of the RxPS2AllPipeData
 * structure with information about the object, as follows:
 *
 * Firstly, the callback needs to extract a pointer to the
 * \ref RpMeshHeader in the object and put it in the meshHeader field.
 * [strictly speaking, this is only necessary if per-mesh instance
 * tests or instancing are going to occur]
 *
 * Secondly, the meshCache field needs to be filled. You can extract
 * a pointer to the \ref RwMeshCache in atomics, geometries and world
 * sectors using rpGeometryGetMeshCache, rpAtomicGetMeshCache and
 * rpWorldSectorGetMeshCache respectively. Note that if your geometry
 * has more than one morphtarget then the meshcache will hang off the
 * atomic instead of the geometry (different atomics may be in different
 * animation states, so the instance data can't be shared).
 *
 * If the object has multiple RpMorphTargets and is to be morph animated
 * then the numMorphTargets field should be set up as appropriate. Also,
 * the current 'scale' value (0-1, the linear interpolant between the
 * current start and end morphtargets) needs to be uploaded. Set the
 * spExtra field to this value so that it will be uploaded in a spare
 * slot in the quadword containing surface properties. Calculate the
 * value thus: "interpolator->recipTime*interpolator->position".
 *
 * To control mesh instancing, set up objInstance and objIdentifier.
 * objIdentifier is a value stored in the rwPS2AllResEntryHeader
 * structure which heads up instance data and which can be inspected
 * to determine if an object has changed sufficiently, since its meshes
 * were last instanced, to merit their being reinstanced. How you
 * construct this value is up to you. The default RenderWare callbacks
 * use a combination of the mesh header's serial number, object flags
 * and the object's number of morph targets.
 *
 * objInstance is an \ref RxInstanceFlags value and an overview
 * of its use is given here: \ref RxPipelineNodePS2AllSetCallBack.
 * To retrieve objIdentifier from the instance data, dereference
 * the meshCache member and extract the rwPS2AllResEntryHeader with
 * the macro RWPS2ALLRESENTRYHEADERFROMRESENTRY. As detailed in the
 * documentation for the \ref RxInstanceFlags, you may specify that
 * an object's meshes should be reinstanced in-place, congruently,
 * fully or not at all.
 *
 * The primType field needs to be initialized to the GS primitive
 * type which will be submitted by your vector code. This value
 * will get uploaded in a GIFTag placed in the VU1 static memory
 * area - this can be ignored by your vector code if you so wish.
 * As of this writing, GS primitive type values are listed on
 * p113 of the GS manual, in section 7.1
 *
 * The transtype field should be initialized to specify the type
 * of the VU1 transform code which will be used. This field is
 * a bitfield constructed from \ref RxSkyTransTypeFlags. It will
 * be used by material pipelines to select the appropriate VU1
 * code fragment from the VU1 code array. For an example, code
 * which clips triangles is generally much slower than code which
 * does not, so you could perform a camera frustum intersection
 * test on a bounding volume of your object to decide whether to
 * set the rxSKYTRANSTYPECLIP flag. The default RenderWare code
 * for RpAtomics and RpWorldSectors does this. Im3D relies on a
 * hint flag passed to \ref RwIm3DTransform.
 *
 * The matModulate field specifies whether the material colors of
 * the object should be modulated with lighting values. Simply
 * set it to TRUE or FALSE. Setting it to FALSE is equivalent
 * to giving all your materials the color {255, 255, 255, 255}.
 *
 * The object-space to camera-space transformation matrix must
 * set up (though the transform parameter). To do this, concatenate
 * the object's LTM matrix with the current camera's view matrix.
 * Alternatively, if you have calculated or cached a complete
 * transform matrix elsewhere, you may simply change the transform
 * pointer (hence the double indirection).
 *
 * In order for dynamic lighting to work, the object setup callback
 * should evaluate which lights affect the current object and for each
 * such light call the supplied lighting function, passing a pointer to
 * the light, the inverse LTM of the object, the reciprocal of the matrix
 * scaling factor (this is 1.0 unless the matrix is non-normalized. Only
 * orthogonal matrices are allowed) and the surface properties of the
 * object. The latter three parameters are actually only required for
 * the first light passed. Here is the prototype of the
 * \ref RxWorldApplyLightFunc callback:
 * \verbatim
   void (*RxWorldApplyLightFunc)(const void *voidLight,
                                 const RwMatrix *inverseMat,
                                 RwReal invScale,
                                 const RwSurfaceProperties *surfaceProps);
   \endverbatim
 * 
 * The default lighting code used for atomics is contained in
 * \ref RpAtomicPS2AllLightingSetup. For sectors,
 * \ref RpWorldSectorPS2AllLightingSetup is used. These use the helper
 * functions: RpAtomicForAllWorldSectors, rpWorldSectorForAllLocalLights,
 * rpWorldForAllGlobalLights, RpAtomicPS2AllDoApplyLight,
 * RpAtomicPS2AllDoApplyLightFrame and RpWorldSectorPS2AllDoApplyLight.
 * Lights are by default not used for Im3D (though on PS2 they may be - the
 * same VU1 code is used as for atomics and world sectors).
 *
 * If no lighting setup is done then no lights will be uploaded to VU1.
 * To compensate, your vector code could contain hard-coded lights or
 * you could upload lights into static data in the
 * \ref RxPipelineNodePS2AllMatInstanceCallBack.
 *
 * Note that the default RenderWare callbacks accumulate metrics
 * information, so replacement callbacks must do so also if metrics
 * information is to be accurate. You may use these helper functions
 * to gather metrics information:
 *
 * The default RxPipelineNodePS2AllObjectSetupCallBack for RpAtomics is
 * \ref RpAtomicPS2AllObjectSetupCallBack. For RpWorldSectors, the default
 * is \ref RpWorldSectorPS2AllObjectSetupCallBack and for RwIm3D the
 * default is \ref RwIm3DPS2AllObjectSetupCallBack. The documentation for
 * these default callbacks list the macros from which they are constructed
 * so that you may (partially or fully) reconstruct the default callbacks
 * and make your own modifications. Note that the macros contain RWASSERT,
 * so they must be used within functions using RWFUNCTION and RWRETURN.
 *
 * In summary, the things this callback MUST set up are: 
 *
 * transform, meshHeader, meshCache, primType
 *
 *
 * The default values of RxPS2AllPipeData members are: 
 * \verbatim
   meshHeader = NULL,
   meshCache = NULL,
   numMorphTargets = 1,
   spExtra = 0,
   objInstance = 0,
   objIdentifier = 0,
   primType = UNDEFINED,
   transType = NoFog|NoClip|Strip|Persp|Tri|NoCull,
   matModulate = FALSE
   \endverbatim
 *
 *
 * The callback can prematurely terminate pipeline execution by returning FALSE.
 *
 * This callback is required for PS2All.csl to function. If it is set
 * to NULL (it is initialized to NULL when the pipeline is unlocked),
 * then there will be an ASSERT (in debug) and a crash.
 *
 * \param  ps2AllPipeData A pointer to a \ref RxPS2AllPipeData struct
 *                        containing information relevant to the
 *                        current pipeline execution
 * \param  transform      A pointer to a pointer to a transform matrix
 * \param  lightingFunc   A pointer to a \ref RxWorldApplyLightFunc
 *                        function to upload information about lights
 *                        to VU1
 *
 * \return TRUE on success, FALSE to prematurely terminate the pipeline
 *
 * \see RxPipelineNodePS2AllSetCallBack
 * \see RxPipelineNodePS2AllObjectFinalizeCallBack
 * \see RxPipelineNodePS2AllMatSetCallBack
 */
typedef RwBool (*RxPipelineNodePS2AllObjectSetupCallBack)
    (RxPS2AllPipeData *ps2AllPipeData,
     RwMatrix **transform,
     RxWorldApplyLightFunc lightingFunc);

/**
 * \ingroup rxpipelinenodeps2all
 * \ref RxPipelineNodePS2AllObjectFinalizeCallBack is the callback to
 * be called, for the owning PS2All.csl pipeline node, once all meshes
 * in the object have been rendered.
 *
 * This callback can be used to perform tasks necessary after the
 * rendering of all meshes in the object. For example, the default
 * RxPipelineNodePS2AllObjectFinalizeCallBack for RpAtomics
 * (RpWorldSectors and Im3D do not use such a callback),
 * \ref RpAtomicPS2AllObjectFinalizeCallBack, clears flags in the
 * contained RpGeometry relating to reinstancing (it cannot clear
 * these after every mesh or else only the first mesh in the object
 * would ever get reinstanced!) as well as flags in the RpAtomic's
 * RpInterpolator. The documentation for
 * \ref RpAtomicPS2AllObjectFinalizeCallBack list the macros from which
 * it is constructed so that you may (partially or fully) reconstruct
 * it and make your own modifications. Note that the macros contain
 * RWASSERT, so they must be used within functions using RWFUNCTION and
 * RWRETURN.
 *
 * This callback is not required for PS2All.csl to function. It may
 * safely be set to NULL (it is initialized to NULL when the pipeline
 * is unlocked).
 *
 * \param  ps2AllPipeData A pointer to a \ref RxPS2AllPipeData struct
 *                        containing information relevant to the
 *                        current pipeline execution
 *
 * \return TRUE on success, FALSE otherwise
 *
 * \see RxPipelineNodePS2AllSetCallBack
 * \see RxPipelineNodePS2AllObjectSetupCallBack
 * \see RxPipelineNodePS2AllMatSetCallBack
 */
typedef RwBool (*RxPipelineNodePS2AllObjectFinalizeCallBack)
    (RxPS2AllPipeData *ps2AllPipeData);

#endif /* (defined(DOXYGEN)) */



/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2AllSetCallBack is a post construction time
 * node API function used to register a callback with a PS2All.csl
 * pipeline node.
 *
 * During its execution, the PS2All.csl node calls several callback
 * functions. Callbacks are also called from the PS2AllMat.csl node
 * in material pipelines (see \ref RxPipelineNodePS2AllMatSetCallBack
 * for details). Here is a list of the callback types (in order of
 * execution within PS2All.csl) used by PS2All.csl:
 *
 * rxPS2ALLCALLBACKOBJECTSETUP - Performs per-object setup
 * (see \ref RxPipelineNodePS2AllObjectSetupCallBack)
 * rxPS2ALLCALLBACKOBJECTFINALIZE - Performs per-object post-render tasks
 * (see \ref RxPipelineNodePS2AllObjectFinalizeCallBack)
 *
 *
 * Here is a list of the default callbacks used within RenderWare for
 * RpAtomics, RpWorldSectors and Im3D, exposed so that you may use them
 * in custom pipelines:
 *
 * RpAtomicPS2AllObjectSetupCallBack, RpWorldSectorPS2AllObjectSetupCallBack, RwIm3DPS2AllObjectSetupCallBack
 * 
 * RpAtomicPS2AllObjectFinalizeCallBack, (none for RpWorldSectors or Im3D)
 *
 *
 * NOTE: When a PS2All.csl or PS2AllMat.csl pipeline is unlocked,
 * its list of callbacks is initialized to NULL - this means that
 * when constructing such a pipeline, you must explicitly set all
 * required callbacks (the docs for each callback type specify which
 * are required, which are optional and why). This is so that no
 * errors can occur due to accidental reliance on inappropriate
 * default callbacks. There will be ASSERTs at runtime if you omit
 * required callbacks. 
 *
 *
 * Here follows an overview of pipeline execution through PS2All.csl
 * (the object pipeline) and PS2AllMat.csl (the material pipeline(s):
 *
 * PS2All.csl performs some per-object processing (through callbacks)
 * and then calls a PS2AllMat.csl-based material pipeline for each
 * mesh in the object. It uses the RxPS2AllPipeData structure to
 * carry data down the pipeline which is useful for all the
 * callbacks which will be executed along the way. For details of
 * the members of this structure, see \ref RxPS2AllPipeData. Some
 * of these members will be initialized by the nodes, some should
 * be initialized by callbacks (see documentation for callback
 * types for more precise details on this), most are modifiable by
 * callbacks.
 *
 * First of all, PS2All.csl calls the \ref RxPipelineNodePS2AllObjectSetupCallBack,
 * which performs tasks such as frustum culling, per-object instance
 * testing and lighting setup. The decision of whether or not to
 * reinstance meshes is controlled in the pipeline by the objInstance and
 * meshInstance members of RxPS2AllPipeData. objInstance is initialized
 * to rxINSTANCEDONTINSTANCE which signifies that no mesh instancing or
 * instance testing is to occur. The RxPipelineNodePS2AllObjectSetupCallBack
 * may clear this flag if it wishes mesh instance testing to occur and
 * may set the rxINSTANCEINPLACEINSTANCE, rxINSTANCECONGRUENTINSTANCE or
 * rxINSTANCEFULLREINSTANCE flags if it wishes in-place, congruent or full
 * (see below) reinstancing to occur for all meshes regardless of mesh
 * instance tests. The meshInstance field is initialized to (the
 * post-object-setup value of) objInstance for all meshes, then processing
 * passes, for each mesh in turn, to material pipelines - containing
 * PS2AllMat.csl.
 *
 * PS2AllMat.csl will first call the
 * \ref RxPipelineNodePS2AllMatMeshInstanceTestCallBack unless
 * meshInstance is set to rxINSTANCEDONTINSTANCE and unless no
 * instance data exists yet (in which case no test is necessary,
 * instancing will definitely have to occur!). This will update
 * meshInstance and if, on exit, rxINSTANCEINPLACEINSTANCE,
 * rxINSTANCECONGRUENTINSTANCE or rxINSTANCEFULLINSTANCE are set
 * then reinstancing will occur. Full reinstancing is performed
 * when the size and layout of instance data has changed and
 * must be recalculated (PS2AllMat.csl will do this). Congruent
 * reinstancing is performed when this is not the case and the
 * size and layout of instance data may be cached. In both
 * cases, a new block of instance data will be allocated and filled,
 * using the \ref RxPipelineNodePS2AllMatResEntryAllocCallBack.
 * In-place instancing is performed 'in-place' in the existing
 * RwResEntry.
 *
 * Modifying instance data in-place will cause a time anomaly (if
 * DMA is running three frames behind the CPU, in-place CPU changes
 * will appear to have their effect three frames 'into the future')
 * but if you still wish to do this then it is more efficient than
 * congruent or full reinstancing (which recreate all the instance
 * data from scratch). In-place data modification could be useful
 * for something like, say, CPU-side particle system animation. For
 * example, you might request the CL_RGBA and CL_XYZ clusters and
 * specify the CL_XYZ cluster as CL_ATTRIB_READWRITE. Then the
 * instance callback could modify the positions of vertices
 * (particles) before they are uploaded to VU1 without incurring
 * the cost of a full (or congruent) reinstance every frame.
 *
 * The \ref RxPipelineNodePS2AllMatInstanceCallBack is used to
 * instance data. This should instance both standard data types and
 * user data types (see \ref RxPS2ClusterType). See
 * \ref RxPipelineNodePS2AllMatInstanceCallBack for details. If
 * rxINSTANCEDONTINSTANCE is set, then the
 * RxPipelineNodePS2AllMatInstanceCallBack will not be called. This
 * is the no instancing 'fast-path' (such a path is always taken if
 * 'persistent instance data' is being used - a new and currently
 * unsupported feature accessible through _rpGeometryInstance, which
 * instances an RpGeometry into memory outside of the resource arena
 * so that it can be streamed to and read from disk and need never be
 * instanced again).
 *
 * After instancing (assuming it occurred), cache is flushed on
 * instance data (DMA doesn't snoop the cache). Then, the
 * \ref RxPipelineNodePS2AllMatBridgeCallBack is called. This selects
 * which VU1 code should be used for this mesh and sets up material
 * properties (color, texture, etc) and this information is uploaded
 * to VU1. In addition, this callback may perform pipeline-specific
 * tasks that it needs to do every time a given mesh is rendered,
 * such as uploading values into the static memory area on VU1 (e.g.
 * a matrix for environment mapping). Finally, it kicks off the
 * transfer of geometry data to VU1 ('renders' the mesh). An
 * optional \ref RxPipelineNodePS2AllMatPostMeshCallBack is then
 * called to perform any necessary post-render, per-mesh tasks.
 *
 * After all meshes have been processed by PS2AllMat.csl material
 * pipelines, an optional \ref RxPipelineNodePS2AllObjectFinalizeCallBack
 * is called, to perform any necessary post-render, per-object
 * tasks.
 *
 * [Note for completeness: PS2AllMat.csl is never actually
 * executed, it is merely presented as if it is for convenience.
 * In reality, it just holds a list of parameters (such as
 * callbacks) for PS2All.csl to use per-mesh. This is why
 * PS2All.csl and PS2AllMat.csl are only compatible with each
 * other]
 *
 * This function should be called after unlocking the pipeline containing
 * this node.
 *
 * \param  self   A pointer to a PS2All.csl pipeline node
 * \param  type   An enum (\ref RxPipelineNodePS2AllCallBackType)
 *                identifying the callback type
 * \param  func   A pointer to the callback function,
 *                NULL sometimes valid
 *
 * \return a pointer to the input pipeline node on success, otherwise NULL.
 *
 * \see RxPipelineNodePS2AllMatSetCallBack
 * \see RxPipelineNodePS2AllMatGenerateCluster
 * \see RxPipelineNodePS2AllMatSetVUBufferSizes
 * \see RxPipelineNodePS2AllMatSetPointListVUBufferSize
 * \see RxPipelineNodePS2AllMatGetVUBatchSize
 * \see RxPipelineNodePS2AllMatGetVU1CodeArray
 * \see RxPipelineNodePS2AllMatSetVU1CodeArray
 * \see RxPipelineNodePS2AllMatSetVIFOffset
 * \see RxPipelineNodePS2AllGroupMeshes
 * \see RxNodeDefinitionGetPS2AllMat
 * \see RxNodeDefinitionGetPS2All
 */
RxPipelineNode *
RxPipelineNodePS2AllSetCallBack(RxPipelineNode *self,
                                RxPipelineNodePS2AllCallBackType type,
                                void *func)
{
    PRIVATEDATATYPE *data;

    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2AllSetCallBack"));

    if (NULL == self)
    {
        MESSAGE("RxPipelineNodePS2AllSetCallBack; NULL node passed");
        RWRETURN(NULL);
    }

    if (NULL == self->privateData)
    {
        MESSAGE("RxPipelineNodePS2AllSetCallBack; Node's private data pointer is NULL - only call this function after unlocking the containing pipeline");
        RWRETURN(NULL);
    }

    data = (PRIVATEDATATYPE *)self->privateData;

/*TODO[4]: A NULL CB MEANS "NO CB", NOT "DEFAULT CB". YOU NEED TO EXPOSE THE DEFAULT RW CBs,
 *        MAYBE VIA API FUNCS. YOU ALSO NEED TO EXPOSE RW DEFAULT CB SUB-COMPONENTS AS FUNCS/MACROS */
    switch(type)
    {
    case rxPS2ALLCALLBACKOBJECTSETUP:
        {
            if (NULL == func)
            {
                MESSAGE("RxPipelineNodePS2AllSetCallBack; ObjectSetup callback cannot be NULL");
                RWRETURN(NULL);
            }
            data->objectSetupCB = (RxPipelineNodePS2AllObjectSetupCallBack)func;
            RWRETURN(self);
            break;
        }
    case rxPS2ALLCALLBACKOBJECTFINALIZE:
        {
            data->objectFinalizeCB = (RxPipelineNodePS2AllObjectFinalizeCallBack)func;
            RWRETURN(self);
            break;
        }
    default:
        {
            MESSAGE("RxPipelineNodePS2AllSetCallBack; Unrecognised object callback type");
            RWRETURN(NULL);
            break;
        }
    }
}


/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2AllGroupMeshes is a post construction time
 * node API function used to cause a PS2All.csl node to process all
 * meshes in an object with the same material pipeline.
 *
 * The default behaviour of the PS2All.csl node is to process each
 * mesh in an object with the material pipeline specified in the mesh's
 * \ref RpMaterial. This function overrides that behaviour, by setting
 * a material pipeline which will be used for *all* meshes in an object
 * regardless of the value of materials in the object - it groups the
 * meshes together and treats them all in the same way. This may be
 * useful for efficiency or convenience.
 *
 * If NULL is passed to this function then the default PS2All.csl
 * behaviour is restored and the node will no longer 'group meshes'.
 *
 * This function should be called after unlocking the pipeline containing
 * this node.
 *
 * \param  self     A pointer to a PS2All.csl pipeline node
 * \param  pipeline A pointer to a material pipeline
 *
 * \return a pointer to the input pipeline node on success, otherwise NULL.
 *
 * \see RxPipelineNodePS2AllSetCallBack
 * \see RxPipelineNodePS2AllMatSetCallBack
 * \see RxPipelineNodePS2AllMatGenerateCluster
 * \see RxPipelineNodePS2AllMatSetVUBufferSizes
 * \see RxPipelineNodePS2AllMatSetPointListVUBufferSize
 * \see RxPipelineNodePS2AllMatGetVUBatchSize
 * \see RxPipelineNodePS2AllMatGetVU1CodeArray
 * \see RxPipelineNodePS2AllMatSetVU1CodeArray
 * \see RxPipelineNodePS2AllMatSetVIFOffset
 * \see RxPipelineNodePS2AllGroupMeshes
 * \see RxNodeDefinitionGetPS2AllMat
 * \see RxNodeDefinitionGetPS2All
 */
extern RxPipelineNode *
RxPipelineNodePS2AllGroupMeshes(RxPipelineNode *self, RxPipeline *pipe)
{
    PRIVATEDATATYPE *data;

    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2AllGroupMeshes"));

    if (NULL == self)
    {
        MESSAGE("RxPipelineNodePS2AllGroupMeshes; NULL node passed");
        RWRETURN(NULL);
    }

    if (NULL == self->privateData)
    {
        MESSAGE("RxPipelineNodePS2AllGroupMeshes; Node's private data pointer is NULL - only call this function after unlocking the containing pipeline");
        RWRETURN(NULL);
    }

    data = (PRIVATEDATATYPE *)self->privateData;

    /* NULL means don't group, non-NULL means group */
    data->groupPipe = pipe;

    RWRETURN(self);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxNodeDefinitionGetPS2All returns a pointer to the
 * "PS2All.csl", node definition, which is used to render RenderWare
 * (or custom) objects.
 *
 * This node does everything required to render an object. It can
 * do all that PS2Manager.csl (see \ref RxNodeDefinitionGetPS2Manager)
 * can do and more. This node will not pass packets on to subsequent
 * nodes in the pipeline nor will accept them from previous nodes.
 * It was designed to be the only node in a pipeline, for maximum
 * speed. [Note that the PVSData.csl node, if placed before PS2All.csl
 * in a pipeline, will still work since it doesn't use packets and
 * merely terminates the pipeline prematurely dependent on PVS data.]
 *
 * Unlike PS2Manager.csl, this node will take into account pipelines
 * specified in materials within an object. Such pipelines must be
 * constructed from the PS2AllMat.csl node only (see
 * \ref RxNodeDefinitionGetPS2AllMat). Using other nodes will simply
 * cause RenderWare to ASSERT and crash.
 *
 * On the PS2, instancing of data is performed in the material pipeline
 * on a per-mesh basis. This is because different material pipelines
 * can require data to be instanced into a different format, depending
 * on what the VU1 code (associated with the pipeline) requires.
 * PS2All.csl is thus not equivalent to AtomicInstance.csl or
 * WorldSectorInstance.csl which form the default generic atomic and
 * world sector instancing pipelines (so in this case "instancing
 * pipeline" isn't really an appropriate name). Instead, it does
 * per-object setup and then allows material pipelines to do instancing
 * and other work per-mesh.
 *
 * After object setup and instancing, mesh material properties (such
 * as texture) are uploaded to VU1 and the geometry is added to the
 * DMA transfer queue (the DMA engine may run several frames behind
 * the CPU).
 * 
 * Like PS2Manager.csl, this node is customisable by the setting of
 * callback functions. It (and PS2AllMat.csl) has many more such
 * overloadable callback functions than did PS2Manager.csl.
 * See \ref RxPipelineNodePS2AllSetCallBack and
 * \ref RxPipelineNodePS2AllMatSetCallBack for details (the former
 * also contains an overview of the flow of pipeline execution
 * through PS2All.csl and PS2AllMat.csl). Because of this,
 * PS2All.csl and PS2AllMat.csl should be the only nodes you
 * need to use on PS2 (prior nodes remain for legacy support), the
 * former for object pipelines and the latter for material pipelines.
 * Do not try to mix these nodes with PS2Manager.csl, PS2ObjAllInOne.csl,
 * PS2MatInstance.csl or PS2MatBridge.csl. They are incompatible.
 *
 * NOTE: When using PS2All.csl to construct an Im3D render pipeline,
 * you MUST use \ref RxPipelineNodePS2AllGroupMeshes to point to the
 * material pipeline which will be used - this is because there are
 * no materials in Im3D with which to locate material pipelines! The
 * Im3D transform pipeline is different altogether and it is not
 * recommended to try replacing it without assistance.
 *
 * \return A pointer to the \ref RxNodeDefinition on success, otherwise NULL
 *
 * \see RxPipelineNodePS2AllMatSetCallBack
 * \see RxPipelineNodePS2AllSetCallBack
 * \see RxPipelineNodePS2AllMatGenerateCluster
 * \see RxPipelineNodePS2AllMatSetVUBufferSizes
 * \see RxPipelineNodePS2AllMatSetPointListVUBufferSize
 * \see RxPipelineNodePS2AllMatGetVUBatchSize
 * \see RxPipelineNodePS2AllMatGetVU1CodeArray
 * \see RxPipelineNodePS2AllMatSetVU1CodeArray
 * \see RxPipelineNodePS2AllMatSetVIFOffset
 * \see RxPipelineNodePS2AllGroupMeshes
 * \see RxNodeDefinitionGetPS2AllMat
 */
RxNodeDefinition *
RxNodeDefinitionGetPS2All(void)
{
    /*************************************/
    /**                                 **/
    /**  PS2ALL.CSL NODE SPECIFICATION  **/
    /**                                 **/
    /*************************************/

#define NUMCLUSTERSOFINTEREST 0
#define NUMOUTPUTS            0

    static RwChar _PS2All_csl[] = RWSTRING("PS2All.csl");

    static RxNodeDefinition nodePS2AllCSL =
    {
        _PS2All_csl,                            /* Name */
        {                                       /* nodemethods */
            PS2AllNodeBody,                     /* +-- nodebody */
            (RxNodeInitFn) NULL,                /* +-- nodeinit */
            (RxNodeTermFn) NULL,                /* +-- nodeterm */
            PS2AllPipelineNodeInit,             /* +-- pipelinenodeinit */
            (RxPipelineNodeTermFn) NULL,        /* +-- pipelineNodeTerm */
            (RxPipelineNodeConfigFn) NULL,      /* +--  pipelineNodeConfig */
            (RxConfigMsgHandlerFn) NULL         /* +--  configMsgHandler */
        },
        {                                       /* Io */
            NUMCLUSTERSOFINTEREST,              /* +-- NumClustersOfInterest */
            NULL,                               /* +-- ClustersOfInterest */
            NULL,                               /* +-- InputRequirements */
            NUMOUTPUTS,                         /* +-- NumOutputs */
            NULL                                /* +-- Outputs */
        },
        (RwUInt32) sizeof(PRIVATEDATATYPE),     /* PipelineNodePrivateDataSize */
        rxNODEDEFCONST,                         /* editable */
        (RwInt32) 0                             /* inPipes */
    };

    /***************************************/

    RxNodeDefinition *result = &nodePS2AllCSL;

    RWAPIFUNCTION(RWSTRING("RxNodeDefinitionGetPS2All"));

    /*RWMESSAGE((RWSTRING("result %p"), result));*/

    RWRETURN(result);
}

