/* *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.84 OF nodePS2Manager.c
#### UP TO DATE WITH VERSION 1.90 OF nodePS2ObjAllInOne.c
#### UP TO DATE WITH VERSION 1.188 OF nodePS2MatInstance.c
#### UP TO DATE WITH VERSION 1.121 OF nodePS2MatBridge.c
####
#### SYNCHRONISATION
*/

/* TODO CLASSIFICATION SYSTEM:
 *  These TODOs have been left in PS2All code as a record of all improvements not
 * yet done. Most are likely worth doing. They also act as extra comments, giving
 * insight into reasoning, problems and possibilities.
 *
 * o "TODO[1]": [obsolete] stuff needed for the first pass of PS2All to compile/work
 * o "TODO[2]": not [1] but crucial to resolve before PS2All is released
 * o "TODO[3]": questions to be considered and deleted or made into a proper TODO
 * o "TODO[4]": stuff to improve PS2All's flexibility, details to be finalised later
 * o "TODO[5]": serious optimisations (memory/speed)
 * o "TODO[6]": not *critical*, but ideally resolved before PS2All is released
 * o "TODO[7]": non-PS2All-specific stuff needing to be resolved (like Im3D primitive splitting)
 * 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":  [obsolete] docs to be written before PS2All is released
 */

/*TODO[6]: REMOVE ALL PLATFORM-SPECIFIC TYPES -- int, etc -- AND REPLACE WITH RW
 *        TYPES [DO WE HAVE RwUInt128 TO USE?]. REMEMBER TO CHECK FOR CASTS
 *        TO CHANGE -- e.g [int]a */

/*TODO[3]: PVS... HOW WOULD IT BE IMPLEMENTED? IN THE ObjectSetupCB?
 *        WHAT ABOUT IT BEING ADDED TO ALL/ANY PIPELINES BY A TOGGLE FUNCTION? */

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

#include "rwcore.h"

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

#include "matputil.h"

#include "nodeps2objallinone.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"

#if (!defined(DOXYGEN))
static const char rcsid[] __RWUNUSED__ = 
    "@@@@(#)$Id: nodeps2all.c,v 1.49 2001/10/01 15:25:17 iestynb Exp $";
#endif /* (!defined(DOXYGEN)) */

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

#define PRIVATEDATATYPE rxNodePS2AllPvtData

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

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


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

/* The NULL light buffer is different for PS2All (we only use a VIF tag
 * at the start, not a DMA tag - saves time in openVU1SetupPktNew()) */
static RwUInt128 PS2AllNULLLightBlock[2];


