/*----------------------------------------------------------------------*
 *                                                                      *
 * Module  :                                                            *
 *                                                                      *
 * Purpose :                                                            *
 *                                                                      *
 * FX      :                                                            *
 *----------------------------------------------------------------------*/

/*----------------------------------------------------------------------*
 *-   Includes                                                         -*
 *----------------------------------------------------------------------*/
#include <rwcore.h>
#include <rpdbgerr.h>
#include <archive/rpmatfx310.h>

#include "rpplugin.h"
#include "effectPipes.h"
#include "effectPipesSky.h"

/*==== Reference header file for the matfx pipelines ====*/
#include "SKY2_BumpMapping/bumpPipe.h"
#include "SKY2_GlosEnvMap/envPipe.h"
#include "SKY2_BumpEnvMap/bumpEnvPipe.h"
#include "SKY2_AlphaBlender/dualPassPipe.h"

/*==== Reference header file for the skinning matfx pipelines ====*/
#include "SKY2_SkinBumpMapping/skinBumpPipe.h"
#include "SKY2_SkinGlosEnvMap/skinEnvPipe.h"
#include "SKY2_SkinBumpEnvMap/skinBumpEnvPipe.h"
#include "SKY2_SkinAlphaBlender/skinDualPassPipe.h"

/*==== Reference header file for VIF offsets ====*/
#include "SKY2_BumpEnvMap/stddata.h"

#include "SKY2_SkinBumpEnvMap/stddata.h"

#if (!defined(DOXYGEN))
static const char rcsid[] __RWUNUSED__ = 
    "@@@@(#)$Id: effectPipesSky.c,v 1.71 2001/09/26 14:23:36 johns Exp $";
#endif /* (!defined(DOXYGEN)) */

/*----------------------------------------------------------------------*
 *-   Local/static Globals                                             -*
 *----------------------------------------------------------------------*/
static RwBool syncBumpFrame = FALSE;
static RwBool syncEnvFrame  = FALSE;

/* Only recalculate stuff when the 'current' frame changes */
static RwFrame *matFXBumpOldFrame = (RwFrame *) NULL;
static RwFrame *matFXEnvOldFrame = (RwFrame *) NULL;




/*----------------------------------------------------------------------*
 *-   Globals across program                                           -*
 *----------------------------------------------------------------------*/

/*--- Static and skinning pipeline data --------------------------------*/
matFXState matFXPipeState =
{
    (RxPipeline *) NULL,          /* matfx atomic pipeline               */
    (RxPipeline *) NULL,          /* matfx atomic pipeline UV2           */
    (RxPipeline *) NULL,          /* matfx worldSector pipeline          */
    (RxPipeline *) NULL,          /* matfx worldSector pipeline UV2      */
    (RxPipeline *) NULL,          /* matfx skin pipeline                 */
    (RxPipeline *) NULL,          /* matfx skin pipeline UV2             */
    {   /*--- Element state type. ---------------------------------------*/
        rpMATFXEFFECTNULL,
        /*--- Effect instance call backs. -------------------------------*/
        { (matFXInstanceCallBack) NULL,                   /* Null        */
          _rpMatFXBumpInstanceCallBack,                   /* BumpMap     */
          _rpMatFXEnvMapInstanceCallBack,                 /* EnvMap      */
          _rpMatFXBumpEnvMapInstanceCallBack,             /* BumpEnvMap  */
          _rpMatFXDualInstanceCallBack },                 /* Dual        */
        /*--- VUcode arrays. --------------------------------------------*/
        { &skyVU1Transforms,                              /* Null        */
          &skyBumpMapTransforms,                          /* BumpMap     */
          &skyEnvMapTransforms,                           /* EnvMap      */
          &skyBumpEnvMapTransforms,                       /* BumpEnvMap  */
          &skyAlphaBlenderTransforms }                    /* Dual        */
    },
    {   /*--- Element state type. ---------------------------------------*/
        rpMATFXEFFECTNULL,
        /*--- Effect instance call backs. -------------------------------*/
        { (matFXInstanceCallBack) NULL,                   /* Null        */
          _rpMatFXBumpInstanceCallBack,                   /* BumpMap     */
          _rpMatFXEnvMapInstanceCallBack,                 /* EnvMap      */
          _rpMatFXBumpEnvMapInstanceCallBack,             /* BumpEnvMap  */
          _rpMatFXDualInstanceCallBack },                 /* Dual        */
        /*--- VUcode arrays. --------------------------------------------*/
        { &skyVU1Transforms,                              /* Null        */
          &skyBumpMapTransforms,                          /* BumpMap     */
          &skyEnvMapTransforms,                           /* EnvMap      */
          &skyBumpEnvMapTransforms,                       /* BumpEnvMap  */
          &skyAlphaBlenderTransforms }                    /* Dual        */
    },
    {   /*--- Element state type. ---------------------------------------*/
        rpMATFXEFFECTNULL,
        /*--- Effect instance call backs. -------------------------------*/
        { (matFXInstanceCallBack) NULL,                   /* Null        */
          _rpMatFXBumpInstanceCallBack,                   /* BumpMap     */
          _rpMatFXEnvMapInstanceCallBack,                 /* EnvMap      */
          _rpMatFXBumpEnvMapInstanceCallBack,             /* BumpEnvMap  */
          _rpMatFXDualInstanceCallBack },                 /* Dual        */
        /*--- VUcode arrays. --------------------------------------------*/
        { &skyVU1Transforms,                              /* Null        */
          &skyBumpMapTransforms,                          /* BumpMap     */
          &skyEnvMapTransforms,                           /* EnvMap      */
          &skyBumpEnvMapTransforms,                       /* BumpEnvMap  */
          &skyAlphaBlenderTransforms }                    /* Dual        */
    },
    {   /*--- Element state type. ---------------------------------------*/
        rpMATFXEFFECTNULL,
        /*--- Effect instance call backs. -------------------------------*/
        { (matFXInstanceCallBack) NULL,                   /* Null        */
          _rpMatFXBumpInstanceCallBack,                   /* BumpMap     */
          _rpMatFXEnvMapInstanceCallBack,                 /* EnvMap      */
          _rpMatFXBumpEnvMapInstanceCallBack,             /* BumpEnvMap  */
          _rpMatFXDualInstanceCallBack },                 /* Dual        */
        /*--- VUcode arrays. --------------------------------------------*/
        { &skyVU1Transforms,                              /* Null        */
          &skyBumpMapTransforms,                          /* BumpMap     */
          &skyEnvMapTransforms,                           /* EnvMap      */
          &skyBumpEnvMapTransforms,                       /* BumpEnvMap  */
          &skyAlphaBlenderTransforms }                    /* Dual        */
    },
    {   /*--- Element state type. ---------------------------------------*/
        rpMATFXEFFECTNULL,
        /*--- Skinning effect instance call backs. ----------------------*/
        { (matFXInstanceCallBack) NULL,                   /* Null        */
          _rpMatFXSkinBumpInstanceCallBack,               /* BumpMap     */
          _rpMatFXSkinEnvMapInstanceCallBack,             /* EnvMap      */
          _rpMatFXSkinBumpEnvMapInstanceCallBack,         /* BumpEnvMap  */
          _rpMatFXSkinDualInstanceCallBack },             /* Dual        */
        /*--- Skinning vucode arrays. -----------------------------------*/
        { (matFXVUcode *) NULL,                           /* Null        */
          &skySkinBumpMapTransforms,                      /* BumpMap     */
          &skySkinEnvMapTransforms,                       /* EnvMap      */
          &skySkinBumpEnvMapTransforms,                   /* BumpEnvMap  */
          &skySkinAlphaBlenderTransforms }                /* Dual        */
    },
    {   /*--- Element state type. ---------------------------------------*/
        rpMATFXEFFECTNULL,
        /*--- Skinning effect instance call backs. ----------------------*/
        { (matFXInstanceCallBack) NULL,                   /* Null        */
          _rpMatFXSkinBumpInstanceCallBack,               /* BumpMap     */
          _rpMatFXSkinEnvMapInstanceCallBack,             /* EnvMap      */
          _rpMatFXSkinBumpEnvMapInstanceCallBack,         /* BumpEnvMap  */
          _rpMatFXSkinDualInstanceCallBack },             /* Dual        */
        /*--- Skinning vucode arrays. -----------------------------------*/
        { (matFXVUcode *) NULL,                           /* Null        */
          &skySkinBumpMapTransforms,                      /* BumpMap     */
          &skySkinEnvMapTransforms,                       /* EnvMap      */
          &skySkinBumpEnvMapTransforms,                   /* BumpEnvMap  */
          &skySkinAlphaBlenderTransforms }                /* Dual        */
    }
};

