/* *INDENT-OFF* */

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

/****************************************************************************
 *                                                                          *
 * module : ps2allmat.c                                                     *
 *                                                                          *
 * purpose: A fake node for PS2All material pipes - it just holds pvtData   *
 *                                                                          *
 ****************************************************************************/

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


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

#include "rwcore.h"

#include "matinstance.h"
#include "ps2allatomic.h"


/* nodePS2All.h includes this anyway, but... */
#include "ps2allmat.h"
#include "nodePS2All.h"


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

#define PRIVATEDATATYPE rxNodePS2AllMatPvtData
#define INITDATATYPE    rxNodePS2AllMatInitData

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

/* Get CL_CODE strings for error messages */
#define GETCLCODESTRING(_cl_code, _string)  \
MACRO_START                                 \
{                                           \
    switch(_cl_code)                        \
    {                                       \
    case CL_XYZ:                            \
        strcpy(_string, "XYZ");             \
        break;                              \
    case CL_UV:                             \
        strcpy(_string, "UV");              \
        break;                              \
    case CL_UV2:                            \
        strcpy(_string, "UV2");             \
        break;                              \
    case CL_RGBA:                           \
        strcpy(_string, "RGBA");            \
        break;                              \
    case CL_NORMAL:                         \
        strcpy(_string, "Normal");          \
        break;                              \
    case CL_USER1:                          \
        strcpy(_string, "User1");           \
        break;                              \
    case CL_USER2:                          \
        strcpy(_string, "User2");           \
        break;                              \
    case CL_USER3:                          \
        strcpy(_string, "User3");           \
        break;                              \
    case CL_USER4:                          \
        strcpy(_string, "User4");           \
        break;                              \
    default:                                \
        strcpy(_string, "!UNKNOWN!");       \
        break;                              \
    }                                       \
}                                           \
MACRO_STOP

/* VU related defines. These really come from elsewhere */
//TODO[7]: THESE VALUES DON'T REALLY SERVE ANY USEFUL PURPOSE DO THEY? RATIONALISE....
#define VU1_MAX_TS_INPUT_SIZE 256
#define VU1_MAX_TL_INPUT_SIZE 64

/* Seems a reasonable value given TS size is in vertices not triangles */
#define VU1_MAX_PL_INPUT_SIZE VU1_MAX_TS_INPUT_SIZE

/****************************************************************************
 Static prototypes
*/

static RwBool
PS2AllMatProcessInitData(RxPipelineNode *self);


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

   Functions

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


/****************************************************************************
 PS2AllMatPipelineNodeInit

 Defaults to setting up CBs for sectors/atomics - default per-mesh callbacks
 are the same for both (not so Im3D).
*/
static RwBool
PS2AllMatPipelineNodeInit(RxPipelineNode *self)
{
    PRIVATEDATATYPE *pvtData;
    RwBool result;

    RWFUNCTION(RWSTRING("PS2AllMatPipelineNodeInit"));

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

    /* Set up batches and clusters (also verify settings of latter) */
    result = PS2AllMatProcessInitData(self);
    if (result == FALSE) RWRETURN(result);

    RWASSERT((void **)NULL != skyVU1Transforms);
    pvtData->vu1CodeArray = (void **)(&skyVU1Transforms[0]); /* driver default */
    pvtData->codeArrayLength = VU1CODEARRAYSIZE;
    /* Use the vifOffset the default pipes will use */
    pvtData->vifOffset = _rwskyVIFOffset;

    /* Initialize CBs to NULL so people have to set theirs explicitly */
    pvtData->im3DPreMeshCB      = NULL;
    pvtData->meshInstanceTestCB = NULL;
    pvtData->resEntryAllocCB    = NULL;
    pvtData->instanceCB         = NULL;
    pvtData->bridgeCB           = NULL;
    pvtData->im3DPostMeshCB     = NULL;
    pvtData->im3DMeshSplitCB    = NULL;
    pvtData->postMeshCB         = NULL; /* Empty unlessdef(RWMETRICS) */

    /* So we can tell this is the right type of node */
    pvtData->magicValue = PS2ALLMATMAGICVALUE;

    RWRETURN(result);
}

/****************************************************************************
 PS2AllMat
 */
static RwBool
PS2AllMatNodeBody(RxPipelineNodeInstance * __RWUNUSED__ self,
                  const RxPipelineNodeParam * __RWUNUSED__ params)
{
    RWFUNCTION(RWSTRING("PS2AllMatNodeBody"));

    /* This is a fake node, it should only be used ot hold private data */
    RWASSERT("This should not be" == "being called!!");

    RWRETURN(FALSE);
}


/* 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 RxPipelineNodePS2AllMatMeshInstanceTestCallBack is the callback to be
 * called, for the owning PS2All.csl pipeline node, to decide whether a mesh
 * should be reinstanced or not.
 * 
 * To control mesh instancing, set up meshInstance and meshIdentifier.
 * meshIdentifier is a value stored in the rwPS2AllResEntryHeader
 * structure which heads up instance data and which can be inspected
 * to determine if a mesh has changed sufficiently, since it was last
 * instanced, to merit its being reinstanced. How you construct this
 * value is up to you. The default RenderWare callback (used by the
 * default RpAtomic and RpWorldSector pipelines - in RwIm3D there's
 * no need for testing since we are instancing from scratch every time
 * we render), \ref RpMeshPS2AllMeshInstanceTestCallBack uses the mesh
 * header's flags.
 *
 * objInstance is of type \ref RxInstanceFlags and an overview
 * of its use is given here: \ref RxPipelineNodePS2AllSetCallBack.
 * In order to retrieve the value meshIdentifier 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 a mesh
 * should be reinstanced in-place, congruently, fully or not at all.
 *
 * The meshInstance member will be initialized to the value of the
 * objInstance member (ORed with rxINSTANCEDONTINSTANCE to assure
 * sensible default behaviour), so if this callback is set to NULL
 * (it is initialized to NULL when the pipeline is unlocked), the
 * decision made by the \ref RxPipelineNodePS2AllObjectSetupCallBack
 * will be used. Even if present, this callback will only be called if
 * objInstance isn't set to rxINSTANCEDONTINSTANCE (see the overview
 * of instance testing in the documentation for
 * \ref RxPipelineNodePS2AllSetCallBack).
 *
 * The number of vertices in the instance data of a mesh (a vertex being
 * a dereferenced index) should also cause a full reinstance if it
 * changes. numVerts is stored in the rwPS2AllResEntryHeader and may be
 * calculated using the macro RPMESHPS2ALLCALCNUMVERTS.
 *
 * The default RxPipelineNodePS2AllMatMeshInstanceTestCallBack for
 * RpAtomics and RpWorldSectors is \ref RpMeshPS2AllMeshInstanceTestCallBack.
 * RwIm3D uses no such callback, since in its case instancing starts from
 * scratch every render. The documentation for
 * \ref RpMeshPS2AllMeshInstanceTestCallBack 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.
 *
 * In summary, the things this callback can (optionally) set up are:
 *
 * meshIdentifier,
 * meshInstance
 *
 *
 * The default values of these things are:
 *
 * meshIdentifier = 0,
 * meshInstance = (objInstance | rxINSTANCEDONTINSTANCE)
 *
 *
 * \param  ps2AllPipeData A pointer to a \ref RxPS2AllPipeData struct
 *                        containing information relevant to the
 *                        current pipeline execution
 *
 * \return TRUE on success, otherwise FALSE
 *
 * \see RxPipelineNodePS2AllMatSetCallBack
 * \see RxPipelineNodePS2AllMatResEntryAllocCallBack
 * \see RxPipelineNodePS2AllMatInstanceCallBack
 * \see RxPipelineNodePS2AllMatBridgeCallBack
 * \see RxPipelineNodePS2AllMatPostMeshCallBack
 * \see RxPipelineNodePS2AllSetCallBack
 */
typedef RwBool (*RxPipelineNodePS2AllMatMeshInstanceTestCallBack)
    (RxPS2AllPipeData *ps2AllPipeData);