/****************************************************************************
 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        *objPvtData;
/*TODO[3]: MAKE ps2AllPipeData STATIC SO STATE THINGS LIKE matModulate PERSIST? */
    RxPS2AllPipeData        ps2AllPipeData;
    RxPipeline             *defaultMatPipe;
    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? UGH, THAT'S WHAT POWERPIPE WAS
 *        GOOD AT... WHAT UNPLEASANT IRONY.
 *         - MORPHTARGET 'SCALE' VALUE, AN OBVIOUS EXAMPLE... PER-OBJECT-CALC'D
 *          VALUES REQUIRED FOR ALL MESH UPLOADS
 *         - WE WANT STATIC DATA RATHER THAN FORCING USING RxHeapAlloc, MAKE
 *          THIS 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, 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
 *                 IN THE STATICALLY ALLOCATED DATA.
 *
 *      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);
    objPvtData = (PRIVATEDATATYPE *)(self->privateData);
    RWASSERT(NULL != objPvtData);
    ps2AllPipeData.objPvtData = objPvtData;
    ps2AllPipeData.matPvtData = NULL;
    /* Set this up once per object, assume it doesn't change within callbacks :)
     * [aside: changing RpMaterialGetDefaultPipeline() (a macro in release)
     *  to use the *function* RpMaterialSkyGetPS2AllMatPipeline() causes
     *  a large *increase* in performance in the PS2All example, for all
     *  pipelines (including the PS2Manager one!). Just goes to show how
     *  important random cache effects can be!!] */
    defaultMatPipe = RpMaterialGetDefaultPipeline();
    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;

            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 == 0)
        {
            /* Clear the lighting buffer */
            _skyLightFillPos = (RwReal *)PS2AllNULLLightBlock;
            _skyLightQWordsWritten = sizeof(PS2AllNULLLightBlock)/sizeof(RwUInt128);
        }
        else
        {
            RwUInt64 tmp;
            RwUInt128 ltmp = 0;

            if (((RwInt32)_skyLightQWordsWritten) == -1)
            {
                /* We allow both the lighting data AND inverse lighting
                 * matrix to persist, so we do absolutely bugger all
                 * here! (useful for ambient/directional lights).
                 * Not even a VIF tag is uploaded in this case! */
                _skyLightQWordsWritten = 0;
            }
            else
            {
                if (((RwInt32)_skyLightQWordsWritten) == -5)
                {
                    RWASSERT(((RwInt32)_skyLightQWordsWritten) == -5);
                    /* Allow lighting data to persist - we're just uploading the
                     * inverse lighting matrix (-5), or nothing at all (-1)
                     * [see RpAtomicPS2AllLightingPersistMacro] */
                    _skyLightQWordsWritten = 4;
                }
                else
                {
                    RWASSERT(((RwInt32)_skyLightQWordsWritten) >= 0);

                    /* Terminate and finalise the light buffer */
                    ((RwUInt128 *)_skyLightFillPos)[0] = PS2AllNULLLightBlock[1];
                    _skyLightQWordsWritten++;
                }

                /* Finally, write the VIF transfer Tag at the top of the buffer.
                 * ["Header for return (with VIF TAG to upload to output buffer)"] */
                _skyLightFillPos = (RwReal *)lightBuffer;

                /* VIF tag to send the data to the appropriate place in VU1 */
                tmp = (((0x6CL << 24) |                           /* VIF unpack 4-32 */
                       ((RwUInt64)_skyLightQWordsWritten << 16) | /* Transfer numQW QWs */
                       VU1LIGHTOFFSET) << 32) |                   /* Destination address */
                      ((1L << 24) | (4 << 8) | 4);/* How to unpack: length 4W, stride 4W */
                MAKE128(ltmp, tmp, 0L);

                ((RwUInt128 *)_skyLightFillPos)[0] = ltmp;
                _skyLightQWordsWritten++;
            }
        }

        RWASSERT(RWMATRIXALIGNMENT(pxForm));

        /* Upload matrix, GIFTag (redone in matBridge - FIX) and light data */
    /*TODO[3]: FIRST COMMENT ABOVE SAYS WE OPEN A LOCAL PACKET... WHAT IF
     *        THE objectSetupCB OPENS ONE? DOES THAT BREAK THIS? */
        openVU1SetupPktNew(pxForm,
                           (const RwUInt128 *)_skyLightFillPos,
                           _skyLightQWordsWritten);

        RWASSERT(FALSE != success);
    }

    /* 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 = objPvtData->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)" */


    /*TODO[7]: TEMPORARY, FOR IM3D PRIMITIVE SPLITTING */
        if (NULL != curMatPvtData->im3DPreMeshCB)
        {
            success = curMatPvtData->im3DPreMeshCB(&ps2AllPipeData);
            RWASSERT(FALSE != success);
        }