/*--- Skinning function and variable pointers --------------------------*/
_rpSkinLink matFXSkinning =
{
    (_rpSkinLinkAtomicGetSkin)NULL,
    (_rpSkinLinkAtomicGlobals *)NULL,
    (_rpSkinLinkMBWeightsInstancing)NULL,
    (_rpSkinLinkMBMatUpdating)NULL,
    (_rpSkinLinkPS2ManagerInstanceCallBack)NULL,
    (_rpSkinLinkVU1Transform *)NULL
};

/*--- BumpMap variables ------------------------------------------------*/

/* FX fading coefficient */
RwReal matFXBumpScale = 1 / 60.0f;

/* Plane beyond at which FX fade out completely */
RwReal matFXBumpFarPlane = 60.0f;

/* Temporary storage before uploading matrices */
RwMatrix matFXBumpMatrix;

/*--- EnvMap variables ------------------------------------------------*/

/* Environment mapping offset and scale factor */
matFXEnvArray matFXEnvParams = { 1.0f, 1.0f, 0.5f, 0.5f };

/* Temporary storage before uploading matrices */
RwMatrix matFXEnvMatrix;



/*----------------------------------------------------------------------*
 *-   Defines                                                          -*
 *----------------------------------------------------------------------*/
#define UV1 FALSE
#define UV2 TRUE

/*
 * This is defined as a *checky* little macro to save
 * the complexity of the instance code.
 */
#define MATFXSYNCFRAME(frame, oldFrame, matrix, sync)                    \
MACRO_START                                                              \
{                                                                        \
    /* Do we need to refresh the frame? */                               \
    if( sync || (frame != oldFrame) )                                    \
    {                                                                    \
        if(NULL == frame)                                                \
        {                                                                \
            /* use the current camera frame */                           \
            frame = RwCameraGetFrame(camera);                            \
            RWASSERT(frame);                                             \
        }                                                                \
                                                                         \
        if( dmaSessionRec->objType == rxOBJTYPE_ATOMIC )                 \
        {                                                                \
            RpAtomic *atomic;                                            \
            RwMatrix inverse;                                            \
            RwFrame  *atomicFrame;                                       \
                                                                         \
            atomic = dmaSessionRec->sourceObject.atomic;                 \
            RwMatrixInvert( &inverse, RwFrameGetLTM(frame) );            \
            atomicFrame = RpAtomicGetFrame(atomic);                      \
            RwMatrixMultiply( &matrix,                                   \
                              RwFrameGetLTM( atomicFrame ),              \
                              &inverse);                                 \
        }                                                                \
        else                                                             \
        {                                                                \
            /* just use the inverse of the frame LTM */                  \
            RwMatrixInvert( &matrix, RwFrameGetLTM(frame) );             \
        }                                                                \
    }                                                                    \
                                                                         \
    /* Remember frames from mesh to mesh to avoid unnecessary work */    \
    oldFrame = frame;                                                    \
    sync = FALSE;                                                        \
}                                                                        \
MACRO_STOP




/*
 * The following code is equivalent to the two sets below.
 */
#define MATFXSETDEFBLENDMODES()                                          \
MACRO_START                                                              \
{                                                                        \
    RwUInt64  tmp, tmp1;                                                 \
    u_long128 ltmp = 0;                                                  \
                                                                         \
    sweOpenLocalPkt(SWE_LPS_CONT, -2);                                   \
                                                                         \
    tmp = ( /* NLOOP */ 3l                                               \
            | /* EOP */ (1l << 15)                                       \
            | /* PRE */ (0l << 46)                                       \
            | /* FLG */ (0l << 58)                                       \
            | /* NREG */ (1l << 60));                                    \
    tmp1 =  (0xel << (64 - 64));                                         \
    MAKE128(ltmp, tmp1, tmp);                                            \
    SWEADDCONTGIFFAST(ltmp, 1);                                          \
    MAKE128(ltmp, GS_ALPHA_1, skyAlpha_1);                               \
    SWEADDCONTFAST(ltmp);                                                \
    MAKE128(ltmp, GS_TEST_1, skyTest_1);                                 \
    SWEADDCONTFAST(ltmp);                                                \
    MAKE128(ltmp, GS_FOGCOL, skyFogcol);                                 \
    SWEADDCONTFAST(ltmp);                                                \
}                                                                        \
MACRO_STOP;





#if (defined(RWDEBUG))
#define RWASSERTMEMSAME(source, compare, count)                         \
MACRO_START                                                             \
{                                                                       \
    const char         *buf1 = (const char *) (source);                 \
    const char         *buf2 = (const char *) (compare);                \
    size_t              length = (count);                               \
                                                                        \
    /* Do the blocks differ ? */                                        \
    if (memcmp(buf1, buf2, length))                                     \
    {                                                                   \
        /* At which offset is the first difference ? */                 \
        const char         *bin1 = buf1;                                \
        const char         *bin2 = buf2;                                \
        size_t              bisect;                                     \
                                                                        \
        RWMESSAGE(("Blocks %p %p of length %d differ",                  \
                    buf1, buf2, length));                               \
                                                                        \
        for (bisect = length >> 1; bisect > 0; bisect = length >> 1)    \
        {                                                               \
            if (memcmp(bin1, bin2, bisect))                             \
            {                                                           \
                length = bisect;                                        \
            }                                                           \
            else                                                        \
            {                                                           \
                bin1 = &bin1[bisect];                                   \
                bin2 = &bin2[bisect];                                   \
                length -= bisect;                                       \
            }                                                           \
        }                                                               \
        length = bin1 - buf1;                                           \
                                                                        \
        RWMESSAGE(("Blocks %p %p first differ at byte offset %d",       \
                   buf1, buf2, length));                                \
                                                                        \
    }                                                                   \
}                                                                       \
MACRO_STOP
#endif /* (defined(RWDEBUG)) */

#if (!defined(RWASSERTMEMSAME))
#define RWASSERTMEMSAME(source, compare, count) /* No op */
#endif /* (!defined(RWASSERTMEMSAME)) */




/*----------------------------------------------------------------------*
 *-   Functions                                                        -*
 *----------------------------------------------------------------------*/