/**
 * \ingroup rxpipelinenodeps2all
 * \ref RxPipelineNodePS2AllMatResEntryAllocCallBack is the callback to be
 * called, for the owning PS2All.csl pipeline node, to allocate memory for
 * a mesh's instance data.
 *
 * If the callback allocates memory with \ref RwResourcesAllocateResEntry
 * then the destroyNotify parameter must be passed to it (in addition
 * to the doubly-indirected \ref RwResEntry pointer). The purpose of
 * destroyNotify is to ensure that all DMA transfers involving the
 * allocated block of memory have been completed before the memory is
 * freed. If you allocate your own memory (note that it must be headed
 * up by a \ref RwResEntry structure, with 'size' bytes after that)
 * then you should use this callback in your free function to ensure
 * the same (if this is unfeasible, the mechanism is to loop until
 * the refCnt member of the rwPS2AllResEntryHeader in the instance data
 * becomes zero. To access this, dereference the meshCache member and
 * extract the rwPS2AllResEntryHeader with the macro
 * RWPS2ALLRESENTRYHEADERFROMRESENTRY).
 *
 * The default RxPipelineNodePS2AllMatResEntryAllocCallBack for
 * RpAtomics and RpWorldSectors is \ref RpMeshPS2AllResEntryAllocCallBack.
 * For RwIm3D, the default is \ref RwIm3DPS2AllResEntryAllocCallBack. The
 * documentation for these defaults list the macros from which they are
 * constructed so that you may (partially or fully) reconstruct them 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 required for PS2AllMat.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 if any instancing
 * occurs (this may not happen in one particular circumstance - if you
 * are using 'persistent instance data' - 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).
 *
 * \param  ps2AllPipeData A pointer to a \ref RxPS2AllPipeData struct
 *                        containing information relevant to the
 *                        current pipeline execution
 * \param  repEntry       A pointer to a pointer to a \ref RwResEntry
 * \param  size           A \ref RwUInt32 value specifying the size
 *                        in bytes of the memory block to allocate
 *                        (excluding the \ref RwResEntry header).
 * \param  destroyNotify  A \ref RwResEntryDestroyNotify callback
 *                        (see \ref RwResourcesFreeResEntry)
 *
 * \return A pointer to a \ref RwResEntry on success, NULL otherwise
 *
 * \see RxPipelineNodePS2AllMatSetCallBack
 * \see RxPipelineNodePS2AllMatMeshInstanceTestCallBack
 * \see RxPipelineNodePS2AllMatInstanceCallBack
 * \see RxPipelineNodePS2AllMatBridgeCallBack
 * \see RxPipelineNodePS2AllMatPostMeshCallBack
 * \see RxPipelineNodePS2AllSetCallBack
 */
typedef RwResEntry *(*RxPipelineNodePS2AllMatResEntryAllocCallBack)
    (RxPS2AllPipeData *ps2AllPipeData,
     RwResEntry **repEntry,
     RwUInt32 size,
     RwResEntryDestroyNotify destroyNotify);

/**
 * \ingroup rxpipelinenodeps2all
 * \ref RxPipelineNodePS2AllMatInstanceCallBack is the callback to be
 * called, for the owning PS2All.csl pipeline node, to instance a mesh.
 *
 * The instance callback will receive an array of pointers to cluster
 * data for all 'visible' clusters  - those that have CL_ATTRIB_READ
 * or CL_ATTRIB_WRITE specified in their attributes (these are the
 * clusters that can be filled in by user CPU-side code). Even if
 * there are no visible clusters, the callback will still be called,
 * because non-visible, standard clusters may need instancing.
 * Functions are available (for use in this callback) to instance the
 * standard cluster types for you (for RpAtomics, RpWorldSectors and
 * RwIm3D), so that you only have to instance user clusters. These are:
 *
 * RpAtomicPS2AllInstance, RpWorldSectorPS2AllInstance, RwIm3DPS2AllInstance
 *
 *
 * Visible clusters will be 'broken out' from the usual (complex,
 * packed) format of DMA packet data such that their data is
 * accessible as a simple, contiguous array. A cluster specified
 * as CL_ATTRIB_READ or CL_ATTRIB_WRITE will be visible and this
 * callback will receive a pointer to its data when instancing occurs. 
 *
 * Clusters flagged with CL_ATTRIB_DONT_FILL or CL_ATTRIB_PLACEHOLDER
 * will be skipped by these default instance functions and you will
 * have to instance data for the former yourself. Clusters marked as
 * CL_ATTRIB_PLACEHOLDER don't need instance data (see
 * \ref RxPS2ClusterAttrib for details of cluster attribute flags).
 *
 * The default instancing functions can instance from meshes containing
 * any \ref RwPrimitiveType, indexed or unindexed. User-supplied callbacks,
 * however, need only handle meshes of the types used by the user.
 *
 * Note also that the default RenderWare atomic instance function sets
 * up three members of the rwPS2AllResEntryHeader at the top of instance
 * data, so that these can be used in reinstance tests (morphing is
 * such that instancing must occur when the morph targets in use
 * change). It sets morphStart (the start morph target), morphFinish
 * (the end morph target) and morphNum (the number of morph targets
 * in the object). These are initialized, outside this function, to
 * zero, zero and one, respectively, by default.
 *
 * If clusters flagged with CL_ATTRIB_DONT_FILL are not flagged with
 * CL_ATTRIB_READ or CL_ATTRIB_WRITE then you will not receive a
 * pointer to their data because it will be in the opaque internal
 * format. It is possible (and there are DMA transfer efficiency
 * benefits. Note that opaque user clusters are not currently
 * supported) to instance into this format but it is complicated and
 * not not recommended without assistance. For the brave of heart,
 * here is a brief description of how to traverse opaque instance data:
 *
 * First, access the triStrip or triList members of the PS2AllMat.csl
 * node's private data (triList should be used for pointLists and
 * lineLists, note that polyLines and triFans are not supported on
 * VU1 and are instanced into lineLists and triLists respectively).
 * batchSize gives the number of vertices per batch, where this is
 * equal to the number of indices (we 'dereference' indices when
 * instancing, we onyl deal with vertices on VU1). Note that for
 * triStrips, the first two vertices of a batch MUST be the same as
 * the last two vertices of the previous batch, so that strips can
 * continue across batches). 
 *
 * The fieldRec array facilitates instance data traversal for each
 * cluster (order given by \ref RxPS2ClusterType). Within this,
 * numVerts is batchSize again (it is different for non-opaque
 * clusters), dataoffset is the offset from the start of the
 * instance data (in QuadWords) to the start of the first batch
 * of data for this cluster (instance data is pointed to by the
 * data member of the rwPS2AllResEntryHeader at the head of
 * instance data - get it using RWPS2ALLRESENTRYHEADERFROMRESENTRY).
 * Skip is the (QuadWord) offset from the beginning of a cluster's
 * data in one batch to the next. Reverse is a negative (QuadWord)
 * offset used for the last batch (in which the data of all the
 * clusters may be smaller, so they need to be shuffled back to
 * pack together). DMA and VIF tags are placed in-between batches
 * of cluster data, so be careful not to overrun!
 *
 * Members beginning with 'morph' are to be used for morphing objects
 * (those which have more than one morphTarget and currently have
 * differing start and end targets). Internally, a second XYZ cluster
 * is created, if XYZs are present, in slot CL_MAXCL. This is for the
 * 'end' morph target. If NORMALs are present, (CL_MAXCL+1) will be
 * used for a second version of those. If you are doing the instancing
 * for XYZs or NORMALs then you will also have to (for morphing objects)
 * do the instancing for the second set of each.
 * 
 * The default RxPipelineNodePS2AllMatInstanceCallBack for
 * RpAtomics and RpWorldSectors is \ref RpMeshPS2AllInstanceCallBack.
 * For RwIm3D, the default is \ref RwIm3DPS2AllInstanceCallBack. The
 * documentation for these defaults list the macros and sub-functions
 * from which they are constructed so that you may (partially or fully)
 * reconstruct them 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 PS2AllMat.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
 * \param  clusterData    An array of void pointers to the data of
 *                        visible clusters
 * \param  numClusters    The length of the clusterData array
 *
 * \return TRUE on success, FALSE otherwise
 *
 * \see RxPipelineNodePS2AllMatSetCallBack
 * \see RxPipelineNodePS2AllMatMeshInstanceTestCallBack
 * \see RxPipelineNodePS2AllMatResEntryAllocCallBack
 * \see RxPipelineNodePS2AllMatBridgeCallBack
 * \see RxPipelineNodePS2AllMatPostMeshCallBack
 * \see RxPipelineNodePS2AllSetCallBack
 */
typedef RwBool (*RxPipelineNodePS2AllMatInstanceCallBack)
    (RxPS2AllPipeData *ps2AllPipeData, void **clusterData, RwUInt32 numClusters);