im3dsplitloop:

        /* MATINSTANCE */

        success = TRUE;

    /*TODO[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
     *        FOUR TESTS IN THE FASTEST NON-PERSISTENT PATH! TOO MANY... */
        if (rxINSTANCEDONTINSTANCE != ps2AllPipeData.objInstance)
        {
        /*TODO[4]: INSTANCING FEATURES - NEED TO BE ABLE TO HANDLE:
         *        o OPAQUE USER DATA
         *        o QUICK-SWAP 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 *matPvtData;
            RwResEntry **repEntry = (RwResEntry **)NULL;
            rwPS2AllResEntryHeader *ps2ResHeader =
                (rwPS2AllResEntryHeader *)NULL;
            RwBool success;

            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 (PER-PIPE) 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. */

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

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

        /*TODO[3]: THE BELOW TEST (TO MAKE SURE USING A LINE/TRI PIPE FOR
         *      LINES/TRIS AND POINT FOR POINTLISTS) IS TEMPORARY, WE SHOULD
         *      HAVE A BETTER WAY WITH THE setVUBufferSizes FUNCS ROLLED INTO
         *      ONE AND DMA-CHAIN CONSTRUCTION CODE GENERALISED BEYOND
         *      TRILIST/TRISTRIP.
         *       RABIN POINTS OUT THAT IF YOU TRISTRIP AN ORIGINALLY TRILIST
         *      ATOMIC (WHY AT RUNTIME???), YOU EXPECT THE DEFAULT PIPELINES
         *      TO JUST WORK... YOU EXPECT TO BE ABLE TO APPLY THE DEFAULT
         *      PIPELINES TO ORDINARY ATOMICS (STRIPPED/NOT) AND IT JUST WORKS.
         *       VERTEX FORMAT DESCRIPTORS (VERTEX STRUCTURE) SHOULD IGNORE
         *      PRIMTYPE (VERTEX ORDER).
         *       PRIMTYPE HELPER LUTS SHOULD BE EXPOSED AS GLOBALS WHICH USERS
         *      *CAN* USE. THESE WILL BE USED IN THE CODE THAT CONSTRUCTS DMA
         *      CHAINS, AS WELL AS THE DEFAULT INSTANCE FUNCTIONS. WE'LL ONLY
         *      EVER SUPPORT 5 TYPES - POINTLISTS CAN BE USED FOR 'NEW' TYPES.
         *       IT'S UP TO USER REINSTANCE CALLBACKS TO BE ABLE TO HANDLE ALL
         *      THE PRIMTYPES THAT THEIR MATERIAL PIPES ARE APPLIED TO. OUR
         *      DEFAULT CALLBACKS WILL USE THE LUTS AND PREDICATE ON PRIMTYPE
         *      IN THE ObjectSetupCB (STORE THE RESULT WHERE?) OR InstanceCB.
         *      WE WANT COMMON CODE WHICH IS DATA-DRIVEN, TO REDUCE PREDICATION
         *      (LIKEWISE DEAL WITH ALL CLUSTER TYPES IN A UNIFORM, DATA-DRIVEN
         *      MANNER).
         *       THE PROBLEM OF THE 'triList' AND 'triStrip' DATA IN PS2ALLMAT
         *      PRIVATE DATA REMAINS - THESE ARE PRECALC'D PER PRIMTYPE PER PIPE,
         *      NOT PER OBJECT, SO WE NEED THE VALUES TO BE PRESENT FOR MULTIPLE
         *      PRIMTYPES GIVEN PIPES (THE RW DEFAULT PIPES AT LEAST) CAN BE
         *      APPLIED TO OBJECTS WITH DIFFERING PRIMTYPES.
         *       THE VU CODE ARRAY... NOW IT'S GENERALISED, PEOPLE SUPPORT AS
         *      MANY PRIMTYPES AS THEY LIKE... PREDICATING IN THE BridgeCB. */

#if (defined(RWDEBUG))
            if ((matPvtData->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 = (RxInstanceFlags)
                (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 != matPvtData->meshInstanceTestCB)
                {
                    /* Test/update meshIdentifier and set final reinstance flags */
                    success = matPvtData->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 = (RxInstanceFlags)
                    ( 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)     )
                {
                    RwUInt128 *data;

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

                /*TODO[2]: FIX THE CONGRUENT(PARTIAL) REINSTANCE BUG (HERE AND
                 *      PROPOGATE TO PS2MANAGER), 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:
                     *   IN-VU POLYLINES MIGHT BE A REASONABLE THING TO ASK FOR
                     *  SO WE SHOULD MAKE SURE THE VERTEX FORMAT DESCRIPTOR 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 VERT DUPLICATION ALA TRISTRIPS.
                     *  ARGH, CAN'T WE 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? OR IS THAT
                     *      THE OBJECTSETUPCB'S JOB? WHAT ABOUT EMPTY MESHES? */
                        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;
                    }

                /*TODO[4][5]: IN THE ALL-NON-OPAQUE CASE (TAGS IN A CONTIGUOUS
                 *   BUNCH AT THE TOP OF THE RESENTRY) SHOULD WE RECREATE THE
                 *  DMA CHAIN FROM SCRATCH OR COPY THE OLD ONE AND FIX UP ITS
                 *  TAGS? */
                    /* Allocate and Create the structure of the new resEntry - DMA tags and wotnot */

                    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 != matPvtData->resEntryAllocCB);
                   *repEntry = matPvtData->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 DO "*ps2ResHeader = *(ps2AllPipeData.ps2ResHeader);"
                 *  OR "ps2ResHeader->subBlock = ps2AllPipeData.ps2ResHeader;" */

                /*TODO[3]: SHOULD WE 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]: HOPEFULLY THIS CAN BE DELETED ONCE WE HAVE
                 *   PER-PIPE VERTEX FORMAT DESCRIPTORS. THESE NEEDN'T BE
                 *  STORED WITH THE RESHEADER SO WE DON'T NEED TO DO MUCH OF
                 *  THIS CACHING. ASSERT ON CHANGING DESCRIPTORS IF POSS. */
#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[5]: THE RESHEADER ELEMENTS WHICH 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;

#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 IM3D OR
                 *   WORLDSECTORS? 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 */
                    data = (RwUInt128 *)
                        (((RwUInt32)(ps2ResHeader + 1) + 63) & ~63);
#if (defined(DMAALIGN))
                    if (matPvtData->totallyOpaque)
                    {
                        data = (RwUInt128 *)
                            (((RwUInt32)data + 127) & ~0x7f);
                    }
#endif /* (defined(DMAALIGN)) */
                    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
                            = &(matPvtData->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 == matPvtData->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 != matPvtData->instanceCB);

                numStripes = matPvtData->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 -> INTO 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 = matPvtData->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);
        success = ps2AllPipeData.matPvtData->bridgeCB(&ps2AllPipeData);
        RWASSERT(FALSE != success);

    /*TODO[7]: TEMPORARY, FOR IM3D PRIMITIVE SPLITTING */
        if (NULL != curMatPvtData->im3DPostMeshCB)
        {
            success = curMatPvtData->im3DPostMeshCB(&ps2AllPipeData);
            RWASSERT(FALSE != success);
        }

        mesh++;

    /*TODO[7]: TEMPORARY, FOR IM3D PRIMITIVE SPLITTING */
        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 != objPvtData->objectFinalizeCB)
    {
        success = objPvtData->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 rpworldp2sky2
 * \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.
 *
 * For the default RxPipelineNodePS2AllObjectSetupCallBack for
 * RpAtomics, \ref RpAtomicPS2AllObjectSetupCallBack, a macro is
 * called (\ref RpAtomicPS2AllClear) to clear flags in the
 * contained RpGeometry relating to reinstancing, once the
 * instance tests have been performed. 
 *
 * The primType field should 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. primType may be set up in an
 * \ref RxPipelineNodePS2AllMatBridgeCallBack instead of here
 * if that is so desired (this could feasibly be useful if,
 * say, frustum-testing is being performed on a per-mesh
 * basis). 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 may be
 * 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). If you wish for the
 * matrix currently in VU1 memory to persist, you may set the
 * pointer to the matrix to be NULL (the transform parameter is
 * doubly indirected). This is a useful performance win for
 * geometry which is specified in world-space or camera-space.
 *
 * 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.
 * You may in some cases use \ref RpAtomicPS2AllLightingPersist in a user
 * callback, as an optimisation for either RpAtomics or RpWorldSectors.
 * 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: 
 *
 * meshHeader, meshCache
 *
 *
 * The default values of RxPS2AllPipeData members are: 
 * \verbatim
   transform = UNDEFINED, (or persistent)
   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 rpworldp2sky2
 * \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. No RenderWare objects
 * currently use a callback of this type in their default
 * pipelines, though it has been used in the past and is included
 * because it may still prove to be of use to developers.
 *
 * 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
 * 
 * (no \ref RxPipelineNodePS2AllObjectFinalizeCallBack is used
 *  by default for RpAtomics, RpWorldSectors or RwIm3D)
 *
 *
 * 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;

    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
 */
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 object pipelines. 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));*/

    /* Set up the PS2All-specific NULL lighting block */
    {
        RwUInt64 tmp;

        /* No lights */
        ((RwUInt32 *)&PS2AllNULLLightBlock[1])[0] = 0;
        ((RwUInt32 *)&PS2AllNULLLightBlock[1])[1] = 0;
        ((RwUInt32 *)&PS2AllNULLLightBlock[1])[2] = 0;
        ((RwUInt32 *)&PS2AllNULLLightBlock[1])[3] = (RwUInt32)rpNALIGHTTYPE;

        /* Just transfer the above QW to 'clear' the lighting buffer */
        tmp = (((0x6CL << 24) |                /* VIF unpack 4-32 */
                (1L << 16) |                   /* Transfer 1 QW */
                (VU1LIGHTOFFSET + 4)) << 32) | /* Skip the matrix, irrelevant */
              ((1L << 24) | (4 << 8) | 4);/* How to unpack: length 4W, stride 4W */
        MAKE128(PS2AllNULLLightBlock[0], tmp, 0L);
    }

    RWRETURN(result);
}