RwBool
_rpMatFXMaterialsPostObjectCallBack(RxPS2DMASessionRecord *
                                    __RWUNUSED__ record)
{
    RWFUNCTION(RWSTRING("_rpMatFXMaterialsPostObjectCallBack"));

    /* Restore our old blending modes */
    MATFXSETDEFBLENDMODES();

    RWRETURN(TRUE);
}

/*----------------------------------------------------------------------*
 *-   Global MatFX Instance CallBack                                   -*
 *----------------------------------------------------------------------*/
RwBool
_rpMatFXMaterialsInstanceCallBack( void ** clusterData,
                                   RwUInt32 numClusters,
                                   matFXElementState *state )
{
    /*==== Pipeline cluster data. =======================*/
    RxPS2Mesh *ps2Mesh = (RxPS2Mesh *) clusterData[0];
    RxPS2DMASessionRecord *dmaSessionRec =
        (RxPS2DMASessionRecord *) clusterData[1];
    /*==== MatFX data. ==================================*/
    RpMatFXMaterialFlags effectType = rpMATFXEFFECTNULL;
    rpMatFXMaterialData  *materialData = (rpMatFXMaterialData *)NULL;

    RwCamera             *camera = ( RwCamera *) NULL;
    RxPipelineNode       *manager = (RxPipelineNode *)NULL;
    RwBool                ret = TRUE;

    RWFUNCTION(RWSTRING("_rpMatFXMaterialsInstanceCallBack"));

    /* Get the current camera */
    camera = RwCameraGetCurrentCamera();
    RWASSERT(camera);

    /*
     * Recalc frame stuff if the object has changed (meshNum = 0),
     * frames could change inbetween objects. Also update if the
     * frame has changed from that used by the last mesh.
     */
    if (0 == ps2Mesh->meshNum)
    {
        syncBumpFrame = TRUE;
        syncEnvFrame  = TRUE;
    }

    /* Lets grab the material ... */
    materialData = (rpMatFXMaterialData *)
        *MATFXMATERIALGETDATA(ps2Mesh->mesh->material);

    /* Assuming the material has been setup for MatFX. */
    if (NULL != materialData)
    {
        RwUInt32              pass  = 0;

        /* RWASSERT(RPMATFXALIGNMENT(materialData)); */

        /* Get the effect type. */
        effectType = materialData->flags;
        RWASSERT(effectType < rpMATFXEFFECTMAX);

        /* Do shared syncing for each pass. */
        for (pass = 0; pass < rpMAXPASS; pass++)
        {
            if (rpMATFXEFFECTBUMPMAP == materialData->data[pass].flag)
            {
                MATFXSYNCFRAME(materialData->data[pass].data.bumpMap.frame,
                               matFXBumpOldFrame,
                               matFXBumpMatrix,
                               syncBumpFrame);

                /*
                 * Should only occur once per frame, give or take multiple
                 * begin/endupdate pairs.
                 */
                if (camera->farPlane != matFXBumpFarPlane)
                {
                    RwReal divisor;

                    matFXBumpFarPlane = camera->farPlane;
                    divisor = (matFXBumpFarPlane - camera->nearPlane);

                    RWASSERT(divisor != ((RwReal) 0));
                    matFXBumpScale = ((RwReal)1 /divisor);
                }
            }
            else if (rpMATFXEFFECTENVMAP == materialData->data[pass].flag)
            {
                MATFXSYNCFRAME(materialData->data[pass].data.envMap.frame,
                               matFXEnvOldFrame,
                               matFXEnvMatrix, syncEnvFrame);
            }
        }
    }

    /*
     * PROBLEM: manager won't be got if PVS is on...
     *          need a better way.
     *          Modifying instance CBs to take an extra parameter
     *          pointing to self seems the only way... bah...
     *
     *          For how we must be alittle hackey.
     */
    /*
     * The following maybe a better solution.
    manager = &(_rxExecCtxGlobal.pipeline->nodes[(_rxExecCtxGlobal.pipeline->numNodes)-1]);
     */
    manager = &(_rxExecCtxGlobal.pipeline->nodes[0]);

    RWASSERT(effectType < rpMATFXEFFECTMAX);

    /* Ok are we _changing_ effect */
    if (state->effectType != effectType)
    {
        /* Need to fix the alpha. */
        MATFXSETDEFBLENDMODES();
        RWASSERT(effectType < rpMATFXEFFECTMAX);

        /* Are we about the render with the generic pipes? */
        if (effectType == rpMATFXEFFECTNULL)
        {
            /* Turn on matbridge texture setup. */
            RxPipelineNodePS2ManagerNoTexture(manager, FALSE);
        }
        else
        {
            if (state->effectType == rpMATFXEFFECTNULL)
            {
                /* Turn off matbridge texture setup. */
                RxPipelineNodePS2ManagerNoTexture(manager, TRUE);
            }

            RWASSERT(rpMATFXEFFECTNULL < effectType);
            RWASSERT(effectType < rpMATFXEFFECTMAX);
        }

        /* Select the correct VU code array. */
        RxPipelineNodePS2ManagerSetVU1CodeArray( manager,
            *(state->vuCodeCache[effectType]) );
    }

    /* Call the instance call back (only if there is one. */
    if( NULL != state->instanceCallBacks[effectType] )
    {
        /* Call the effects instance call back. */
        ret = state->instanceCallBacks[effectType]( clusterData,
                                                    numClusters );
    }

    /* Remember the present effect type. */
    state->effectType = effectType;

    RWRETURN(ret);
}

/*----------------------------------------------------------------------*
 *-   Skinning MatFX Instance CallBack                                 -*
 *----------------------------------------------------------------------*/
static RwBool
MatFXMaterialsSkinInstance( void **clusterData,
                            RwUInt32 numClusters )
{
    RwBool ret;

    RWFUNCTION(RWSTRING("MatFXMaterialsSkinInstance"));

    /* We don't know about instancing any clusters
     * and currently instanceCBs don't chain. */
    RWASSERT(3 == numClusters);

    ret = _rpMatFXMaterialsInstanceCallBack( clusterData,
                                             numClusters,
                                             &(matFXPipeState.skinning) );

    RWRETURN(ret);
}

/*----------------------------------------------------------------------*
 *-   Skinning UV2 MatFX Instance CallBack                             -*
 *----------------------------------------------------------------------*/
static RwBool
MatFXMaterialsSkinUV2Instance( void **clusterData,
                               RwUInt32 numClusters )
{
    RwBool ret;

    RWFUNCTION(RWSTRING("MatFXMaterialsSkinUV2Instance"));

    /* We don't know about instancing any clusters
     * and currently instanceCBs don't chain. */
    RWASSERT(3 == numClusters);

    ret = _rpMatFXMaterialsInstanceCallBack( clusterData,
                                             numClusters,
                                             &(matFXPipeState.skinningUV2) );

    RWRETURN(ret);
}

/*----------------------------------------------------------------------*
 *-   WorldSector MatFX Instance CallBack                              -*
 *----------------------------------------------------------------------*/
static RwBool
MatFXMaterialsWorldInstance( void **clusterData,
                             RwUInt32 numClusters )
{
    RwBool ret;

    RWFUNCTION(RWSTRING("MatFXMaterialsWorldInstance"));

    /* We don't know about instancing any clusters
     * and currently instanceCBs don't chain. */
    RWASSERT(2 == numClusters);

    ret = _rpMatFXMaterialsInstanceCallBack( clusterData,
                                             numClusters,
                                             &(matFXPipeState.worldSector) );

    RWRETURN(ret);
}