/**
 * \ingroup rxpipelinenodeps2all
 * \ref RxPipelineNodePS2AllMatBridgeCallBack is the callback to be
 * called, for the owning PS2All.csl pipeline node, before the mesh's
 * instance data is dispatched to VU1 for rendering.
 *
 * This callback should be used to upload various pieces of information
 * to VU1, including (finally) the mesh's instance data. This involves
 * constructing a DMA packet, which is quite involved - so there are
 * several macros provided to construct this packet for you. In summary,
 * you should call \ref RpMeshPS2AllAsyncTextureUpload and then
 * \ref RpMeshPS2AllSyncTextureUpload to upload your texture first of
 * all. Then, you should open the VU1 uploads (VIF) packet with
 * \ref RpMeshPS2AllStartVIFUploads, perform several uploads (individual
 * macros are listed below) and finally call \ref RpMeshPS2AllEndVIFUploads
 * to close the packet and initiate dispatch of your mesh's instance data
 * to VU1. These last two are the only obligatory function calls which you
 * must make in your RxPipelineNodePS2AllMatBridgeCallBack (if they are
 * omitted then there won't be a crash, but nothing will be rendered since
 * the mesh instance data will not be dispatched to VU1), all others are
 * optional (in general, if they are omitted then prior state will persist).
 *
 * \ref RpMeshPS2AllStartVIFUploads opens a VIF packet and the following
 * macros will add small uploads to this packet:
 *
 * \li \ref RpMeshPS2AllGIFTagUpload,
 * \li \ref RpMeshPS2AllMatColUpload,
 * \li \ref RpMeshPS2AllSurfPropsUpload,
 * \li \ref RpMeshPS2AllClipInfoUpload,
 * \li \ref RpMeshPS2AllTextureStateUpload,
 * \li \ref RpMeshPS2AllVU1CodeIndexSetup,
 * \li \ref RpMeshPS2AllVU1CodeUpload
 *
 * See the documentation for these individual macros for details of what they
 * do (\ref RpMeshPS2AllStartVIFUploads gives an overview of the uploads
 * process). They should be called in the order listed above (though some or
 * all may be omitted) . Most importantly, \ref RpMeshPS2AllVU1CodeUpload
 * MUST be called last (directly before \ref RpMeshPS2AllEndVIFUploads).
 * 
 * See the documentation for \ref RpMeshPS2AllStartVIFUploads for details on
 * how to perform further VIF uploads (beyond the default ones provided for
 * by the macros listed above) in your RxPipelineNodePS2AllMatBridgeCallBack.
 *
 * Note that the macros listed above will be functions in a debug build.
 * You may explicity select a function or macro version (depending on how
 * you want to balance code size and function call overheads) by appending
 * 'Func' or 'Macro' to the end of the name. For example, to select the
 * function version of \ref RpMeshPS2AllMatColUpload, you would call
 * 'RpMeshPS2AllMatColUploadFunc'. The macros contain RWASSERT, so they
 * must be used within functions using RWFUNCTION and RWRETURN.
 *
 * The macros use several values in the \ref RxPS2AllPipeData struct.
 * These all have valid defaults, which may be overridden. The default
 * values of RxPS2AllPipeData members used in the macros are:
 * \verbatim
   vu1CodeIndex = ps2AllPipeData.transType &
                (rxSKYTRANSTYPEFOG | rxSKYTRANSTYPECLIP | rxSKYTRANSTYPELIST |
                 rxSKYTRANSTYPEISO | rxSKYTRANSTYPELINE),
   texture = mesh->material->texture,
   surfProps = &(material->surfaceProps),
   matCol = (ps2AllPipeData->matModulate) ? mesh->material->color : {255, 255, 255, 255}
   \endverbatim
 *
 * The default RenderWare RxPipelineNodePS2AllMatBridgeCallBack function
 * for RpAtomics and RpWorldSectors is RpMeshPS2AllBridgeCallBack and
 * for RwIm3D is RwIm3DPS2AllBridgeCallBack. The documentation for these
 * defaults list the macros from which they are constructed so that you
 * may construct replacement callbacks with your own modifications.
 *
 * This callback is required for PS2AllMat.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
 *
 * \return TRUE on success, FALSE otherwise
 *
 * \see RpMeshPS2AllStartVIFUploads
 * \see RpMeshPS2AllEndVIFUploads
 * \see RpMeshPS2AllGIFTagUpload
 * \see RpMeshPS2AllMatColUpload
 * \see RpMeshPS2AllSurfPropsUpload
 * \see RpMeshPS2AllClipInfoUpload
 * \see RpMeshPS2AllTextureStateUpload
 * \see RpMeshPS2AllVU1CodeIndexSetup
 * \see RpMeshPS2AllVU1CodeUpload
 * \see RpMeshPS2AllAsyncTextureUpload
 * \see RpMeshPS2AllSyncTextureUpload
 * \see RxPipelineNodePS2AllMatSetCallBack
 * \see RxPipelineNodePS2AllMatMeshInstanceTestCallBack
 * \see RxPipelineNodePS2AllMatResEntryAllocCallBack
 * \see RxPipelineNodePS2AllMatInstanceCallBack
 * \see RxPipelineNodePS2AllMatPostMeshCallBack
 * \see RxPipelineNodePS2AllSetCallBack
 */
typedef RwBool (*RxPipelineNodePS2AllMatBridgeCallBack)
    (RxPS2AllPipeData *ps2AllPipeData);

/**
 * \ingroup rxpipelinenodeps2all
 * \ref RxPipelineNodePS2AllMatPostMeshCallBack is the callback to be
 * called, for the owning PS2All.csl pipeline node, after the mesh's
 * instance data is dispatched to VU1 for rendering.
 *
 * This callback may be used to perform tasks necessary after the
 * rendering of a mesh. The default RenderWare callback,
 * \ref RpMeshPS2AllPostMeshCallBack, accumulates metrics information,
 * so replacement callbacks must do so also if metrics information is to
 * be accurate. The documentation for this default callback list the macros
 * from which it is constructed so that you may (partially or fully)
 * reconstruct the default callback 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
 * 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 RxPipelineNodePS2AllMatSetCallBack
 * \see RxPipelineNodePS2AllMatMeshInstanceTestCallBack
 * \see RxPipelineNodePS2AllMatResEntryAllocCallBack
 * \see RxPipelineNodePS2AllMatInstanceCallBack
 * \see RxPipelineNodePS2AllMatBridgeCallBack
 * \see RxPipelineNodePS2AllSetCallBack
 */
typedef RwBool (*RxPipelineNodePS2AllMatPostMeshCallBack)
    (RxPS2AllPipeData *ps2AllPipeData);


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


/**********************************************************************
 PS2AllMatCreateInitData
 */
static RwBool
PS2AllMatCreateInitData(RxPipelineNode *self)
{
    INITDATATYPE *data;

    RWFUNCTION(RWSTRING("PS2AllMatCreateInitData"));

    data = (INITDATATYPE *)
        RxPipelineNodeCreateInitData(self, sizeof(INITDATATYPE));
    if (data != NULL)
    {
        RwUInt32 i;
        for (i = 0;i < CL_MAXCL;i++)
        {
            data->clusters[i] = NULL;
            data->pipeType    = (RpMeshHeaderFlags)0;
            /* Use the values the default pipes will use */
            data->sizeOnVU    = _rwskyStrideOfInputCluster;
            data->vu1MaxTLInputSize = _rwskyTLTriCount;
            data->vu1MaxTSInputSize = _rwskyTSVertexCount;
            data->vu1MaxPLInputSize = _rwskyTSVertexCount;
        }
        RWRETURN(TRUE);
    }

    RWRETURN(FALSE);
}

/**********************************************************************
 PS2AllMatProcessInitData
 */
