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

#if (!defined(DOXYGEN))
static const char rcsid[] __RWUNUSED__ = 
    "@@@@(#)$Id: ps2allmat.c,v 1.18 2001/09/14 14:21:05 iestynb Exp $";
#endif /* (!defined(DOXYGEN)) */

/****************************************************************************
 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]: DO THESE VALUES SERVE ANY USEFUL PURPOSE? 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 rpworldp2sky2
 * \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.
 *
 * 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 rpworldp2sky2
 * \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.
 *
 * 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 rpworldp2sky2
 * \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.
 * 
 * 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 rpworldp2sky2
 * \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 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 rpworldp2sky2
 * \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.
 *
 * 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]: WE *SHOULD* PROVIDE 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
                    {
                /* TODO[4][5]: THIS NEEDS TO CHANGE - ALEX WANTS TO INSTANCE
                 *  INTO OPAQUE USER CLUSTERS (IT'S A GOOD DMA WIN). THE USER
                 *  WILL HAVE TO KNOW HOW TO INSTANCE INTO OPAQUE DATA, BUT
                 *  THAT'S OK. YOU NEED TO REMOVE THIS TEST HERE [ALSO, THE
                 *  SECOND TEST BELOW WILL BE INVALID... IF PEOPLE CAN
                 *  INSTANCE INTO OPAQUE USER CLUSTERS, THEY CAN INSTANCE INTO
                 *  OPAQUE STANDARD CLUSTERS] AND MAKE CHANGES TO INSTANCING
                 *  CODE AS PER BELOW:
                 *   IN _rwPS2AllRabinsConstructionTimeCode, THE TOTALLYOPAQUE
                 *  ASSUMPTION IS ALL STANDARD CLUSTERS ARE REQUIRED AND
                 *  OPAQUE - NEEDS TO CHANGE (ALSO EFFECTS BATCHESPERTAG CALC).
                 *  ALSO, THE SWITCH STATEMENTS ONLY COUNT THE STANDARD
                 *  CLUSTERS, ASSUMING USER CLUSTERS ARE NON-OPAQUE. CHANGE
                 *  THIS SO THAT THERE ARE NO SWITCHES, JUST LUTS (SEE LINE
                 *  1410, dmadATAfILLtAGS USES FORMAT ATTRIBS ALREADY) BASED
                 *  ON CLUSTER FORMAT ATTRIBS (WHICH WE OVERRIDE AT UNLOCK
                 *  TIME FOR THE STANDARD CLUSTERS, YES?). SAME ISSUES APPLY
                 *  TO DMADataSizeRecalc. DMADataFillTags DOES THE RIGHT THING
                 *  FOR NON-OPAQUE CLUSTERS BUT AGAIN ONLY DEALS WITH THE
                 *  STANDARD CLUSTERS AS 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);
}

/**
 * \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. DEFAULT RW
 *  VU1 CODE MAY ALWAYS MAKE THE SAME ASSUMPTIONS ABOUT VERTEX FORMAT, THOUGH.
 *  I SHOULD THINK ALLOWING ANYTHING WOULD BE FINE. HOPEFULLY OUR DEFAULT
 *  BRIDGECB CAN ASSERT. 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;

/* TODO[7]: THESE CALLBACKS ARE TEMPORARY FOR THE IM3D SPLITTING:
 *  rxPS2ALLMATCALLBACKIM3DPREMESH,
 *  rxPS2ALLMATCALLBACKIM3DPOSTMESH,
 *  rxPS2ALLMATCALLBACKIM3DMESHSPLIT */
    switch(type)
    {
    case rxPS2ALLMATCALLBACKIM3DPREMESH:
        {
            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:
        {
            data->im3DPostMeshCB = (RxPipelineNodePS2AllMatIm3DPostMeshCallBack)func;
            RWRETURN(self);
            break;
        }
    case rxPS2ALLMATCALLBACKIM3DMESHSPLIT:
        {
            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 PREUNLOCK AND ALL THE POSTUNLOCK SETUP API
 *         FUNCTIONS SO THAT USING PS2ALL IS 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);
}