/*----------------------------------------------------------------------*
 *-   WorldSector UV2 MatFX Instance CallBack                          -*
 *----------------------------------------------------------------------*/
static RwBool
MatFXMaterialsWorldUV2Instance( void **clusterData,
                                RwUInt32 numClusters )
{
    RwBool ret;

    RWFUNCTION(RWSTRING("MatFXMaterialsWorldUV2Instance"));

    /* We don't know about instancing any clusters
     * and currently instanceCBs don't chain. */
    RWASSERT(2 == numClusters);

    ret = _rpMatFXMaterialsInstanceCallBack( clusterData,
                                             numClusters,
                                             &(matFXPipeState.worldSectorUV2) );

    RWRETURN(ret);
}

/*----------------------------------------------------------------------*
 *-   Atomic MatFX Instance CallBack                                   -*
 *----------------------------------------------------------------------*/
static RwBool
MatFXMaterialsInstance( void **clusterData,
                        RwUInt32 numClusters )
{
    RwBool ret;

    RWFUNCTION(RWSTRING("MatFXMaterialsInstance"));

    /* We don't know about instancing any clusters
     * and currently instanceCBs don't chain. */
    RWASSERT(2 == numClusters);

    ret = _rpMatFXMaterialsInstanceCallBack( clusterData,
                                             numClusters,
                                             &(matFXPipeState.atomic) );

    RWRETURN(ret);
}

/*----------------------------------------------------------------------*
 *-   Atomic UV2 MatFX Instance CallBack                               -*
 *----------------------------------------------------------------------*/
static RwBool
MatFXMaterialsUV2Instance( void **clusterData,
                           RwUInt32 numClusters )
{
    RwBool ret;

    RWFUNCTION(RWSTRING("MatFXMaterialsUV2Instance"));

    /* We don't know about instancing any clusters
     * and currently instanceCBs don't chain. */
    RWASSERT(2 == numClusters);

    ret = _rpMatFXMaterialsInstanceCallBack( clusterData,
                                             numClusters,
                                             &(matFXPipeState.atomicUV2) );

    RWRETURN(ret);
}


/*--- Create and initialize a pipeline ----------------------------------*/

/*=====================================================================*/
/*=             MatFX Skinning PipeLine Constructor                   =*/
/*=====================================================================*/

static RxPipeline *
MatFXMaterialsSkinPipelineCreate(const RwBool uv2,
                                 RxPipelineNodePS2ManagerInstanceCallBack callback)
{
    RxPipeline          *result = (RxPipeline *)NULL;
    RxPipeline          *newPipe;
    RxLockedPipe        *lpipe;
    RxNodeDefinition    *manager;
    RxPipelineNode      *plnode;
    RxPipelineNode      *verify;
    RxClusterDefinition *const UVClusterDef = (uv2)? &RxClPS2uv2: &RxClPS2uv;
    const RwUInt32       UVType = (uv2)? CL_UV2: CL_UV;

    RWFUNCTION(RWSTRING("MatFXMaterialsSkinPipelineCreate"));

    newPipe = RxPipelineCreate();
    RWASSERT(NULL != newPipe);

    newPipe->pluginId = rwID_MATERIALEFFECTSPLUGIN;

    lpipe = RxPipelineLock(newPipe);
    RWASSERT(NULL != lpipe);

    manager = RxNodeDefinitionGetPS2Manager((RwInt32) rxOBJTYPE_ATOMIC);
    RWASSERT(NULL != manager);

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

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

    plnode = RxPipelineNodePS2ManagerGenerateCluster(plnode,
                                                     &RxClPS2xyz,
                                                     CL_XYZ);
    RWASSERT(plnode != NULL);

    plnode = RxPipelineNodePS2ManagerGenerateCluster(plnode,
                                                     UVClusterDef,
                                                     UVType);
    RWASSERT(plnode != NULL);

    plnode = RxPipelineNodePS2ManagerGenerateCluster(plnode,
                                                     &RxClPS2rgba,
                                                     CL_RGBA);
    RWASSERT(plnode != NULL);
    plnode = RxPipelineNodePS2ManagerGenerateCluster(plnode,
                                                     &RxClPS2normal,
                                                     CL_NORMAL);
    RWASSERT(plnode != NULL);

    plnode = RxPipelineNodePS2ManagerGenerateCluster(plnode,
                                                     &RxClPS2user1,
                                                     CL_USER1);
    RWASSERT(NULL != plnode);

    verify = RxPipelineNodePS2ManagerSetVUBufferSizes(plnode,
                 pipeASymbSkinStrideOfInputCluster,
                 pipeASymbSkinTSVertexCount,
                 pipeASymbSkinTLTriCount);
    RWASSERT(NULL != verify);

    /* Unlock! */
    lpipe = RxLockedPipeUnlock(lpipe);
    RWASSERT(NULL != lpipe);

    /* Best to re-find nodes after an unlock */
    plnode = RxPipelineFindNodeByName(lpipe,
                                      manager->name,
                                      (RxPipelineNode *) NULL,
                                      (RwInt32 *) NULL);
    RWASSERT(plnode != NULL);

    verify = RxPipelineNodePS2ManagerSetVIFOffset(plnode,
                                                  pipeASymbSkinVIFOffset);
    RWASSERT(NULL != verify);

    /*---- New VU Array description allowing BF Culling ----*/
    ((RxPipelineNodePS2ObjAllInOneData *)plnode->privateData)->genericVU1Index = TRUE;

    verify = RxPipelineNodePS2ManagerSetInstanceCallBack(plnode, callback);
    RWASSERT(NULL != verify);

    verify = RxPipelineNodePS2ManagerSetPostObjectCallBack(plnode,
                 _rpMatFXMaterialsPostObjectCallBack);
    RWASSERT(NULL != verify);

    result = lpipe;
    RWRETURN(result);
}

static RxPipeline *
MatFXMaterialsPipelineCreate(const RxPS2ObjectType objType, const RwBool uv2,
                             RxPipelineNodePS2ManagerInstanceCallBack callback)
{
    RxPipeline          *result = (RxPipeline *)NULL;
    RxPipeline          *newPipe;
    RxLockedPipe        *lpipe;
    RxNodeDefinition    *manager;
    RxPipelineNode      *plnode;
    RxPipelineNode      *verify;
    RxClusterDefinition *const UVClusterDef = (uv2)? &RxClPS2uv2: &RxClPS2uv;
    const RwUInt32       UVType = (uv2)? CL_UV2: CL_UV;

    RWFUNCTION(RWSTRING("MatFXMaterialsPipelineCreate"));

    newPipe = RxPipelineCreate();
    RWASSERT(NULL != newPipe);

    newPipe->pluginId = rwID_MATERIALEFFECTSPLUGIN;

    lpipe = RxPipelineLock(newPipe);
    RWASSERT(NULL != lpipe);

    manager = RxNodeDefinitionGetPS2Manager((RwInt32) objType);
    RWASSERT(NULL != manager);

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

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

    plnode = RxPipelineNodePS2ManagerGenerateCluster(plnode,
                                                     &RxClPS2xyz,
                                                     CL_XYZ);
    RWASSERT(plnode != NULL);

    plnode = RxPipelineNodePS2ManagerGenerateCluster(plnode,
                                                     UVClusterDef,
                                                     UVType);
    RWASSERT(plnode != NULL);

    plnode = RxPipelineNodePS2ManagerGenerateCluster(plnode,
                                                     &RxClPS2rgba,
                                                     CL_RGBA);
    RWASSERT(plnode != NULL);
    plnode = RxPipelineNodePS2ManagerGenerateCluster(plnode,
                                                     &RxClPS2normal,
                                                     CL_NORMAL);
    RWASSERT(plnode != NULL);

    verify =
        RxPipelineNodePS2ManagerSetVUBufferSizes(plnode,
                                                 pipeASymbStrideOfInputCluster,
                                                 pipeASymbTSVertexCount,
                                                 pipeASymbTLTriCount);
    RWASSERT(NULL != verify);

    /* Unlock! */
    lpipe = RxLockedPipeUnlock(lpipe);
    RWASSERT(NULL != lpipe);

    /* Best to re-find nodes after an unlock */
    plnode = RxPipelineFindNodeByName(lpipe,
                                      manager->name,
                                      (RxPipelineNode *) NULL,
                                      (RwInt32 *) NULL);
    RWASSERT(plnode != NULL);

    verify = RxPipelineNodePS2ManagerSetVIFOffset(plnode,
                                                  pipeASymbVIFOffset);
    RWASSERT(NULL != verify);

    /*---- New VU Array description allowing BF Culling ----*/
    ((RxPipelineNodePS2ObjAllInOneData *)plnode->privateData)->genericVU1Index = TRUE;

    verify = RxPipelineNodePS2ManagerSetInstanceCallBack(plnode, callback);
    RWASSERT(NULL != verify);

    verify = RxPipelineNodePS2ManagerSetPostObjectCallBack(plnode,
                 _rpMatFXMaterialsPostObjectCallBack);
    RWASSERT(NULL != verify);

    result = lpipe;
    RWRETURN(result);
}