static RwBool
PS2AllMatProcessInitData(RxPipelineNode *self)
{
    PRIVATEDATATYPE *pvtdata;
    INITDATATYPE *initData;
    RwUInt32 numRequiredClusters = 0;
    RwBool result = FALSE;

    RWFUNCTION(RWSTRING("PS2AllMatProcessInitData"));

    RWASSERT( self != NULL );

    pvtdata  = (PRIVATEDATATYPE  *)self->privateData;
//TODO[7]: SHOULD REALLY HAVE RxPipelineNodeGetPrivateData TO MIRROR RxPipelineNodeGetInitData
    initData = (INITDATATYPE *)RxPipelineNodeGetInitData(self);

    RWASSERT( pvtdata != NULL );

    if (initData != NULL)
    {
        RwUInt32 CL_code;

        pvtdata->sizeOnVU = initData->sizeOnVU;
        if (0 == pvtdata->sizeOnVU)
        {
            MESSAGE("PS2AllMatProcessInitData; zero clusters generated! (currently not allowed)");
            RWRETURN(FALSE);
        }
        pvtdata->vu1MaxTSInputSize = initData->vu1MaxTSInputSize;
        pvtdata->vu1MaxTLInputSize = initData->vu1MaxTLInputSize;
        pvtdata->vu1MaxPLInputSize = initData->vu1MaxPLInputSize;
        pvtdata->pipeType          = initData->pipeType;
        if (pvtdata->pipeType == 0)
        {
            /* Default to triangles/lines, not points */
            pvtdata->pipeType = (RpMeshHeaderFlags) 
                (rpMESHHEADERTRISTRIP|rpMESHHEADERTRIFAN|
                 rpMESHHEADERLINELIST|rpMESHHEADERPOLYLINE);
        }

        pvtdata->numStripes = 0;
        for (CL_code = 0;CL_code < ((int)(CL_MAXCL));CL_code++)
        {
            RxClusterDefinition *cluster = initData->clusters[CL_code];

            if (cluster != NULL)
            {
                numRequiredClusters++;

                /* update instancing data (pvtdata->clinfo) for all required clusters
                 * (required means at the very least that space for instance data is
                 * created) and fastpath data (ps2ResHeader->clquickinfo) for all
                 * clusters to be exposed to nodes in the pipeline for reading/writing */

                if (cluster->defaultAttributes & CL_ATTRIB_REQUIRED)
                {
                    /* determine the extent to which instancing needs to account for this cluster */
                    pvtdata->clinfo[CL_code].attrib = cluster->defaultAttributes;

                    if (cluster->defaultAttributes & CL_ATTRIB_PLACEHOLDER)
                    {
                        RwChar string[256];
                        rwsprintf(string,
                                  "PS2AllMatProcessInitData; cluster %s has both CL_ATTRIB_REQUIRED and CL_ATTRIB_PLACEHOLDER set in its attributes - illegal!",
                                  cluster->name);
                        MESSAGE(string);
                        RWRETURN(FALSE);
                    }

                    /* Calculate stride here to negate the need for switch statements at run-time */
                    switch (pvtdata->clinfo[CL_code].attrib & CL_TYPE_MASK
                            & ~CL_USN)
                    {
                        case CL_S32:
                        case CL_V4_8:
                        case CL_V2_16:
                            pvtdata->clinfo[CL_code].stride = 1;
                            break;
                        case CL_V2_32:
                        case CL_V4_16:
                            pvtdata->clinfo[CL_code].stride = 2;
                            break;
                        case CL_V3_32:
                            pvtdata->clinfo[CL_code].stride = 3;
                            break;
                        case CL_V4_32:
                        default:
                            pvtdata->clinfo[CL_code].stride = 4;
                            break;
                    }

                    /* If the cluster should be visible in the pipeline, we
                     * need to know which clusterofinterest it is so we can
                     * access its data at instance-time */
                    if (cluster->defaultAttributes & CL_ATTRIB_READWRITE)
                    {
                        RwUInt32 cliIndex = 0;

                        if (cluster->defaultAttributes & CL_ATTRIB_OPAQUE)
                        {
                            RwChar string[256];
                            rwsprintf(string,
                                      "PS2AllMatProcessInitData; cluster %s has both (CL_ATTRIB_READ or CL_ATTRIB_WRITE) and CL_ATTRIB_OPAQUE set, these are mutually incompatible",
                                      cluster->name);
                            MESSAGE(string);
                            RWRETURN(FALSE);
                        }

                        pvtdata->cliIndex[pvtdata->numStripes] = cliIndex;
                        pvtdata->numStripes++;
                    }
                    else
                    {
//TODOCS[3][4][5]: THIS NEEDS TO CHANGE - ALEX WANTS TO INSTANCE INTO OPAQUE USER CLUSTERS (AND WHO'D BLAME HIM?)
//                THE USER WILL HAVE TO KNOW HOW TO INSTANCE INTO OPAQUE DATA, BUT THAT'S OK...
//                YOU NEED TO REMOVE THIS TEST HERE AND CHECK THAT ASSUMPTIONS AREN'T MADE IN INSTANCE DATA
//                SIZE/LAYOUT CALCS THAT ASSUME USER DATA CLUSTERS ARE NON-OPAQUE...
                        /* User clusters not set to READWRITE are meaningless,
                         * the instance node won't fill 'em, the bridge node
                         * won't upload 'em and no nodes be able to access 'em! */
                        if (CL_code >= CL_USER1)
                        {
                            RwChar string[256];
                            rwsprintf(string,
                                      "PS2AllMatProcessInitData; user clusters must have attributes set to CL_ATTRIB_PLACEHOLDER or CL_ATTRIB_WRITE or they will have no effect!",
                                      cluster->name);
                            MESSAGE(string);
                            RWRETURN(FALSE);
                        }
                        else
                        {
                            /* Standard clusters set to DONTFILL and not set to
                             * WRITE and not set to PLACEHOLDER are invalid - we'd
                             * just be uploading uninitialised junk, which is silly. */
                            if ( (cluster->defaultAttributes &
                                  CL_ATTRIB_DONT_FILL) )
                            {
                                RwChar string[256];
                                rwsprintf(string,
                                          "PS2AllMatProcessInitData; non-user clusters with CL_ATTRIB_DONTFILL attributes must also have either CL_ATTRIB_PLACEHOLDER or CL_ATTRIB_WRITE attributes, or uninitialised junk will be uploaded to VU1",
                                          cluster->name);
                                MESSAGE(string);
                                RWRETURN(FALSE);
                            }
                        }
                    }
                }
                else if (cluster->defaultAttributes & CL_ATTRIB_PLACEHOLDER)
                {
                    /* instancing just needs to reserve space VU-side for this cluster */
                    pvtdata->clinfo[CL_code].attrib = cluster->defaultAttributes;

                    /* Calculate stride here to negate the need for switch statements at run-time */
                    switch (pvtdata->clinfo[CL_code].attrib & CL_TYPE_MASK
                            & ~CL_USN)
                    {
                        case CL_S32:
                        case CL_V4_8:
                        case CL_V2_16:
                            pvtdata->clinfo[CL_code].stride = 1;
                            break;
                        case CL_V2_32:
                        case CL_V4_16:
                            pvtdata->clinfo[CL_code].stride = 2;
                            break;
                        case CL_V3_32:
                            pvtdata->clinfo[CL_code].stride = 3;
                            break;
                        case CL_V4_32:
                        default:
                            pvtdata->clinfo[CL_code].stride = 4;
                            break;
                    }
                }
                else
                {
                    /* This cluster is present but has weird flags... fail and warn */
                    RwChar string[256];
                    RwChar string2[32];

                    GETCLCODESTRING(CL_code, string2);
                    rwsprintf(string,
                              "PS2AllMatProcessInitData; %s cluster has unrecognised attributes",
                              string2);
                    MESSAGE(string);
                    RWRETURN(FALSE);
                }
            }
            else
            {
                /* Instancing doesn't need to deal with this cluster at all */
                pvtdata->clinfo[CL_code].attrib = 0;
            }

        }
    }
    else
    {
        /* Set up default VU sizes
           (this will be executed once zero clusters is supported) */
        pvtdata->sizeOnVU = 4;
        pvtdata->vu1MaxTSInputSize = VU1_MAX_TS_INPUT_SIZE;
        pvtdata->vu1MaxTLInputSize = VU1_MAX_TL_INPUT_SIZE;
        pvtdata->vu1MaxPLInputSize = VU1_MAX_PL_INPUT_SIZE;
        pvtdata->pipeType          = (RpMeshHeaderFlags)
            (rpMESHHEADERTRISTRIP|rpMESHHEADERTRIFAN|
             rpMESHHEADERLINELIST|rpMESHHEADERPOLYLINE);
    }

    if ((initData            == NULL) ||
        (numRequiredClusters <= 0   )    )
    {
        MESSAGE("PS2AllMatProcessInitData; zero clusters generated! (currently not allowed)");
        RWRETURN(FALSE);
    }

    result = _rwPS2AllRabinsConstructionTimeCode(pvtdata); /* success? */

    RWRETURN(result);
}

//TODOCS: DON'T RELY ON "TOD0CS" BEING COMPREHENSIVE, DO A SCAN BY
//       EYE OF ALL FUNCS/TYPES THAT MIGHT NEED DOCUMENTING
//        DONE: PS2ALLMAT.C (REMAINING -> TOD0CS NOW)
//        DONE: PS2ALLMAT.H (REMAINING -> TOD0CS NOW)
//        DONE: PS2ALL.C (REMAINING -> TOD0CS NOW)
//        DONE: PS2ALL.H (REMAINING -> TOD0CS NOW)
//        - default helper funcs/macros: ps2all[atomic|sector|im3d]
//        - compile up and look at the resulting docs
//        DONE: check rpworld.h

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2AllMatGenerateCluster is a pre
 * construction time node API function used to cause a PS2AllMat.csl
 * node to generate clusters. It should be called for each of the
 * RxClPS2* clusters required.
 *
 * These are the PS2-specific clusters you can request for this node
 * (see also \ref RxPS2ClusterType):
 *      \li RxClPS2xyz    - Object-space position of vertices
 *      \li RxClPS2uv     - Base texture coordinates of vertices
 *      \li RxClPS2uv2    - Two texture coordinate sets
 *      \li RxClPS2rgba   - Prelight colors of vertices
 *      \li RxClPS2normal - Object-space normals at each vertex
 *      \li RxClPS2user1  - User-defined cluster 1
 *      \li RxClPS2user2  - User-defined cluster 2
 *      \li RxClPS2user3  - User-defined cluster 3
 *      \li RxClPS2user4  - User-defined cluster 4
 *
 * PS2-specific attributes (in the RxPS2AttributeSet namespace, which
 * has the identifier string "PS2") are used to define two properties
 * of the above PS2-specific clusters. Firstly, the
 * \ref RxPS2ClusterAttrib flags define how the data's instancing into
 * DMA chains should be handled (see the docs for these flags for details).
 * Secondly, the \ref RxPS2ClusterFormatAttrib flags define what format
 * the data will be in as interpreted by the DMA engine and VIF when the
 * data is uploaded to VU1.
 *
 * The format attribute flags of the first four of the above PS2-specific
 * clusters are fixed and you cannot change them, however the other attribute
 * flags may be changed. The user-defined clusters can have any attributes you
 * please. Since these cluster definitions are static globals and provided
 * really as examples, it is suggested that you create copies, modify their
 * attributes as appropriate and submit these copies to this function. See
 * \ref RxClusterSetAttributes for details on changing cluster attributes.
 *
 * Note that in the case of PS2All.csl and PS2AllMat.csl, each cluster
 * generated does not actually imply the presence of an \ref RxCluster.
 * Clusters are only present from the point of view of the instance data
 * which is created and uploaded to VU1 by the pipeline. Cluster data may
 * be accessed within an \ref RxPipelineNodePS2AllMatInstanceCallBack and
 * the details of how cluster flags affect the format of this data are
 * given in the docs for that callback type.
 *
 * You have to have at least one cluster and can have a maximum of eight
 * (one each of the RxClPS2* clusters listed above). Clusters currently
 * have a maximum stride of one quadword though this limitation will be
 * removed in the future.
 *
 *
 * This function must be called before unlocking the pipeline
 * containing this PS2AllMat.csl node because cluster attributes
 * are finalized when the pipeline is unlocked.
 *
 * \param  self              A pointer to a PS2AllMat.csl \ref RxPipelineNode
 *                           in a locked pipeline
 * \param  cluster2generate  A pointer to a PS2-specific cluster definition
 * \param  type              PS2-specific cluster type specifier
 *
 * \return a pointer to the input pipeline node on success, otherwise NULL.
 *
 * \see RxPipelineNodePS2AllMatSetCallBack
 * \see RxPipelineNodePS2AllSetCallBack
 * \see RxPipelineNodePS2AllMatSetVUBufferSizes
 * \see RxPipelineNodePS2AllMatSetPointListVUBufferSize
 * \see RxPipelineNodePS2AllMatGetVUBatchSize
 * \see RxPipelineNodePS2AllMatGetVU1CodeArray
 * \see RxPipelineNodePS2AllMatSetVU1CodeArray
 * \see RxPipelineNodePS2AllMatSetVIFOffset
 * \see RxPipelineNodePS2AllGroupMeshes
 * \see RxNodeDefinitionGetPS2AllMat
 * \see RxNodeDefinitionGetPS2All
 */