/*--- Slaves to _rpMatFXSetupDualRenderState --------------------------------*/

static RwBool
MatFXSetupDualRenderStateBlend(MatFXDualData * dualData)
{
    RwBool     result = TRUE;
    static int skyBlendStates[6][6] = {
        {0x0000008al, 0x0000000al, 0x00000088l,
         0x00000002l, 0x00000098l, 0x00000012l},
        {0x0000004al, 0x00000068l, 0x00000048l,
         0xffffffffl, 0x00000058l, 0xffffffffl},
        {0x00000089l, 0x00000009l, 0xffffffffl,
         0x00000001l, 0xffffffffl, 0xffffffffl},
        {0x00000046l, 0xffffffffl, 0x00000044l,
         0xffffffffl, 0xffffffffl, 0xffffffffl},
        {0x00000099l, 0x00000019l, 0xffffffffl,
         0xffffffffl, 0xffffffffl, 0x00000011l},
        {0x00000056l, 0xffffffffl, 0xffffffffl,
         0xffffffffl, 0x00000054l, 0xffffffffl}
    };

    /* Maybe make these static? */
    int  skySrcBlend = 2; /* Originally 2 : SRCALPHA */
    int  skyDestBlend = 3; /* Originally 3 : INVSRCALPHA */
    long local_skyAlpha_1 =
        (long) skyBlendStates[skyDestBlend][skySrcBlend];

    RWFUNCTION(RWSTRING("MatFXSetupDualRenderStateBlend"));

    /*===========================================*
     *= Let us first set the src blend mode     =*
     *===========================================*/

    /* Warning: Not all combinations of Blend render
     * states are valid, so going from
     * Cd(1-As) to Cs(1-As)+CdAs via Cs(1-As)+Cd(1-As)
     * should fail, where as going via CdAs should not.
     * Until we can set both SRC and DEST blend states together,
     * we have a hack where even when we fail,
     * we still store the RW blend state if
     * it might make sense later */

    /*
     * Src alpha, 1-src alpha, etc/
     * on PSX-II, you get:
     * ({Cs, Cd, 0} - {Cs, Cd, 0})*{As, Ad, FIX}>>7 + {Cs, Cd, 0}
     */

    switch (dualData->srcBlendMode)
    {
        case rwBLENDZERO:
            skySrcBlend = 0;
            break;
        case rwBLENDONE:
            skySrcBlend = 1;
            break;
        case rwBLENDSRCALPHA:
            skySrcBlend = 2;
            break;
        case rwBLENDINVSRCALPHA:
            skySrcBlend = 3;
            break;
        case rwBLENDDESTALPHA:
            skySrcBlend = 4;
            break;
        case rwBLENDINVDESTALPHA:
            skySrcBlend = 5;
            break;
        default:
            RWRETURN(FALSE);
    }

    /*===========================================*
     *= Now let us set the dst blend mode       =*
     *===========================================*/

    /*
     * Src alpha, 1-src alpha, etc
     * on PSX-II, you get:
     * ({Cs, Cd, 0} - {Cs, Cd, 0})*{As, Ad, FIX}>>7 + {Cs, Cd, 0}
     */
    switch (dualData->dstBlendMode)
    {
        case rwBLENDZERO:
            skyDestBlend = 0;
            break;
        case rwBLENDONE:
            skyDestBlend = 1;
            break;
        case rwBLENDSRCALPHA:
            skyDestBlend = 2;
            break;
        case rwBLENDINVSRCALPHA:
            skyDestBlend = 3;
            break;
        case rwBLENDDESTALPHA:
            skyDestBlend = 4;
            break;
        case rwBLENDINVDESTALPHA:
            skyDestBlend = 5;
            break;
        default:
            RWRETURN(FALSE);
    }

    local_skyAlpha_1 = (long) skyBlendStates[skyDestBlend][skySrcBlend];

    if (local_skyAlpha_1 < 0)
    {
        RWRETURN(FALSE);
    }
    else if ((skySrcBlend == 1) && (skyDestBlend == 1))
    {
        local_skyAlpha_1 |= (0x80l << 32);
    }

    /* Lets remember the values. */
    dualData->alpha_2 = local_skyAlpha_1;

    RWRETURN(result);
}

static RwBool
MatFXSetupDualRenderStateAddress(MatFXDualData * dualData)
{
    RwBool result = TRUE;

    long   local_skyClamp_1 = 0;

    /*===========================================*
     *= Now let us set texture addressing mode  =*
     *===========================================*/

    RWFUNCTION(RWSTRING("MatFXSetupDualRenderStateAddress"));

    RWASSERT(dualData->texture);

    /* Clamp, wrap, mirror or border */

    switch (dualData->texture->addressingU)
    {
        case rwTEXTUREADDRESSWRAP:
            local_skyClamp_1 &= ~0x3l;
            break;
        case rwTEXTUREADDRESSCLAMP:
            local_skyClamp_1 |= 1;
            break;
        case rwTEXTUREADDRESSMIRROR:
        case rwTEXTUREADDRESSBORDER:
        default:
            /* Can't do this */
            RWRETURN(FALSE);
    }

    /* Clamp, wrap, mirror or border */
    switch (dualData->texture->addressingV)
    {
        case rwTEXTUREADDRESSWRAP:
            local_skyClamp_1 &= ~0xcl;
            break;
        case rwTEXTUREADDRESSCLAMP:
            local_skyClamp_1 |= (1 << 2);
            break;
        case rwTEXTUREADDRESSMIRROR:
        case rwTEXTUREADDRESSBORDER:
        default:
            /* Can't do this */
            RWRETURN(FALSE);
    }

    /* Lets remember the values. */
    dualData->clamp_2 = local_skyClamp_1;

    RWRETURN(result);
}

static RwBool
MatFXSetupDualRenderStateRaster(MatFXDualData * dualData)
{
    RwBool    result = TRUE;
    long      local_skyTest_1 = 0x50002l;
              /* rwRENDERSTATEZTESTENABLE: Z test on */
    long      local_skyPrim_State = 0x8;
              /* Gouraud shading on */
    RwBool    local_skyAlphaTex = FALSE;
    RwRaster *local_skyTextureRaster = (RwRaster *) NULL;

    RWFUNCTION(RWSTRING("MatFXSetupDualRenderStateRaster"));

    /*===========================================*
     *= Now let us set texture raster           =*
     *===========================================*/
    RWASSERT(dualData->texture);

    /* Raster to texture with */
    local_skyTextureRaster = RwTextureGetRaster(dualData->texture);

    if (local_skyTextureRaster)
    {

        /* Do what it takes to get the raster selected */
        if (skyTexCacheAccessRaster(local_skyTextureRaster, FALSE))
        {
            local_skyPrim_State |= 0x10;

            /* If an alpha format texture enable alpha blending */
            switch (local_skyTextureRaster->cFormat &
                    (rwRASTERFORMATPIXELFORMATMASK >> 8))
            {
                case (rwRASTERFORMAT888 >> 8):
                    local_skyAlphaTex = FALSE;
                    break;
                default:
                    local_skyAlphaTex = TRUE;
                    break;
            }
        }
        else
        {
            /* We do something sensible on fail */
            local_skyAlphaTex = FALSE;
            local_skyPrim_State &= ~0x10l;
            local_skyTextureRaster = (RwRaster *) NULL;
            result = FALSE;
        }
    }
    else
    {
        local_skyAlphaTex = FALSE;
        local_skyPrim_State &= ~0x10l;
    }

    if (skyVertexAlpha || local_skyAlphaTex)
    {
        local_skyPrim_State |= 0x40;
    }
    else
    {
        local_skyPrim_State &= ~0x40l;
    }

    /* Finally iff local_skyAlphaTex we turn on Alpha test */
    if (local_skyAlphaTex)
    {
        if (!(local_skyTest_1 & 1))
        {
            local_skyTest_1 |= 1;
        }
    }
    else
    {
        if (local_skyTest_1 & 1)
        {
            local_skyTest_1 &= ~1l;
        }
    }

    /* Lets remember the values. */
    dualData->test_2 = local_skyTest_1;

    RWRETURN(result);
}

/*--- Device data fucntions -------------------------------------------------*/
RwBool
_rpMatFXSetupDualRenderState(MatFXDualData * dualData,
                             RwRenderState nState)
{
    RwBool result = FALSE;

    RWFUNCTION(RWSTRING("_rpMatFXSetupDualRenderState"));
    RWASSERT(dualData);

    RWASSERT(rwRENDERSTATENARENDERSTATE != nState);
    RWASSERT(rwRENDERSTATETEXTUREPERSPECTIVE != nState);
    RWASSERT(rwRENDERSTATEZTESTENABLE != nState);
    RWASSERT(rwRENDERSTATESHADEMODE != nState);
    RWASSERT(rwRENDERSTATEZWRITEENABLE != nState);
    RWASSERT(rwRENDERSTATETEXTUREFILTER != nState);
    RWASSERT(rwRENDERSTATEVERTEXALPHAENABLE != nState);
    RWASSERT(rwRENDERSTATEBORDERCOLOR != nState);
    RWASSERT(rwRENDERSTATEFOGENABLE != nState);
    RWASSERT(rwRENDERSTATEFOGCOLOR != nState);
    RWASSERT(rwRENDERSTATEFOGTYPE != nState);
    RWASSERT(rwRENDERSTATEFOGDENSITY != nState);
    RWASSERT(rwRENDERSTATEFOGTABLE != nState);
    RWASSERT(rwRENDERSTATEALPHAPRIMITIVEBUFFER != nState);
    RWASSERT(rwRENDERSTATESTENCILENABLE != nState);
    RWASSERT(rwRENDERSTATESTENCILFAIL != nState);
    RWASSERT(rwRENDERSTATESTENCILZFAIL != nState);
    RWASSERT(rwRENDERSTATESTENCILPASS != nState);
    RWASSERT(rwRENDERSTATESTENCILFUNCTION != nState);
    RWASSERT(rwRENDERSTATESTENCILFUNCTIONREF != nState);
    RWASSERT(rwRENDERSTATESTENCILFUNCTIONMASK != nState);
    RWASSERT(rwRENDERSTATEFORCEENUMSIZEINT != nState);

    switch (nState)
    {
        case rwRENDERSTATESRCBLEND:
        case rwRENDERSTATEDESTBLEND:
            result = MatFXSetupDualRenderStateBlend(dualData);
            break;

        case rwRENDERSTATETEXTUREADDRESS:
        case rwRENDERSTATETEXTUREADDRESSU:
        case rwRENDERSTATETEXTUREADDRESSV:
            result = MatFXSetupDualRenderStateAddress(dualData);
            break;

        case rwRENDERSTATETEXTURERASTER:
            result = MatFXSetupDualRenderStateRaster(dualData);
            break;

        case rwRENDERSTATEALPHAPRIMITIVEBUFFER:
        case rwRENDERSTATEBORDERCOLOR:
        case rwRENDERSTATECULLMODE:
        case rwRENDERSTATEFOGCOLOR:
        case rwRENDERSTATEFOGDENSITY:
        case rwRENDERSTATEFOGENABLE:
        case rwRENDERSTATEFOGTABLE:
        case rwRENDERSTATEFOGTYPE:
        case rwRENDERSTATEFORCEENUMSIZEINT:
        case rwRENDERSTATENARENDERSTATE:
        case rwRENDERSTATESHADEMODE:
        case rwRENDERSTATESTENCILENABLE:
        case rwRENDERSTATESTENCILFAIL:
        case rwRENDERSTATESTENCILFUNCTION:
        case rwRENDERSTATESTENCILFUNCTIONMASK:
        case rwRENDERSTATESTENCILFUNCTIONREF:
        case rwRENDERSTATESTENCILFUNCTIONWRITEMASK:
        case rwRENDERSTATESTENCILPASS:
        case rwRENDERSTATESTENCILZFAIL:
        case rwRENDERSTATETEXTUREFILTER:
        case rwRENDERSTATETEXTUREPERSPECTIVE:
        case rwRENDERSTATEVERTEXALPHAENABLE:
        case rwRENDERSTATEZTESTENABLE:
        case rwRENDERSTATEZWRITEENABLE:
            break;

    }

    RWRETURN(result);
}