RxPipelineNode *
RxPipelineNodePS2AllMatGenerateCluster(RxPipelineNode      *self,
                                       RxClusterDefinition *cluster2generate,
                                       RwUInt32             type)
{
    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2AllMatGenerateCluster"));

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

    if (NULL == cluster2generate)
    {
        MESSAGE("RxPipelineNodePS2AllMatGenerateCluster; NULL cluster definition passed");
        RWRETURN(NULL);
    }

    if (type >= CL_MAXCL)
    {
        MESSAGE("RxPipelineNodePS2AllMatGenerateCluster; Invalid cluster type");
        RWRETURN(NULL);
    }


    /* The standard cluster types are non-editable */
/*TODO[4]: WE MAY BECOME MORE FLEXIBLE ON THIS... THE STANDARD CLUSTERS (I.E THE GLOBALS
          DEFINING THEM) WILL BECOME JUST EXAMPLES BUT DEFAULT RW INSTANCING CODE WILL
          ALWAYS MAKE THE SAME ASSUMPTIONS ABOUT VERTEX FORMAT... ...MAYBE. RABIN HAD A
          CLEARER IDEA ABOUT IT THAN DID I (THOUGH IT'S STUCK IN HIS BRAIN AS USUAL)
           I SHOULD THINK ALLOWING ANYTHING WOULD BE FINE. HOPEFULLY OUT DEFAULT CODE
          CAN ASSERT OR SOMETHING. ALL CLUSTERS SHOULD BE ABLE TO BE OPAQUE. */
    switch (type)
    {
    case CL_XYZ:
        {
            if ((!(cluster2generate->defaultAttributes & CL_V3_32)) ||
                strcmp(cluster2generate->attributeSet, RxPS2AttributeSet))
            {
                RWASSERT(cluster2generate->defaultAttributes & CL_V3_32);
                RWASSERT(!strcmp(cluster2generate->attributeSet,
                                 RxPS2AttributeSet) );
                MESSAGE("You may not change the data format of the standard RxClPS2xyz cluster. If you wish to change its attributes, omit it and insert a user cluster to replace it");
                RWRETURN(NULL);
            }
            break;
        }
    case CL_UV:
        {
            if ((!(cluster2generate->defaultAttributes & CL_V2_32)) ||
                strcmp(cluster2generate->attributeSet, RxPS2AttributeSet))
            {
                RWASSERT(cluster2generate->defaultAttributes & CL_V2_32);
                RWASSERT(!strcmp(cluster2generate->attributeSet,
                                 RxPS2AttributeSet) );
                MESSAGE("You may not change the data format of the standard RxClPS2uv cluster. If you wish to change its attributes, omit it and insert a user cluster to replace it");
                RWRETURN(NULL);
            }
            break;
        }
    case CL_UV2:
        {
            if ((!(cluster2generate->defaultAttributes & CL_V4_32)) ||
                strcmp(cluster2generate->attributeSet, RxPS2AttributeSet))
            {
                RWASSERT(cluster2generate->defaultAttributes & CL_V4_32);
                RWASSERT(!strcmp(cluster2generate->attributeSet,
                                 RxPS2AttributeSet) );
                MESSAGE("You may not change the data format of the standard RxClPS2uv2 cluster. If you wish to change its attributes, omit it and insert a user cluster to replace it");
                RWRETURN((RxPipelineNode *)NULL);
            }
            break;
        }
    case CL_RGBA:
        {
            if ((!(cluster2generate->defaultAttributes & CL_V4_8)) ||
                (!(cluster2generate->defaultAttributes & CL_USN )) ||
                strcmp(cluster2generate->attributeSet, RxPS2AttributeSet))
            {
                RWASSERT(cluster2generate->defaultAttributes & CL_V4_8);
                RWASSERT(cluster2generate->defaultAttributes & CL_USN);
                RWASSERT(!strcmp(cluster2generate->attributeSet,
                                 RxPS2AttributeSet) );
                MESSAGE("You may not change the data format of the standard RxClPS2rgba cluster. If you wish to change its attributes, omit it and insert a user cluster to replace it");
                RWRETURN(NULL);
            }
            break;
        }
    case CL_NORMAL:
        {
            if ((!(cluster2generate->defaultAttributes & CL_V4_8)) ||
                strcmp(cluster2generate->attributeSet, RxPS2AttributeSet))
            {
                RWASSERT(cluster2generate->defaultAttributes & CL_V4_8);
                RWASSERT(!strcmp(cluster2generate->attributeSet,
                                 RxPS2AttributeSet) );
                MESSAGE("You may not change the data format of the standard RxClPS2normal cluster. If you wish to change its attributes, omit it and insert a user cluster to replace it");
                RWRETURN(NULL);
            }
            break;
        }
    }

    if( !strcmp(cluster2generate->attributeSet, RxPS2AttributeSet) )
    {
        INITDATATYPE *data;

        data = (INITDATATYPE *)RxPipelineNodeGetInitData(self);
        if (data == NULL)
        {
            if (PS2AllMatCreateInitData(self) == FALSE)
            {
                RWRETURN(NULL);
            }
            data = (INITDATATYPE *)RxPipelineNodeGetInitData(self);
        }

        /* We really don't want them setting a cluster twice... it'd be
         * a real pain to handle and I don't see why they need to do it. */
        RWASSERT(data->clusters[type] == NULL);

        data->clusters[type] = cluster2generate;
    }
    else
    {
        MESSAGE("RxPipelineNodePS2AllMatGenerateCluster; this cluster does not have attributes in the PS2 attributes set");
    }


    RWRETURN(self);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2AllMatSetVUBufferSizes is a pre
 * contruction time node API function which is used to set the
 * maximum number of triangles (for trilists) and vertices 
 * (for tristrips) that may be uploaded, by this pipeline, to
 * VU1 in a single batch. The stride (in quad words) of the input
 * buffer in VU1 memory is also set by this function.
 *
 * If this function is not called, the defaults required by the
 * standard transforms will be used. For pipelines feeding
 * user-supplied VU1 code, the values to pass to this function may
 * be obtained from the pipeline-specific header file created by
 * processing stddata.i
 *
 * Note that the buffer sizes submitted may be rounded down. For
 * trilists, buffer sizes will be rounded to a multiple of four
 * triangles (or twelve vertices) due to DMA read alignment
 * requirements. For tristrips, if your vertices contain any
 * broken-out clusters (that is clusters whose attributes flags
 * contain CL_ATTRIB_READ or CL_ATTRIB_WRITE), then the buffer size
 * will be rounded down to (4n + 2) vertices and if there are no
 * broken-out clusters then it will be rounded down to (4n). This
 * is due again to DMA read alignment requirements and also
 * internal vertex duplication used to continue tristrips between
 * batches.
 *
 * If this function is not called, stride will default to 4 and
 * vuTSVertexMaxCount and vuTLTriMaxCount will default to the
 * maximum possible batch sizes for that stride (if you have
 * changed the VIFOffset from the default value then this will
 * not be valid).
 *
 * This function must be called before unlocking the pipeline
 * containing this PS2AllMat.csl node.
 *
 * \param  self                  A pointer to a PS2AllMat.csl \ref RxPipelineNode
 *                               in a locked pipeline
 * \param  strideOfInputVertex   stride of vertex in VU memory
 * \param  vuTSVertexMaxCount    maximum number of vertices per TriStrip batch
 * \param  vuTLTriMaxCount       maximum number of triangles per TriList batch
 *
 * \return a pointer to this node on success, otherwise NULL
 *
 * \see RxPipelineNodePS2AllMatSetCallBack
 * \see RxPipelineNodePS2AllSetCallBack
 * \see RxPipelineNodePS2AllMatGenerateCluster
 * \see RxPipelineNodePS2AllMatSetPointListVUBufferSize
 * \see RxPipelineNodePS2AllMatGetVUBatchSize
 * \see RxPipelineNodePS2AllMatGetVU1CodeArray
 * \see RxPipelineNodePS2AllMatSetVU1CodeArray
 * \see RxPipelineNodePS2AllMatSetVIFOffset
 * \see RxPipelineNodePS2AllGroupMeshes
 * \see RxNodeDefinitionGetPS2AllMat
 * \see RxNodeDefinitionGetPS2All
 */
RxPipelineNode *
RxPipelineNodePS2AllMatSetVUBufferSizes(RxPipelineNode *self,
                                        RwInt32 strideOfInputVertex,
                                        RwInt32 vuTSVertexMaxCount,
                                        RwInt32 vuTLTriMaxCount)
{
    INITDATATYPE *data;

    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2AllMatSetVUBufferSizes"));


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

    if (strideOfInputVertex <= 0)
    {
        MESSAGE("RxPipelineNodePS2AllMatSetVUBufferSizes; strideOfInputVertex must be positive");
        RWRETURN(NULL);
    }
    
    if (vuTSVertexMaxCount < 3)
    {
        MESSAGE("RxPipelineNodePS2AllMatSetVUBufferSizes; vuTSVertexMaxCount must be greater than 2");
        RWRETURN(NULL);
    }

    if (vuTLTriMaxCount <= 0)
    {
        MESSAGE("RxPipelineNodePS2AllMatSetVUBufferSizes; vuTLTriMaxCount must be positive");
        RWRETURN(NULL);
    }


    data = (INITDATATYPE *)RxPipelineNodeGetInitData(self);
    if ((data != NULL) &&
        (data->pipeType & rpMESHHEADERPOINTLIST))
    {
        MESSAGE("RxPipelineNodePS2AllMatSetVUBufferSizes; Pipes can currently be either triangle/line pipes or point pipes, not both");
        RWRETURN(NULL);
    }

    if (data == NULL)
    {
        if (PS2AllMatCreateInitData(self) == FALSE)
        {
            RWRETURN(NULL);
        }
        data = (INITDATATYPE *)RxPipelineNodeGetInitData(self);
    }
    data->vu1MaxTLInputSize = vuTLTriMaxCount*3*strideOfInputVertex;
    data->vu1MaxTSInputSize = vuTSVertexMaxCount*strideOfInputVertex;
    data->sizeOnVU          = strideOfInputVertex;
    data->pipeType          = ( (RpMeshHeaderFlags)
                                (rpMESHHEADERTRISTRIP|rpMESHHEADERTRIFAN|
                                 rpMESHHEADERLINELIST|rpMESHHEADERPOLYLINE) );

    RWRETURN(self);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2AllMatSetPointListVUBufferSize is a pre
 * construction time node API function which is used to set the
 * maximum number of vertices for pointlists that may be uploaded,
 * by this pipeline, to VU1 in a single batch. The stride (in quad
 * words) of the input buffer in VU1 memory is also set by this
 * function.
 *
 * For rwPRIMTYPEPOINTLIST-based objects, this function should be called
 * instead of \ref RxPipelineNodePS2AllMatSetVUBufferSizes. They should not
 * both be called. Pipelines set up with this function will only be able
 * to handle pointlist-based objects, not triangle- or line-based objects.
 * For pipelines feeding user-supplied VU1 code, the values to pass to
 * this function may be obtained from the pipeline-specific header file
 * created by processing stddata.i. Note that, for a pointlist pipeline,
 * the first half of the VU1 code array should be used - i.e the TRANSTRI
 * slots are reused (see \ref RxPipelineNodePS2AllMatGetVU1CodeArray
 * and \ref RxSkyTransTypeFlags).
 *
 * Note that the buffer size submitted may be rounded down. For pointlists,
 * it will be rounded down to (4n) due to DMA read alignment requirements.
 *
 * If this function is not called, stride will default to 4 and
 * vuPLVertexMaxCount will default to the maximum possible batch
 * size for that stride (if you have changed the VIFOffset from
 * the default value then this will not be valid).
 *
 * This function must be called before unlocking the pipeline
 * containing this PS2AllMat.csl node.
 *
 * \param  self                  A pointer to a PS2AllMat.csl \ref RxPipelineNode
 *                               in a locked pipeline
 * \param  strideOfInputVertex   stride of vertex in VU1 memory
 * \param  vuPLVertexMaxCount    maximum number of vertices per pointlist batch
 *
 * \return a pointer to this node on success, otherwise NULL
 *
 * \see RxPipelineNodePS2AllMatSetCallBack
 * \see RxPipelineNodePS2AllSetCallBack
 * \see RxPipelineNodePS2AllMatGenerateCluster
 * \see RxPipelineNodePS2AllMatSetVUBufferSizes
 * \see RxPipelineNodePS2AllMatGetVUBatchSize
 * \see RxPipelineNodePS2AllMatGetVU1CodeArray
 * \see RxPipelineNodePS2AllMatSetVU1CodeArray
 * \see RxPipelineNodePS2AllMatSetVIFOffset
 * \see RxPipelineNodePS2AllGroupMeshes
 * \see RxNodeDefinitionGetPS2AllMat
 * \see RxNodeDefinitionGetPS2All
 */
RxPipelineNode *
RxPipelineNodePS2AllMatSetPointListVUBufferSize(RxPipelineNode *self,
                                                RwInt32 strideOfInputVertex,
                                                RwInt32 vuPLVertexMaxCount)
{
    INITDATATYPE *data;

    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2AllMatSetPointListVUBufferSize"));

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

    if (strideOfInputVertex <= 0)
    {
        MESSAGE("RxPipelineNodePS2AllMatSetPointListVUBufferSize; strideOfInputVertex must be positive");
        RWRETURN(NULL);
    }

    if (vuPLVertexMaxCount <= 0)
    {
        MESSAGE("RxPipelineNodePS2AllMatSetPointListVUBufferSize; vuPLVertexMaxCount must be positive");
        RWRETURN(NULL);
    }

    data = (INITDATATYPE *)RxPipelineNodeGetInitData(self);
    if ((data != NULL) &&
        (data->pipeType &
         (rpMESHHEADERTRISTRIP|rpMESHHEADERTRIFAN|
          rpMESHHEADERLINELIST|rpMESHHEADERPOLYLINE)))
    {
        MESSAGE("RxPipelineNodePS2AllMatSetPointListVUBufferSize; Pipes can currently be either triangle/line pipes or point pipes, not both");
        RWRETURN(NULL);
    }

    if (data == NULL)
    {
        if (PS2AllMatCreateInitData(self) == FALSE)
        {
            RWRETURN(NULL);
        }
        data = (INITDATATYPE *)RxPipelineNodeGetInitData(self);
    }

    data->vu1MaxPLInputSize = vuPLVertexMaxCount*strideOfInputVertex;
    data->sizeOnVU          = strideOfInputVertex;
    data->pipeType          = rpMESHHEADERPOINTLIST;

    RWRETURN(self);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2AllMatGetVUBatchSize is a post
 * construction time node API function used to query the
 * batch size used for uploading data to VU1.
 *
 * This function mirrors \ref RxPipelineNodePS2AllMatSetVUBufferSizes
 * or \ref RxPipelineNodePS2AllMatSetPointListVUBufferSize,
 * depending on the type of primitive being rendered by the
 * pipeline containing this node. The return value is in vertices,
 * so "6" would mean two triangles per batch for a trilist, four
 * triangles for a tristrip or six points for a pointlist. Note that,
 * for tristrips, the last two vertices in a batch will be duplicated
 * in the following batch, so that the tristrip can be continued.
 * 
 *
 * This function must be called after unlocking the pipeline
 * containing the PS2AllMat.csl node.
 *
 * \param  self   A pointer to a PS2AllMat.csl \ref RxPipelineNode
 *                in an unlocked pipeline
 * \param  flags  \ref RpMeshHeaderFlags specifying what sort of mesh the
 *                returned values should relate to. Valid values are zero or
 *                rpMESHHEADERTRISTRIP - for pointlist rendering, use zero
 *
 * \return batch size on success, zero otherwise
 *
 * \see RxPipelineNodePS2AllMatSetCallBack
 * \see RxPipelineNodePS2AllSetCallBack
 * \see RxPipelineNodePS2AllMatGenerateCluster
 * \see RxPipelineNodePS2AllMatSetVUBufferSizes
 * \see RxPipelineNodePS2AllMatSetPointListVUBufferSize
 * \see RxPipelineNodePS2AllMatGetVU1CodeArray
 * \see RxPipelineNodePS2AllMatSetVU1CodeArray
 * \see RxPipelineNodePS2AllMatSetVIFOffset
 * \see RxPipelineNodePS2AllGroupMeshes
 * \see RxNodeDefinitionGetPS2AllMat
 * \see RxNodeDefinitionGetPS2All
 */
RwInt32
RxPipelineNodePS2AllMatGetVUBatchSize(
    RxPipelineNode *self,
    RpMeshHeaderFlags flags)
{
    RwInt32          result;
    PRIVATEDATATYPE *data;

    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2AllMatGetVUBatchSize"));

    /* Pipeline needs to have been unlocked */
    RWASSERT(NULL != self->privateData);
    data = (PRIVATEDATATYPE *) self->privateData;

    if (flags & rpMESHHEADERTRISTRIP)
    {
        result = data->triStrip.batchSize;
    }
    else
    {
        result = data->triList.batchSize;
    }

    RWRETURN(result);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2AllMatSetVIFOffset is a post
 * construction time node API function which is used to
 * set the VIF offset that is used when processing multiple
 * batches of data.
 *
 * The VIF offset is the address in VU1 memory of the second
 * data input buffer (we upload data in a double-buffered scheme
 * so that upload and calculations can occur in parallel). Its
 * location relies on the size of input batches. (See ppvu.doc
 * in the PPVU example for an overview of VU1 memory layout and use)
 *
 * If this function is not called, a value suitable for the default
 * code fragments and static data allocations will be used. For user
 * provided VU1 code, this value can be obtained from the pipeline
 * specific headerfile generated by processing stddata.i
 *
 * This function must be called after unlocking the pipeline
 * containing the PS2AllMat.csl node.
 *
 * \param  self       A pointer to a PS2AllMat.csl \ref RxPipelineNode
 *                    in an unlocked pipeline
 * \param  vifOffset  Value of VIF double buffer offset to be used
 *                    by this pipe
 *
 * \return a pointer to this node on success, otherwise NULL.
 *
 * \see RxPipelineNodePS2AllMatSetCallBack
 * \see RxPipelineNodePS2AllSetCallBack
 * \see RxPipelineNodePS2AllMatGenerateCluster
 * \see RxPipelineNodePS2AllMatSetVUBufferSizes
 * \see RxPipelineNodePS2AllMatSetPointListVUBufferSize
 * \see RxPipelineNodePS2AllMatGetVUBatchSize
 * \see RxPipelineNodePS2AllMatGetVU1CodeArray
 * \see RxPipelineNodePS2AllMatSetVU1CodeArray
 * \see RxPipelineNodePS2AllGroupMeshes
 * \see RxNodeDefinitionGetPS2AllMat
 * \see RxNodeDefinitionGetPS2All
 */
RxPipelineNode *
RxPipelineNodePS2AllMatSetVIFOffset(RxPipelineNode *self, int vifOffset)
{
    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2AllMatSetVIFOffset"));

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

    if (self->privateData == NULL)
    {
        MESSAGE("RxPipelineNodePS2AllMatSetVIFOffset; Call this function only *after* RxLockedPipeUnlock");
        RWRETURN(NULL);
    }

    ((PRIVATEDATATYPE *)(self->privateData))->vifOffset = vifOffset;

    RWRETURN(self);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2AllMatSetVU1CodeArray is a post
 * construction time node API function which permits setting
 * of the VU1 code fragments to be used by this pipeline.
 *
 * After calling the \ref RxPipelineNodePS2AllMatBridgeCallBack,
 * PS2AllMat.csl will upload and activate (or queue for activation)
 * a VU1 code fragment. It will use the vu1CodeIndex member of
 * the \ref RxPS2AllPipeData struct to select the code fragment
 * to use from the VU1 code array (see
 * \ref RxPipelineNodePS2AllObjectSetupCallBack and
 * \ref RxPipelineNodePS2AllMatBridgeCallBack for details on
 * setting up vu1CodeIndex). The array should contain pointers
 * to callable DMA chain mode packets that will upload code to
 * location 0 onward on VU1.
 *
 * You may provide an array of any length, though naturally the
 * vu1CodeIndex must not select an element beyond its bounds!
 * The purpose of the array is to allow run-time switching
 * between different code fragments to perform different
 * transforms such as clipping, non-clipping, fogging, non-fogging,
 * etcetera. Array elements must not be NULL.
 * 
 * If this function is not called, the default VU1-based code fragments
 * will be used (these can be reinstated by calling this function with
 * a NULL array pointer - FYI the default VU1 code array may be accessed
 * through the global variable 'skyVU1Transforms'). See
 * \ref RxPipelineNodePS2AllMatBridgeCallBack for the details of the
 * layout of this array.
 *
 * This function must be called after unlocking the pipeline
 * containing the PS2AllMat.csl node.
 *
 * \param  self          A pointer to a PS2AllMat.csl \ref RxPipelineNode
 *                       in an unlocked pipeline
 * \param  vu1CodeArray  An array of pointers to callable dma chains (to
 *                       be used by reference) which upload code and return.
 * \param  length        The length of the array
 *
 * \return a pointer to this node on success, otherwise NULL.
 *
 * \see RxPipelineNodePS2AllMatSetCallBack
 * \see RxPipelineNodePS2AllSetCallBack
 * \see RxPipelineNodePS2AllMatGenerateCluster
 * \see RxPipelineNodePS2AllMatSetVUBufferSizes
 * \see RxPipelineNodePS2AllMatSetPointListVUBufferSize
 * \see RxPipelineNodePS2AllMatGetVUBatchSize
 * \see RxPipelineNodePS2AllMatGetVU1CodeArray
 * \see RxPipelineNodePS2AllMatSetVIFOffset
 * \see RxPipelineNodePS2AllGroupMeshes
 * \see RxNodeDefinitionGetPS2AllMat
 * \see RxNodeDefinitionGetPS2All
 */
RxPipelineNode *
RxPipelineNodePS2AllMatSetVU1CodeArray(RxPipelineNode *self,
                                       void **vu1CodeArray,
                                       RwUInt32 length)
{
    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2AllMatSetVU1CodeArray"));

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

    if (self->privateData == NULL)
    {
        MESSAGE("RxPipelineNodePS2AllMatSetVU1CodeArray; Call this function only *after* RxLockedPipeUnlock");
        RWRETURN(NULL);
    }

    if (vu1CodeArray != NULL)
    {
        if (length < 1)
        {
            MESSAGE("RxPipelineNodePS2AllMatSetVU1CodeArray; invalid length passed (must be >= 1)");
            RWRETURN(NULL);
        }

        ((PRIVATEDATATYPE *)(self->privateData))->vu1CodeArray = vu1CodeArray;
        ((PRIVATEDATATYPE *)(self->privateData))->codeArrayLength = length;
    }
    else
    {
        /* Revert to driver defaults */
        ((PRIVATEDATATYPE *)(self->privateData))->vu1CodeArray =
            (void **)(&skyVU1Transforms[0]);
        /* (Not all the elements are used any more. Legacy...) */
        ((PRIVATEDATATYPE *)(self->privateData))->codeArrayLength =
            VU1CODEARRAYSIZE;
    }

    RWRETURN(self);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2AllMatGetVU1CodeArray is a post
 * construction time node API function which retrieves an
 * array of pointers to the VU1 code fragments currently to
 * be used by this pipeline. It mirrors
 * \ref RxPipelineNodePS2AllMatSetVU1CodeArray
 *
 * This function must be called after unlocking the pipeline
 * containing the PS2AllMat.csl node.
 *
 * \param  self   A pointer to a PS2AllMat.csl \ref RxPipelineNode
 *                in an unlocked pipeline
 * \param  length An (optional) pointer to a RwUInt32 to receive
 *                the length of the VU1 code array
 *
 * \return a pointer to the current VU1 code array on success,
 * otherwise NULL.
 *
 * \see RxPipelineNodePS2AllMatSetCallBack
 * \see RxPipelineNodePS2AllSetCallBack
 * \see RxPipelineNodePS2AllMatGenerateCluster
 * \see RxPipelineNodePS2AllMatSetVUBufferSizes
 * \see RxPipelineNodePS2AllMatSetPointListVUBufferSize
 * \see RxPipelineNodePS2AllMatGetVUBatchSize
 * \see RxPipelineNodePS2AllMatSetVU1CodeArray
 * \see RxPipelineNodePS2AllMatSetVIFOffset
 * \see RxPipelineNodePS2AllGroupMeshes
 * \see RxNodeDefinitionGetPS2AllMat
 * \see RxNodeDefinitionGetPS2All
 */
const void **
RxPipelineNodePS2AllMatGetVU1CodeArray(RxPipelineNode * self, RwUInt32 *length)
{
    PRIVATEDATATYPE *pvtdata;

    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2AllMatGetVU1CodeArray"));

    if (self == NULL)
    {
        MESSAGE("RxPipelineNodePS2AllMatGetVU1CodeArray; NULL node passed");
        RWRETURN((const void **)NULL);
    }

    if (self->privateData == NULL)
    {
        MESSAGE("RxPipelineNodePS2AllMatGetVU1CodeArray; Call this function only *after* RxLockedPipeUnlock");
        RWRETURN((const void **)NULL);
    }

    pvtdata = (PRIVATEDATATYPE *) self->privateData;

    if (NULL != length)
    {
       *length = pvtdata->codeArrayLength;
    }

    if (NULL != pvtdata->vu1CodeArray)
    {
        RWRETURN((const void **)(pvtdata->vu1CodeArray));
    }

    /* The default */
    RWRETURN((const void **)(&(skyVU1Transforms[0])));
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2AllMatSetCallBack is is a post construction
 * time API function used to register a callback with a PS2AllMat.csl
 * pipeline node.
 *
 * During its execution, the PS2AllMat.csl node (called from the
 * PS2All.csl node) calls several callback functions. Callbacks
 * are also called from the PS2All.csl node in object pipelines
 * (see \ref RxPipelineNodePS2AllSetCallBack for details). Here
 * is a list of the callback types (in order of execution within
 * PS2AllMat.csl) used by PS2AllMat.csl:
 *
 * \li rxPS2ALLMATCALLBACKMESHINSTANCETEST - Performs per-mesh reinstance tests
 *      (see \ref RxPipelineNodePS2AllMatMeshInstanceTestCallBack)
 * \li rxPS2ALLMATCALLBACKRESENTRYALLOC - Allocates memory for instance data
 *      (see \ref RxPipelineNodePS2AllMatResEntryAllocCallBack)
 * \li rxPS2ALLMATCALLBACKINSTANCE - Instances a mesh and performs static data uploads
 *      (see \ref RxPipelineNodePS2AllMatInstanceCallBack)
 * \li rxPS2ALLMATCALLBACKBRIDGE - Selects VU code and sets up material properties
 *      (see \ref RxPipelineNodePS2AllMatBridgeCallBack)
 * \li rxPS2ALLMATCALLBACKPOSTMESH - Performs per-mesh post-render tasks
 *      (see \ref RxPipelineNodePS2AllMatPostMeshCallBack)
 *
 *
 * 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:
 *
 * \li \ref RpMeshPS2AllMeshInstanceTestCallBack, (same for RpWorldSectors, Im3D always reinstances)
 * \li \ref RpMeshPS2AllResEntryAllocCallBack, (same for RpWorldSectors), \ref RwIm3DPS2AllResEntryAllocCallBack
 * \li \ref RpMeshPS2AllInstanceCallBack, (same for RpWorldSectors), \ref RwIm3DPS2AllInstanceCallBack
 * \li \ref RpMeshPS2AllBridgeCallBack, (same for RpWorldSectors), \ref RwIm3DPS2AllBridgeCallBack
 * \li \ref RpMeshPS2AllPostMeshCallBack, (same for RpWorldSectors), \ref RwIm3DPS2AllPostMeshCallBack
 *
 *
 * See \ref RxPipelineNodePS2AllSetCallBack for an overview of the
 * flow of pipeline execution (comprising just PS2All.csl in the
 * object pipeline and PS2AllMat.csl in the material pipeline(s)).
 *
 * This function must be called before unlocking the pipeline
 * containing this PS2AllMat.csl node.
 *
 * \param  self  A pointer to a PS2AllMat.csl \ref RxPipelineNode
 *               in a locked pipeline
 * \param  type  An enum (\ref RxPipelineNodePS2AllMatCallBackType) 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 RxPipelineNodePS2AllSetCallBack
 * \see RxPipelineNodePS2AllMatGenerateCluster
 * \see RxPipelineNodePS2AllMatSetVUBufferSizes
 * \see RxPipelineNodePS2AllMatSetPointListVUBufferSize
 * \see RxPipelineNodePS2AllMatGetVUBatchSize
 * \see RxPipelineNodePS2AllMatGetVU1CodeArray
 * \see RxPipelineNodePS2AllMatSetVU1CodeArray
 * \see RxPipelineNodePS2AllMatSetVIFOffset
 * \see RxPipelineNodePS2AllGroupMeshes
 * \see RxNodeDefinitionGetPS2AllMat
 * \see RxNodeDefinitionGetPS2All
 */
RxPipelineNode *
RxPipelineNodePS2AllMatSetCallBack(RxPipelineNode *self,
                                   RxPipelineNodePS2AllMatCallBackType type,
                                   void *func)
{
    PRIVATEDATATYPE *data;

    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2AllMatSetCallBack"));

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

    if (NULL == self->privateData)
    {
        MESSAGE("RxPipelineNodePS2AllMatSetCallBack; 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 rxPS2ALLMATCALLBACKIM3DPREMESH:
        {
//TODO[7]: TEMPORARY
            data->im3DPreMeshCB = (RxPipelineNodePS2AllMatIm3DPreMeshCallBack)func;
            RWRETURN(self);
            break;
        }
    case rxPS2ALLMATCALLBACKMESHINSTANCETEST:
        {
            data->meshInstanceTestCB = (RxPipelineNodePS2AllMatMeshInstanceTestCallBack)func;
            RWRETURN(self);
            break;
        }
    case rxPS2ALLMATCALLBACKRESENTRYALLOC:
        {
            if (NULL == func)
            {
                MESSAGE("RxPipelineNodePS2AllMatSetCallBack; ResEntryAlloc callback cannot be NULL");
                RWRETURN(NULL);
            }
            data->resEntryAllocCB = (RxPipelineNodePS2AllMatResEntryAllocCallBack)func;
            RWRETURN(self);
            break;
        }
    case rxPS2ALLMATCALLBACKINSTANCE:
        {
            if (NULL == func)
            {
                MESSAGE("RxPipelineNodePS2AllMatSetCallBack; Instance callback cannot be NULL");
                RWRETURN(NULL);
            }
            data->instanceCB = (RxPipelineNodePS2AllMatInstanceCallBack)func;
            RWRETURN(self);
            break;
        }
    case rxPS2ALLMATCALLBACKBRIDGE:
        {
            if (NULL == func)
            {
                MESSAGE("RxPipelineNodePS2AllMatSetCallBack; Bridge callback cannot be NULL");
                RWRETURN(NULL);
            }
            data->bridgeCB = (RxPipelineNodePS2AllMatBridgeCallBack)func;
            RWRETURN(self);
            break;
        }
    case rxPS2ALLMATCALLBACKIM3DPOSTMESH:
        {
//TODO[7]: TEMPORARY
            data->im3DPostMeshCB = (RxPipelineNodePS2AllMatIm3DPostMeshCallBack)func;
            RWRETURN(self);
            break;
        }
    case rxPS2ALLMATCALLBACKIM3DMESHSPLIT:
        {
//TODO[7]: TEMPORARY
            data->im3DMeshSplitCB = (RxPipelineNodePS2AllMatIm3DMeshSplitCallBack)func;
            RWRETURN(self);
            break;
        }
    case rxPS2ALLMATCALLBACKPOSTMESH:
        {
            data->postMeshCB = (RxPipelineNodePS2AllMatPostMeshCallBack)func;
            RWRETURN(self);
            break;
        }
    default:
        {
            MESSAGE("RxPipelineNodePS2AllMatSetCallBack; Unrecognised material callback type");
            RWRETURN(NULL);
            break;
        }
    }
}

//TODO[6]: COMBINE ALL THE PRELOCK AND ALL THE POSTLOCK SETUP API FUNCS SO IT'S CLEARER AND SIMPLER?

/**
 * \ingroup rpworldp2sky2
 * \ref RxNodeDefinitionGetPS2AllMat returns a pointer to the
 * "PS2AllMat.csl", node definition, which is used to render
 * meshes within RenderWare (or custom) objects.
 *
 * This node is used to construct material pipelines which will
 * be called from PS2All.csl (from which object pipelines can be
 * constructed), for each mesh in an object. Each mesh's material
 * may point to a different pipeline, containing a different
 * version of PS2AllMat.csl (set up differently by the pipeline
 * construction-time API).
 *
 * Like PS2Manager.csl, this node is customisable by the setting of
 * callback functions. It (and PS2All.csl) has many more such
 * overloadable callback functions than did PS2Manager.csl.
 * See \ref RxPipelineNodePS2AllMatSetCallBack and
 * \ref RxPipelineNodePS2AllSetCallBack for details (the latter
 * 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.
 * Also, note that the PS2AllMat.csl node must be the ONLY node in
 * the material pipeline which contains it (due to the way PS2All.csl
 * accesses this pipeline).
 *
 * NOTE: When using PS2All.csl and PS2AllMat.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 RxNodeDefinitionGetPS2All
 */
RxNodeDefinition *
RxNodeDefinitionGetPS2AllMat(void)
{
    /*************************************/
    /**                                 **/
    /**  PS2ALL.CSL NODE SPECIFICATION  **/
    /**                                 **/
    /*************************************/

#define NUMCLUSTERSOFINTEREST 0
#define NUMOUTPUTS            0

    static RwChar _PS2AllMat_csl[] = RWSTRING("PS2AllMat.csl");

    static RxNodeDefinition nodePS2AllMatCSL =
    {
        _PS2AllMat_csl,                         /* Name */
        {                                       /* nodemethods */
            PS2AllMatNodeBody,                  /* +-- nodebody */
            (RxNodeInitFn) NULL,                /* +-- nodeinit */
            (RxNodeTermFn) NULL,                /* +-- nodeterm */
            PS2AllMatPipelineNodeInit,          /* +-- 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 = &nodePS2AllMatCSL;

    RWAPIFUNCTION(RWSTRING("RxNodeDefinitionGetPS2AllMat"));

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

    RWRETURN(result);
}