/*--- Upload texture --------------------------------------------------------*/
RwBool
_rpMatFXUploadTexture(const RwTexture * texture,
                      const RxPS2DMASessionRecord * DMASessionRecord,
                      const RxPS2Mesh * Mesh)
{
    RpMaterial *material = Mesh->mesh->material;

    RWFUNCTION(RWSTRING("_rpMatFXUploadTexture"));

    /* Set up render state */
    skyVertexAlpha = (material->color.alpha != 255);

    if (NULL != texture)
    {
        if (texture->raster != skyTextureRaster)
        {
            /* Raster to texture with */
            skyTextureRaster = texture->raster;

            skyAlphaTex = FALSE;
            if (NULL != skyTextureRaster)
            {
                RwBool texUploadFailed;
                const RwUInt32      cFormat =
                    (skyTextureRaster->cFormat &
                     (rwRASTERFORMATPIXELFORMATMASK >> 8));

                /* If an alpha format texture - enable alpha blending */
                skyAlphaTex |=
                    (((rwRASTERFORMAT1555 >> 8) == cFormat) |
                     ((rwRASTERFORMAT8888 >> 8) == cFormat));

                skyPrim_State |= 0x10;

                /* Do what it takes to get the raster selected */
                texUploadFailed =
                    RpSkyTexCacheAccessRaster(skyTextureRaster, FALSE);
                RWASSERT(texUploadFailed);
            }
            else
            {
                skyPrim_State &= ~0x10l;
            }

            if (skyVertexAlpha || skyAlphaTex)
            {
                skyPrim_State |= 0x40;
            }
            else
            {
                skyPrim_State &= ~0x40l;
            }

            /* Finally iff skyAlphaTex we turn on Alpha test */
            if (skyAlphaTex)
            {
                skyTest_1 |= 1;
            }
            else
            {
                skyTest_1 &= ~1l;
            }
        }

        /* NOTE: the following is BAD.
         *       people can change texture filter/address modes
         *       behind our back, so we have to test that every damn time!
         *       As with everything else we need to introduce locking either
         *       of textures or parent objects such as materials/geometrys
         *       to avoid redundant effort here in the fast-path */

        /* For Im3D renderstate have been set already */
        if (DMASessionRecord->objType != rxOBJTYPE_IM3D)
        {
            /* A more efficient texture command production than a
             * switch statement, which uses a jump table pulled into
             * the d-cache from the text segment.
             * ASSUMES THESE ENUM VALUES AREN'T GOING ANYWHERE  :-)
             */

            /* *INDENT-OFF* */

            do
            {
                /* We try to force CW to work by using a temp var */
                long tmp =0 ;

                asm __volatile__ (".set noreorder
                                  .set noat
                                  ori $at, $0, 0x6
                                  beql $at, %2, nps2mb0
                                  ori %0, %1, 0x160

                                  ori $at, $0, 0x5
                                  beql $at, %2, nps2mb0
                                  ori %0, %1, 0xc0

                                  ori $at, $0, 0x4
                                  beql $at, %2, nps2mb0
                                  ori %0, %1, 0x120

                                  ori $at, $0, 0x3
                                  beql $at, %2, nps2mb0
                                  ori %0, %1, 0x80

                                  ori $at, $0, 0x2
                                  beql $at, %2, nps2mb0
                                  ori %0, %1, 0x60

                                  .set reorder
                                  .set at

                                  nps2mb0:
                                  "
                                  : "=r" (tmp)
                                  : "r" (skyTex1_1 & ~0x1e0l),
                                  "r" (texture->filtering));
                skyTex1_1 = tmp;
            }
            while (0);
            /* *INDENT-ON* */

            /* Clamp, wrap, mirror or border.
             * We now have two addressing modes,
             * one for U and one for V directions.
             * If the app has never set the V direction,
             * then default both U and V to the setting
             * from the U direction, which will have been set. */

            {
                RwTextureAddressMode u;
                RwTextureAddressMode v;

                u = RwTextureGetAddressingU(texture);
                v = RwTextureGetAddressingV(texture);

                skyClamp_1 = 0; /* default to repeat in U and V */

                if (u == rwTEXTUREADDRESSCLAMP)
                {
                    skyClamp_1 |= 1;
                }

                if (v == rwTEXTUREADDRESSCLAMP)
                {
                    skyClamp_1 |= 4;
                }
            }
        }

        /* Now write to pkt */
        sweOpenLocalPkt(SWE_LPS_CONT, -4);

        {
            RwUInt64            tmp, tmp1;
            u_long128           ltmp = 0;

            tmp = ( /* NLOOP */ 3l
#ifdef LESSEOPS
                    | /* EOP */ (0l << 15)
#else /* LESSEOPS */
                    | /* EOP */ (1l << 15)
#endif /* LESSEOPS */
                    | /* PRE */ (0l << 46)
                    | /* FLG */ (0l << 58)
                    | /* NREG */ (1l << 60));
            tmp1 = /* A+D */ (0xel << (64 - 64));
            MAKE128(ltmp, tmp1, tmp);
            SWEADDCONTGIFFAST(ltmp, 1);

            tmp = skyTest_1;
            tmp1 = (GS_TEST_1 << (64 - 64));
            MAKE128(ltmp, tmp1, tmp);
            SWEADDCONTFAST(ltmp);

            tmp = skyTex1_1;
            tmp1 = (GS_TEX1_1 << (64 - 64));
            MAKE128(ltmp, tmp1, tmp);
            SWEADDCONTFAST(ltmp);

            tmp = skyClamp_1;
            tmp1 = (GS_CLAMP_1 << (64 - 64));
            MAKE128(ltmp, tmp1, tmp);
            SWEADDCONTFAST(ltmp);
        }
    }

    else
    {
        _rwSkySetRenderState(rwRENDERSTATETEXTURERASTER, NULL);
    }

    RWRETURN(TRUE);
}


/*--- Setup the skin / matfx link -------------------------------------------*/
static RwBool
MatFXSetupSkinning(void)
{
    _rpSkinLinkCallBack *skinLinkCallBack;
    RwInt32 skinPluginOffset;

    RwBool success;

    RWFUNCTION(RWSTRING("MatFXSetupSkinning"));

    success = FALSE;
    skinPluginOffset = RwEngineGetPluginOffset(rwID_SKINPLUGIN);
    if(-1 != skinPluginOffset)
    {
        /* Get the Skin Plugin register function. */
        skinLinkCallBack = RPSKINLINKGETDATA(RwEngineInstance);
        RWASSERT(NULL != skinLinkCallBack);
        RWASSERT(NULL != skinLinkCallBack->skinLinkPopulateCallBack);

        /* Populate the matfx skinning function pointers. */
        (skinLinkCallBack->skinLinkPopulateCallBack)(&matFXSkinning);

        /* Setup the skinning instance callbacks and transforms. */
        matFXPipeState.skinning.instanceCallBacks[rpMATFXEFFECTNULL] =
            matFXSkinning.skinPS2ManagerInstanceCallBack;
        matFXPipeState.skinning.vuCodeCache[rpMATFXEFFECTNULL] =
            matFXSkinning.skinVU1Transform;
        matFXPipeState.skinningUV2.instanceCallBacks[rpMATFXEFFECTNULL] =
            matFXSkinning.skinPS2ManagerInstanceCallBack;
        matFXPipeState.skinningUV2.vuCodeCache[rpMATFXEFFECTNULL] =
            matFXSkinning.skinVU1Transform;

        success = TRUE;
    }

    RWRETURN(success);
}


/*--- Test the atomic for a skin --------------------------------------------*/
static RwBool
MatFXTestSkinning(RpAtomic *atomic)
{
    RwBool success;

    RWFUNCTION(RWSTRING("MatFXTestSkinning"));
    RWASSERT(NULL != atomic);

    success = ( (NULL != matFXSkinning.skinAtomicGetSkin) &&
                (NULL != matFXSkinning.skinAtomicGetSkin(atomic)) );

    RWRETURN(success);
}


/*--- Create and destory pipelines ------------------------------------------*/
RwBool
_rpMatFXPipelinesCreate(void)
{
    RwBool skinning;
    RWFUNCTION(RWSTRING("_rpMatFXPipelinesCreate"));

    /* Is the skin plugin attached? */
    skinning = MatFXSetupSkinning();

    /* First we create single UV pipes. */

    /* First for the atomics ... */
    matFXPipeState.atomicPipeline = MatFXMaterialsPipelineCreate(
        rxOBJTYPE_ATOMIC, UV1, MatFXMaterialsInstance);
    if (NULL == matFXPipeState.atomicPipeline)
    {
        RWRETURN(FALSE);
    }

    /* ... and then the world sectors ... */
    matFXPipeState.worldSectorPipeline = MatFXMaterialsPipelineCreate(
        rxOBJTYPE_WORLDSECTOR, UV1, MatFXMaterialsWorldInstance);
    if (NULL == matFXPipeState.worldSectorPipeline)
    {
        RWRETURN(FALSE);
    }

    /* Secondly we create double UV pipes. */

    /* First for the atomics ... */
    matFXPipeState.atomicPipelineUV2 = MatFXMaterialsPipelineCreate(
        rxOBJTYPE_ATOMIC, UV2, MatFXMaterialsUV2Instance);
    if (NULL == matFXPipeState.atomicPipelineUV2)
    {
        RWRETURN(FALSE);
    }

    /* ... and then the world sectors ... */
    matFXPipeState.worldSectorPipelineUV2 = MatFXMaterialsPipelineCreate(
        rxOBJTYPE_WORLDSECTOR, UV2, MatFXMaterialsWorldUV2Instance);
    if (NULL == matFXPipeState.worldSectorPipelineUV2)
    {
        RWRETURN(FALSE);
    }

    /* Should we create the skinned pipelines? */
    if(TRUE == skinning)
    {
        /* Yep. */
        matFXPipeState.skinPipelineUV2 = MatFXMaterialsSkinPipelineCreate(
            UV2, MatFXMaterialsSkinInstance);
        if (NULL == matFXPipeState.skinPipelineUV2)
        {
            RWRETURN(FALSE);
        }

        matFXPipeState.skinPipeline = MatFXMaterialsSkinPipelineCreate(
            UV1, MatFXMaterialsSkinUV2Instance);
        if (NULL == matFXPipeState.skinPipeline)
        {
            RWRETURN(FALSE);
        }
    }

    RWRETURN(TRUE);
}

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

    if (NULL != matFXPipeState.atomicPipeline)
    {
        RxPipelineDestroy(matFXPipeState.atomicPipeline);
        matFXPipeState.atomicPipeline = (RxPipeline *) NULL;
    }

    if (NULL != matFXPipeState.worldSectorPipeline)
    {
        RxPipelineDestroy(matFXPipeState.worldSectorPipeline);
        matFXPipeState.worldSectorPipeline = (RxPipeline *) NULL;
    }

    if (NULL != matFXPipeState.atomicPipelineUV2)
    {
        RxPipelineDestroy(matFXPipeState.atomicPipelineUV2);
        matFXPipeState.atomicPipelineUV2 = (RxPipeline *) NULL;
    }

    if (NULL != matFXPipeState.worldSectorPipelineUV2)
    {
        RxPipelineDestroy(matFXPipeState.worldSectorPipelineUV2);
        matFXPipeState.worldSectorPipelineUV2 = (RxPipeline *) NULL;
    }

    if (NULL != matFXPipeState.skinPipeline)
    {
        RxPipelineDestroy(matFXPipeState.skinPipeline);
        matFXPipeState.skinPipeline = (RxPipeline *) NULL;
    }

    if (NULL != matFXPipeState.skinPipelineUV2)
    {
        RxPipelineDestroy(matFXPipeState.skinPipelineUV2);
        matFXPipeState.skinPipelineUV2 = (RxPipeline *) NULL;
    }

    /* Set the present pipes to NULL. */
    matFXPipeState.atomic.effectType = rpMATFXEFFECTNULL;
    matFXPipeState.atomicUV2.effectType = rpMATFXEFFECTNULL;
    matFXPipeState.worldSector.effectType = rpMATFXEFFECTNULL;
    matFXPipeState.worldSectorUV2.effectType = rpMATFXEFFECTNULL;
    matFXPipeState.skinning.effectType = rpMATFXEFFECTNULL;
    matFXPipeState.skinningUV2.effectType = rpMATFXEFFECTNULL;

    RWRETURN(TRUE);
}

/*--- Attach pipelines ------------------------------------------------------*/
RpWorldSector *
_rpMatFXPipelineWorldSectorSetup(RpWorldSector * worldSector)
{
    RpWorld  *world;
    RwUInt32  flags;

    RWFUNCTION(RWSTRING("_rpMatFXPipelineWorldSectorSetup"));
    RWASSERT(worldSector);

    world = RpWorldSectorGetWorld(worldSector);
    RWASSERT(world);

    flags = RpWorldGetFlags(world);
    if (flags & rpWORLDTEXTURED2)
    {
        RpWorldSectorSetPipeline(worldSector,
            matFXPipeState.worldSectorPipelineUV2);
    }
    else
    {
        RpWorldSectorSetPipeline(worldSector,
                                         matFXPipeState.worldSectorPipeline);
    }

    RWRETURN(worldSector);
}

RpAtomic *
_rpMatFXPipelineAtomicSetup(RpAtomic * atomic)
{
    RpGeometry *geometry;
    RwUInt32    flags;

    RWFUNCTION(RWSTRING("_rpMatFXPipelineAtomicSetup"));
    RWASSERT(atomic);

    geometry = RpAtomicGetGeometry(atomic);
    RWASSERT(geometry);

    flags = RpGeometryGetFlags(geometry);

    /* Do we have the skin plugin attached and is the atomic a skinned. */
    if(TRUE == MatFXTestSkinning(atomic))
    {
        if (flags & rpGEOMETRYTEXTURED2)
        {
            RWASSERT(NULL != matFXPipeState.skinPipelineUV2);
            RpAtomicSetPipeline(atomic,
                                        matFXPipeState.skinPipelineUV2);
        }
        else
        {
            RWASSERT(NULL != matFXPipeState.skinPipeline);
            RpAtomicSetPipeline(atomic, matFXPipeState.skinPipeline);
        }
    }
    else
    {
        if (flags & rpGEOMETRYTEXTURED2)
        {
            RWASSERT(NULL != matFXPipeState.atomicPipelineUV2);
            RpAtomicSetPipeline(atomic,
                                        matFXPipeState.atomicPipelineUV2);
        }
        else
        {
            RWASSERT(NULL != matFXPipeState.atomicPipeline);
            RpAtomicSetPipeline(atomic, matFXPipeState.atomicPipeline);
        }
    }

    RWRETURN(atomic);
}

/*--- Device data fucntions -------------------------------------------------*/
RwTexture *
_rpMatFXSetupBumpMapTexture(const RwTexture * baseTexture,
                            const RwTexture * effectTexture)
{
    RwTexture *texture;

    RWFUNCTION(RWSTRING("_rpMatFXSetupBumpMapTexture"));

    texture = _rpMatFXTextureMaskCreate(baseTexture, effectTexture);

    RWRETURN(texture);
}

/*--- Enable effects --------------------------------------------------------*/
RpMaterial *
_rpMatFXEnvMapEnable(RpMaterial * material)
{
    RWFUNCTION(RWSTRING("_rpMatFXEnvMapEnable"));
    RWRETURN(material);
}

RpMaterial *
_rpMatFXBumpMapEnable(RpMaterial * material)
{
    RWFUNCTION(RWSTRING("_rpMatFXBumpMapEnable"));
    RWRETURN(material);
}

RpMaterial *
_rpMatFXDualEnable(RpMaterial * material)
{
    RWFUNCTION(RWSTRING("_rpMatFXDualEnable"));
    RWRETURN(material);
}
