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

/*----------------------------------------------------------------------*
 *-   Includes                                                         -*
 *----------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*==== D3D8 includes ====*/
#include <d3d8.h>

/*==== RW libs includes ====*/
#include "rpplugin.h"
#include <rpdbgerr.h>
#include <rwcore.h>
#include <archive/rpmatfx310.h>

#include "effectpipes.h"

#include "BumpMapPShader.h"
#include "EnvMapPShader.h"
#include "EnvMapNoBaseTexturePShader.h"
#include "BumpEnvMapPShader.h"

#if (!defined(DOXYGEN))
static const char rcsid[] __RWUNUSED__ = 
    "@@@@(#)$Id: effectPipesD3D8.c,v 1.31 2001/09/26 10:47:59 johns Exp $";
#endif /* (!defined(DOXYGEN)) */

#define BUMPMAP_FVF                             \
    (D3DFVF_XYZ |                               \
     D3DFVF_NORMAL |                            \
     D3DFVF_DIFFUSE |                           \
     D3DFVF_TEX2 |                              \
     D3DFVF_TEXCOORDSIZE2(0) |                  \
     D3DFVF_TEXCOORDSIZE2(1))

/*----------------------------------------------------------------------*
 *-   Local Types                                                      -*
 *----------------------------------------------------------------------*/

typedef struct _rxD3D8BumpMapVertex RxD3D8BumpMapVertex;
struct _rxD3D8BumpMapVertex
{
    RwV3d       position;
    RwV3d       normal;
    RwUInt32    color;
    RwTexCoords texcoords1;
    RwTexCoords texcoords2;
};

/*----------------------------------------------------------------------*
 *-   Local/static Globals                                             -*
 *----------------------------------------------------------------------*/

static RxPipeline   *MatFXAtomicPipe = NULL,
                    *MatFXWorldSectorPipe = NULL;

static RwUInt32     BumpMapPixelShader = 0;

static RwUInt32     EnvMapPixelShader = 0;
static RwUInt32     EnvMapNoBaseTexturePixelShader = 0;

static RwUInt32     BumpEnvMapPixelShader = 0;

static RwBool       VideoCardSupportsMultitexture = FALSE;
static RwBool       VideoCardSupportsMultiplyAdd = FALSE;
static RwUInt32     VideoCardMaxTextureBlendStages = 0;

/*----------------------------------------------------------------------*
 *-   Defines                                                          -*
 *----------------------------------------------------------------------*/

#define MATFXD3D8ENVMAPGETDATA(material, pass)                          \
  &((*((rpMatFXMaterialData **)                                         \
       (((RwUInt8 *)material) +                                         \
        MatFXMaterialDataOffset)))->data[pass].data.envMap)

#define MATFXD3D8BUMPMAPGETDATA(material)                               \
  &((*((rpMatFXMaterialData **)                                         \
       (((RwUInt8 *)material) +                                         \
        MatFXMaterialDataOffset)))->data[rpSECONDPASS].data.bumpMap)

#define MATFXD3D8DUALGETDATA(material)                                  \
  &((*((rpMatFXMaterialData **)                                         \
       (((RwUInt8 *)material) +                                         \
        MatFXMaterialDataOffset)))->data[rpSECONDPASS].data.dual)

/*----------------------------------------------------------------------*
 *-   Functions                                                        -*
 *----------------------------------------------------------------------*/
/********************************************************/

/****************************************************************************
 FrameGetFirstLight
 
 Purpose: Find and return the first light that owns the frame
 
 */
static const RpLight *
FrameGetFirstLight(const RwFrame *frame)
{
    const RpLight         *light = NULL;
    const RwLLLink        *current, *next, *end;
    int objcount = 0;

    RWFUNCTION(RWSTRING("FrameGetFirstLight"));
    
    current = rwLinkListGetFirstLLLink(&frame->objectList);
    end = rwLinkListGetTerminator(&frame->objectList);
    
    while (current != end)
    {
        const RwObject *object;

        next = rwLLLinkGetNext(current);
        object = (const RwObject *) 
            rwLLLinkGetData(current,RwObjectHasFrame, lFrame);

        if (RwObjectGetType(object) == rpLIGHT)
        {
            light = (const RpLight *)object;

            break;
        }

        objcount++;
        current = next;
    }

    RWRETURN((light));
}

/****************************************************************************
 CalculatePerturbedUVs
 
 */
static RwBool
CalculatePerturbedUVs(RxD3D8InstanceData *instancedData,
                    RxD3D8BumpMapVertex *bumpVertex)
{
    RwUInt32 numVerts;
    RwUInt8 *processedFlags;

    RWFUNCTION(RWSTRING("CalculatePerturbedUVs"));

    numVerts = instancedData->numVertices;

    processedFlags = RwMalloc(instancedData->numVertices);
    memset(processedFlags, 0, instancedData->numVertices);

    if (instancedData->indexBuffer)
    {
        const MatFXBumpMapData  *bumpMapData = MATFXD3D8BUMPMAPGETDATA(instancedData->material);
        const RwReal            factor = bumpMapData->coef * bumpMapData->invBumpWidth;
        RwFrame                 *bumpFrame  = bumpMapData->frame;
        RwInt32                 i, maxIndex;
        RwMatrix                *objToWorld;
        RwMatrix                *worldToObj;
        const RpLight           *light;
        RwV3d                   lightPosObj;
        RwReal                  *lockedVB;
        RxVertexIndex           *inds;
        RwInt32                 texCoordsOffset;

        if (bumpFrame == NULL)
        {
            bumpFrame = RwCameraGetFrame(RwCameraGetCurrentCamera());
            RWASSERT(bumpFrame);
        }

        objToWorld = RwMatrixCreate();
        worldToObj = RwMatrixCreate();

        RwD3D8GetTransform(D3DTS_WORLD, objToWorld);

        RwMatrixInvert(worldToObj, objToWorld);

        light = FrameGetFirstLight(bumpFrame);

        if ( light != NULL &&
            (RpLightGetType(light) == rpLIGHTDIRECTIONAL ||
             RpLightGetType(light) == rpLIGHTSPOT ||
             RpLightGetType(light) == rpLIGHTSPOTSOFT) )
        {
            const RwV3d *lightAt;
            RwV3d       lightAtInv;

            lightAt = RwMatrixGetAt(RwFrameGetLTM(bumpFrame));

            lightAtInv.x = - (lightAt->x);
            lightAtInv.y = - (lightAt->y);
            lightAtInv.z = - (lightAt->z);

            RwV3dTransformVectors(&lightPosObj, &lightAtInv, 1, worldToObj);
        }
        else
        {
            const RwV3d *lightPos;

            lightPos = RwMatrixGetPos(RwFrameGetLTM(bumpFrame));

            RwV3dTransformPoints(&lightPosObj, lightPos, 1, worldToObj);
        }

        RwMatrixDestroy(worldToObj);
        RwMatrixDestroy(objToWorld);

        maxIndex = instancedData->numIndices;

        /* Get pointer to the verter information */
        IDirect3DVertexBuffer8_Lock((LPDIRECT3DVERTEXBUFFER8)instancedData->vertexBuffer,
                            instancedData->baseIndex * instancedData->stride,
                            instancedData->numVertices * instancedData->stride,
                            (RwUInt8 **)&lockedVB,
                            D3DLOCK_NOSYSLOCK);

        /* Get pointer to the index information */
        IDirect3DIndexBuffer8_Lock((LPDIRECT3DINDEXBUFFER8)instancedData->indexBuffer,
                            0,
                            instancedData->numIndices * sizeof(RwUInt16),
                            (RwUInt8 **)&inds,
                            D3DLOCK_NOSYSLOCK);

        /* Calculates the texture coords offset */
        texCoordsOffset = 3;

        if (instancedData->vertexShader & D3DFVF_NORMAL)
        {
            texCoordsOffset += 3;
        }

        if (instancedData->vertexShader & D3DFVF_DIFFUSE)
        {
            texCoordsOffset += 1;
        }

        for (i = 0; i < maxIndex; i++)
        {
            if (processedFlags[inds[i]] == FALSE)
            {
                const RwReal *vert1 = NULL;
                const RwReal *vert2 = NULL;
                const RwReal *vert3 = NULL;
                RwV3d   b, t, n, temp1, temp2, l;
                RwReal  unused;

                if (instancedData->primType == D3DPT_TRIANGLELIST)
                {
                    vert1 = lockedVB + ((inds[i] * instancedData->stride) / sizeof(RwReal));
                    vert2 = lockedVB + ((inds[((i/3)*3) + (i+1)%3] * instancedData->stride) / sizeof(RwReal));
                    vert3 = lockedVB + ((inds[((i/3)*3) + (i+2)%3] * instancedData->stride) / sizeof(RwReal));
                }
                else if (instancedData->primType == D3DPT_TRIANGLESTRIP)
                {
                    RwInt32 i1, i2, i3;
                    i1 = i;
                    if (i < 2)
                    {
                        if (i%2)
                        {
                            i2 = i + 2;
                            i3 = i + 1;
                        }
                        else
                        {
                            i2 = i + 1;
                            i3 = i + 2;
                        }
                    }
                    else
                    {
                        if (i%2)
                        {
                            i2 = i - 1;
                            i3 = i - 2;
                        }
                        else
                        {
                            i2 = i - 2;
                            i3 = i - 1;
                        }
                    }
                    vert1 = lockedVB + ((inds[i1] * instancedData->stride) / sizeof(RwReal));
                    vert2 = lockedVB + ((inds[i2] * instancedData->stride) / sizeof(RwReal));
                    vert3 = lockedVB + ((inds[i3] * instancedData->stride) / sizeof(RwReal));
                }

                if (instancedData->vertexShader & D3DFVF_NORMAL)
                {
                    n.x = vert1[3];
                    n.y = vert1[4];
                    n.z = vert1[5];
                }
                else
                {
                    n.x = 0;
                    n.y = 0;
                    n.z = 0;
                }

                RwV3dSub(&l, &lightPosObj, (const RwV3d *)vert1);
                _rwV3dNormalizeMacro(unused, &l, &l);

                /* Check to see whether the light is behind the triangle */
                if (1) /* RwV3dDotProduct(&l, &n) > 0.0f) */
                {
                    RwV2d               shift;
                    RxD3D8BumpMapVertex *currentBumpVertex;

                    /* A nice little algorithm to find the tangent vector */
                    RwV3dSub(&temp1, (const RwV3d *)vert2, (const RwV3d *)vert1);
                    RwV3dSub(&temp2, (const RwV3d *)vert3, (const RwV3d *)vert1);

                    RwV3dScale(&temp1, &temp1, vert3[texCoordsOffset+1] - vert1[texCoordsOffset+1]);
                    RwV3dScale(&temp2, &temp2, vert2[texCoordsOffset+1] - vert1[texCoordsOffset+1]);

                    RwV3dSub(&t, &temp1, &temp2);
                    _rwV3dNormalizeMacro(unused, &t, &t);
                    RwV3dCrossProduct(&b, &t, &n);

                    /*
                     * So now that we have b, t and n, we have the tangent
                     * space coordinate system axes.
                     */
                    shift.x = RwV3dDotProduct(&t, &l);
                    shift.y = RwV3dDotProduct(&b, &l);

                    currentBumpVertex = &(bumpVertex[inds[i]]);

                    currentBumpVertex->position.x = vert1[0];
                    currentBumpVertex->position.y = vert1[1];
                    currentBumpVertex->position.z = vert1[2];

                    if (instancedData->vertexShader & D3DFVF_NORMAL)
                    {
                        currentBumpVertex->normal.x = vert1[3];
                        currentBumpVertex->normal.y = vert1[4];
                        currentBumpVertex->normal.z = vert1[5];

                        if (instancedData->vertexShader & D3DFVF_DIFFUSE)
                        {
                            currentBumpVertex->color = *((const RwUInt32 *)vert1+6);
                        }
                        else
                        {
                            currentBumpVertex->color = 0xffffffff;
                        }
                    }
                    else
                    {
                        currentBumpVertex->normal.x = 0;
                        currentBumpVertex->normal.y = 0;
                        currentBumpVertex->normal.z = 0;

                        if (instancedData->vertexShader & D3DFVF_DIFFUSE)
                        {
                            currentBumpVertex->color = *((const RwUInt32 *)vert1+3);
                        }
                        else
                        {
                            currentBumpVertex->color = 0xffffffff;
                        }
                    }
                    
                    currentBumpVertex->texcoords1.u = vert1[texCoordsOffset];
                    currentBumpVertex->texcoords1.v = vert1[texCoordsOffset+1];
                    
                    currentBumpVertex->texcoords2.u = vert1[texCoordsOffset] - (shift.x * factor);
                    currentBumpVertex->texcoords2.v = vert1[texCoordsOffset+1] - (shift.y * factor);
                }

                processedFlags[inds[i]] = TRUE;
            }
        }

        IDirect3DIndexBuffer8_Unlock((LPDIRECT3DINDEXBUFFER8)instancedData->indexBuffer);

        IDirect3DVertexBuffer8_Unlock((LPDIRECT3DVERTEXBUFFER8)instancedData->vertexBuffer);
    }

    RwFree(processedFlags);

    RWRETURN(TRUE);
}


/****************************************************************************
 ApplyEnvMapTextureMatrix
 
 */
static RwBool
ApplyEnvMapTextureMatrix(RwUInt32 stage, RwFrame *frame)
{
    static const RwMatrix texMat =
    {
        {((RwReal)0.5), ((RwReal)0.0), ((RwReal)0.0)}, 0,
        {((RwReal)0.0),-((RwReal)0.5), ((RwReal)0.0)}, 0,
        {((RwReal)0.0), ((RwReal)0.0), ((RwReal)1.0)}, 0,
        {((RwReal)0.5), ((RwReal)0.5), ((RwReal)0.0)}, 0
    };

    RWFUNCTION(RWSTRING("ApplyEnvMapTextureMatrix"));

    if (frame)
    {
        const RwMatrix    *camMtx;
        const RwMatrix    *envMtx;
        RwMatrix    *invMtx;
        RwMatrix    *tmpMtx;
        RwMatrix    *result;

        invMtx = RwMatrixCreate();
        tmpMtx = RwMatrixCreate();
        result = RwMatrixCreate();

        /* Transform the normals back into world space */
        camMtx = RwFrameGetLTM(RwCameraGetFrame(RwCameraGetCurrentCamera()));

        /* Transfrom the normals by the inverse of the env maps frame */
        envMtx = RwFrameGetLTM(frame);
    
        RwMatrixInvert(invMtx, envMtx);

        RwMatrixMultiply(tmpMtx, invMtx, camMtx);

        tmpMtx->right.x = -tmpMtx->right.x;
        tmpMtx->right.y = -tmpMtx->right.y;
        tmpMtx->right.z = -tmpMtx->right.z;

        tmpMtx->flags = 0;

        tmpMtx->pos.x = 0.0f;
        tmpMtx->pos.y = 0.0f;
        tmpMtx->pos.z = 0.0f;

        RwMatrixMultiply(result, tmpMtx, &texMat);

        RwD3D8SetTransform(stage, result);

        RwMatrixDestroy(result);
        RwMatrixDestroy(tmpMtx);
        RwMatrixDestroy(invMtx);
    }
    else
    {
        RwD3D8SetTransform(stage, &texMat);
    }

    RWRETURN(TRUE);
}

/****************************************************************************
 D3D8AtomicMatFXDefaultRender
 
 Purpose:
 
 On entry:
                
 On exit:
 */
static void
D3D8AtomicMatFXDefaultRender(RxD3D8InstanceData *instancedData,
                             RwUInt32 flags)
{
    RWFUNCTION(RWSTRING("D3D8AtomicMatFXDefaultRender"));

    if (flags & (rxGEOMETRY_TEXTURED|rpGEOMETRYTEXTURED2))
    {
        RwD3D8SetTexture(instancedData->material->texture, 0);
    }
    else
    {
        RwD3D8SetTexture(NULL, 0);
    }

    if (instancedData->vertexAlpha ||
        (0xFF != instancedData->material->color.alpha))
    {
        RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE);
    }
    else
    {
        RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE);
    }

    if (instancedData->vertexAlpha)
    {
        RwD3D8SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1);
    }
    else
    {
        RwD3D8SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_MATERIAL);
    }

    /*
     * Set the default Pixel shader
     */
    RwD3D8SetPixelShader(0);

    /*
     * Vertex shader
     */
    RwD3D8SetVertexShader(instancedData->vertexShader);

    /*
     * Set the stream source
     */
    RwD3D8SetStreamSource(0, instancedData->vertexBuffer,
                              instancedData->stride);
    /*
     * Set Indices
     */
    RwD3D8SetIndices(instancedData->indexBuffer, instancedData->baseIndex);

    /*
     * Draw the indexed primitive
     */
    RwD3D8DrawIndexedPrimitive((D3DPRIMITIVETYPE)instancedData->primType,
                                  0, instancedData->numVertices,
                                  0, instancedData->numIndices);

    RWRETURNVOID();
}

/****************************************************************************
 D3D8AtomicMatFXRenderBlack
 
 Purpose:
 
 On entry:
                
 On exit:
 */
static void
D3D8AtomicMatFXRenderBlack(RxD3D8InstanceData *instancedData)
{
    RWFUNCTION(RWSTRING("D3D8AtomicMatFXRenderBlack"));

    if (instancedData->vertexAlpha ||
        (0xFF != instancedData->material->color.alpha))
    {
        RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE);
    }
    else
    {
        RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE);
    }

    if (instancedData->vertexAlpha)
    {
        RwD3D8SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1);
    }
    else
    {
        RwD3D8SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_MATERIAL);
    }

    /*
     * Set the default Pixel shader
     */
    RwD3D8SetPixelShader(0);

    /*
     * Vertex shader
     */
    RwD3D8SetVertexShader(instancedData->vertexShader);

    /*
     * Set the stream source
     */
    RwD3D8SetStreamSource(0, instancedData->vertexBuffer,
                              instancedData->stride);
    /*
     * Set Indices
     */
    RwD3D8SetIndices(instancedData->indexBuffer, instancedData->baseIndex);

    /*
     * Draw the indexed primitive
     */
    RwD3D8DrawIndexedPrimitive((D3DPRIMITIVETYPE)instancedData->primType,
                                  0, instancedData->numVertices,
                                  0, instancedData->numIndices);

    RWRETURNVOID();
}

/****************************************************************************
 D3D8AtomicMatFXDualPassRender
 
 Purpose:
 
 On entry:
                
 On exit:
 */
static void
D3D8AtomicMatFXDualPassRender(RxD3D8InstanceData *instancedData,
                             RwUInt32 flags)
{
    MatFXDualData   *dualData;

    RWFUNCTION(RWSTRING("D3D8AtomicMatFXDualPassRender"));

    dualData = MATFXD3D8DUALGETDATA(instancedData->material);

    if ( dualData && dualData->texture )
    {
        RwBool          needSecondPass = TRUE;

        if (flags & (rxGEOMETRY_TEXTURED|rpGEOMETRYTEXTURED2))
        {
            RwD3D8SetTexture(instancedData->material->texture, 0);
        }
        else
        {
            RwD3D8SetTexture(NULL, 0);
        }

        if (instancedData->vertexAlpha ||
            (0xFF != instancedData->material->color.alpha))
        {
            RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE);
        }
        else
        {
            RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE);
        }

        if (instancedData->vertexAlpha)
        {
            RwD3D8SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1);
        }
        else
        {
            RwD3D8SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_MATERIAL);
        }

        /* Check for some blend modes optimizations */
        if (VideoCardSupportsMultitexture)
        {
            if ( (dualData->srcBlendMode == rwBLENDDESTCOLOR && dualData->dstBlendMode == rwBLENDZERO) ||
                 (dualData->srcBlendMode == rwBLENDZERO && dualData->dstBlendMode == rwBLENDSRCCOLOR))
            {
                RwD3D8SetTexture(dualData->texture, 1);

                /* Some old cards with 3 stages need the diffuse in the last one */
                if (VideoCardMaxTextureBlendStages == 3)
                {
                    RwD3D8SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_SELECTARG1);
                    RwD3D8SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);

                    RwD3D8SetTextureStageState(1, D3DTSS_COLOROP,   D3DTOP_MODULATE);
                    RwD3D8SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE);
                    RwD3D8SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT);

                    RwD3D8SetTextureStageState(2, D3DTSS_COLOROP,   D3DTOP_MODULATE);
                    RwD3D8SetTextureStageState(2, D3DTSS_COLORARG1, D3DTA_DIFFUSE);
                    RwD3D8SetTextureStageState(2, D3DTSS_COLORARG2, D3DTA_CURRENT);

                    if(_rwD3D8TextureHasAlpha(dualData->texture))
                    {
                        if (_rwD3D8TextureHasAlpha(instancedData->material->texture))
                        {
                            RwD3D8SetTextureStageState(1, D3DTSS_ALPHAOP,   D3DTOP_SELECTARG2);
                            RwD3D8SetTextureStageState(1, D3DTSS_ALPHAARG2, D3DTA_CURRENT);

                            RwD3D8SetTextureStageState(2, D3DTSS_ALPHAOP,   D3DTOP_SELECTARG2);
                            RwD3D8SetTextureStageState(2, D3DTSS_ALPHAARG2, D3DTA_CURRENT);                   
                        }
                    }
                }
                else
                {
                    /* This could fail in very old cards */
                    if(_rwD3D8TextureHasAlpha(dualData->texture))
                    {
                        if (_rwD3D8TextureHasAlpha(instancedData->material->texture))
                        {
                            RwD3D8SetTextureStageState(1, D3DTSS_ALPHAOP,   D3DTOP_SELECTARG2);
                            RwD3D8SetTextureStageState(1, D3DTSS_ALPHAARG2, D3DTA_CURRENT);
                        }
                        else
                        {
                            RwD3D8SetTextureStageState(1, D3DTSS_ALPHAOP,   D3DTOP_DISABLE);
                        }
                    }
                }

                needSecondPass = FALSE;
            }
            else if ( dualData->srcBlendMode == rwBLENDSRCALPHA && dualData->dstBlendMode == rwBLENDINVSRCALPHA )
            {
                if (VideoCardMaxTextureBlendStages >= 3)
                {
                    RwD3D8SetTexture(dualData->texture, 1);

                    RwD3D8SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_SELECTARG1);
                    RwD3D8SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);

                    RwD3D8SetTextureStageState(1, D3DTSS_COLOROP,   D3DTOP_BLENDTEXTUREALPHA);
                    RwD3D8SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE);
                    RwD3D8SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT);

                    RwD3D8SetTextureStageState(2, D3DTSS_COLOROP,   D3DTOP_MODULATE);
                    RwD3D8SetTextureStageState(2, D3DTSS_COLORARG1, D3DTA_DIFFUSE);
                    RwD3D8SetTextureStageState(2, D3DTSS_COLORARG2, D3DTA_CURRENT);

                    if(_rwD3D8TextureHasAlpha(instancedData->material->texture))
                    {
                        RwD3D8SetTextureStageState(1, D3DTSS_ALPHAOP,   D3DTOP_BLENDTEXTUREALPHA);
                        RwD3D8SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
                        RwD3D8SetTextureStageState(1, D3DTSS_ALPHAARG2, D3DTA_CURRENT);

                        RwD3D8SetTextureStageState(2, D3DTSS_ALPHAOP,   D3DTOP_SELECTARG2);
                        RwD3D8SetTextureStageState(2, D3DTSS_ALPHAARG2, D3DTA_CURRENT);
                    }

                    needSecondPass = FALSE;
                }
            }
            else if ( dualData->srcBlendMode == rwBLENDONE && dualData->dstBlendMode == rwBLENDONE )
            {
                if (VideoCardSupportsMultiplyAdd)
                {
                    RwD3D8SetTexture(dualData->texture, 1);

                    RwD3D8SetTextureStageState(1, D3DTSS_COLOROP,   D3DTOP_MULTIPLYADD);
                    RwD3D8SetTextureStageState(1, D3DTSS_COLORARG0, D3DTA_CURRENT);
                    RwD3D8SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE);
                    RwD3D8SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_DIFFUSE);

                    if(_rwD3D8TextureHasAlpha(dualData->texture))
                    {
                        if (_rwD3D8TextureHasAlpha(instancedData->material->texture))
                        {
                            RwD3D8SetTextureStageState(1, D3DTSS_ALPHAOP,   D3DTOP_SELECTARG2);
                            RwD3D8SetTextureStageState(1, D3DTSS_ALPHAARG2, D3DTA_CURRENT);
                        }
                        else
                        {
                            RwD3D8SetTextureStageState(1, D3DTSS_ALPHAOP,   D3DTOP_DISABLE);
                        }
                    }

                    needSecondPass = FALSE;
                }
            }
            else if ( dualData->srcBlendMode == rwBLENDZERO && dualData->dstBlendMode == rwBLENDSRCALPHA )
            {
                RwD3D8SetTexture(dualData->texture, 1);

                /* Some old cards with 3 stages need the diffuse in the last one */
                if (VideoCardMaxTextureBlendStages == 3)
                {
                    RwD3D8SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_SELECTARG1);
                    RwD3D8SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);

                    RwD3D8SetTextureStageState(1, D3DTSS_COLOROP,   D3DTOP_MODULATE);
                    RwD3D8SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE | D3DTA_ALPHAREPLICATE);
                    RwD3D8SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT);

                    RwD3D8SetTextureStageState(2, D3DTSS_COLOROP,   D3DTOP_MODULATE);
                    RwD3D8SetTextureStageState(2, D3DTSS_COLORARG1, D3DTA_DIFFUSE);
                    RwD3D8SetTextureStageState(2, D3DTSS_COLORARG2, D3DTA_CURRENT);

                    if (_rwD3D8TextureHasAlpha(instancedData->material->texture))
                    {
                        RwD3D8SetTextureStageState(1, D3DTSS_ALPHAOP,   D3DTOP_SELECTARG2);
                        RwD3D8SetTextureStageState(1, D3DTSS_ALPHAARG2, D3DTA_CURRENT);

                        RwD3D8SetTextureStageState(2, D3DTSS_ALPHAOP,   D3DTOP_SELECTARG2);
                        RwD3D8SetTextureStageState(2, D3DTSS_ALPHAARG2, D3DTA_CURRENT);                   
                    }
                }
                else
                {
                    /* This could fail in very old cards*/
                    RwD3D8SetTextureStageState(1, D3DTSS_COLOROP,   D3DTOP_MODULATE);
                    RwD3D8SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE | D3DTA_ALPHAREPLICATE);
                    RwD3D8SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT);

                    if (_rwD3D8TextureHasAlpha(instancedData->material->texture))
                    {
                        RwD3D8SetTextureStageState(1, D3DTSS_ALPHAOP,   D3DTOP_SELECTARG2);
                        RwD3D8SetTextureStageState(1, D3DTSS_ALPHAARG2, D3DTA_CURRENT);
                    }
                }

                needSecondPass = FALSE;
            }

            if (!needSecondPass)
            {
                if ((flags & rpGEOMETRYTEXTURED2) == 0)
                {
                    RwD3D8SetTextureStageState(1, D3DTSS_TEXCOORDINDEX, 0);
                }
            }
        }

        /*
         * Set the default Pixel shader
         */
        RwD3D8SetPixelShader(0);

        /*
         * Vertex shader
         */
        RwD3D8SetVertexShader(instancedData->vertexShader);

        /*
         * Set the stream source
         */
        RwD3D8SetStreamSource(0, instancedData->vertexBuffer,
                                  instancedData->stride);
        /*
         * Set Indices
         */
        RwD3D8SetIndices(instancedData->indexBuffer, instancedData->baseIndex);

        /*
         * Draw the indexed primitive
         */
        RwD3D8DrawIndexedPrimitive((D3DPRIMITIVETYPE)instancedData->primType,
                                      0, instancedData->numVertices,
                                      0, instancedData->numIndices);

        /*
         * Draw second pass
         */
        if (needSecondPass)
        {
            RwBool  alphaBlend;
            RwBool  zWriteEnable;

            RwD3D8SetTexture(dualData->texture, 0);

            RwD3D8GetRenderState(D3DRS_ALPHABLENDENABLE, (void *)&alphaBlend);

            if(!alphaBlend)
            {
                RwD3D8SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
                RwD3D8SetRenderState(D3DRS_ALPHATESTENABLE, TRUE);
            }

            /* Remove alpha test for some combos */
            if ( (dualData->srcBlendMode == rwBLENDDESTCOLOR && dualData->dstBlendMode == rwBLENDZERO) ||
                 (dualData->srcBlendMode == rwBLENDZERO && dualData->dstBlendMode == rwBLENDSRCCOLOR))
            {
                RwD3D8SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
            }

            /*
             * Set appropiate blending mode
             */
            RwD3D8SetRenderState(D3DRS_SRCBLEND, dualData->srcBlendMode);
            RwD3D8SetRenderState(D3DRS_DESTBLEND, dualData->dstBlendMode);

            RwD3D8GetRenderState(D3DRS_ZWRITEENABLE, (void *)&zWriteEnable);
            RwD3D8SetRenderState(D3DRS_ZWRITEENABLE, FALSE);

            if (flags & rpGEOMETRYTEXTURED2)
            {
                RwD3D8SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, 1);
            }

            RwD3D8DrawIndexedPrimitive((D3DPRIMITIVETYPE)instancedData->primType,
                                          0, instancedData->numVertices,
                                          0, instancedData->numIndices);

            RwD3D8SetRenderState(D3DRS_ZWRITEENABLE, zWriteEnable);

            if (flags & rpGEOMETRYTEXTURED2)
            {
                RwD3D8SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, 0);
            }

            if(!alphaBlend)
            {
                RwD3D8SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
                RwD3D8SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);

                RwD3D8SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
                RwD3D8SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);
            }
            else
            {
                RwD3D8SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
                RwD3D8SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
            }
        }
        else
        {
            RwD3D8SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_MODULATE);
            RwD3D8SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
            RwD3D8SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);

            RwD3D8SetTexture(NULL, 1);

            RwD3D8SetTextureStageState(2, D3DTSS_COLOROP,   D3DTOP_DISABLE);
            RwD3D8SetTextureStageState(2, D3DTSS_ALPHAOP,   D3DTOP_DISABLE);

            if ((flags & rpGEOMETRYTEXTURED2) == 0)
            {
                RwD3D8SetTextureStageState(1, D3DTSS_TEXCOORDINDEX, 1);
            }       
        }
    }
    else
    {
        D3D8AtomicMatFXDefaultRender(instancedData, flags);
    }

    RWRETURNVOID();
}

/****************************************************************************
 D3D8AtomicMatFXEnvRender
 
 Purpose:
 
 On entry:
                
 On exit:
 */
static void
D3D8AtomicMatFXEnvRender(RxD3D8InstanceData *instancedData,
                         RwUInt32 flags,
                         MatFXPass pass)
{
    MatFXEnvMapData *envMapData;
    RwUInt32        shinny;

    RWFUNCTION(RWSTRING("D3D8AtomicMatFXEnvRender"));

    envMapData = MATFXD3D8ENVMAPGETDATA(instancedData->material, pass);

    shinny = (RwFastRealToUInt32(envMapData->coef * 255) & 0xFF);

    if (shinny && envMapData->texture)
    {
        RwBool  useEnvMapPixelShader = FALSE;
        RwBool  useMultitexture = FALSE;
        RwBool  hasBaseTexture = FALSE;

        /* Vertex alpha */
        if (instancedData->vertexAlpha ||
            (0xFF != instancedData->material->color.alpha))
        {
            RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE);
        }
        else
        {
            RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE);
        }

        if (instancedData->vertexAlpha)
        {
            RwD3D8SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1);
        }
        else
        {
            RwD3D8SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_MATERIAL);
        }

        if (pass == rpSECONDPASS)
        {
            /* Set the base texture */
            if (flags & (rxGEOMETRY_TEXTURED|rpGEOMETRYTEXTURED2) &&
                instancedData->material->texture)
            {
                RwD3D8SetTexture(instancedData->material->texture, 0);

                hasBaseTexture = TRUE;
            }
            else
            {
                RwD3D8SetTexture(NULL, 0);
            }

            /* Choose code path */
            if (_rwD3D8TextureHasAlpha(envMapData->texture))
            {
                if (EnvMapPixelShader)
                {
                    useEnvMapPixelShader = TRUE;
                }
            }
            else if (VideoCardSupportsMultiplyAdd)
            {
                useMultitexture = TRUE;
            }
            else if (!hasBaseTexture && shinny == 0xFF)
            {
                useMultitexture = TRUE;
            }

            /*
             * Set the default Pixel shader
             */
            if (!useEnvMapPixelShader)
            {
                RwD3D8SetPixelShader(0);
            }

            /*
             * Vertex shader
             */
            RwD3D8SetVertexShader(instancedData->vertexShader);

            /*
             * Set the stream source
             */
            RwD3D8SetStreamSource(0, instancedData->vertexBuffer,
                                      instancedData->stride);

            /*
             * Set the index buffer
             */
            RwD3D8SetIndices(instancedData->indexBuffer, instancedData->baseIndex);

            /*
             * Draw the indexed primitive if dual pass
             */
            if (!useEnvMapPixelShader && !useMultitexture)
            {
                RwD3D8DrawIndexedPrimitive((D3DPRIMITIVETYPE)instancedData->primType,
                                              0, instancedData->numVertices,
                                              0, instancedData->numIndices);
            }
        }

        /*
         * Add envmap scaled by coef
         */
        if (useEnvMapPixelShader)
        {
            const RwReal ShinyFloats[4]={envMapData->coef, envMapData->coef, envMapData->coef, envMapData->coef};

            RwD3D8SetPixelShaderConstant(0, ShinyFloats, 1);

            if (hasBaseTexture)
            {
                ApplyEnvMapTextureMatrix(D3DTS_TEXTURE1, envMapData->frame);

                /* Set the envmap texture */
                RwD3D8SetTexture(envMapData->texture, 1);

                /* Set pixel shader */
                RwD3D8SetPixelShader(EnvMapPixelShader);

                /* Generate spheremap texture coords from the position */
                RwD3D8SetTextureStageState(1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2);
                RwD3D8SetTextureStageState(1, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACENORMAL);

                RwD3D8DrawIndexedPrimitive((D3DPRIMITIVETYPE)instancedData->primType,
                                      0, instancedData->numVertices,
                                      0, instancedData->numIndices);

                RwD3D8SetTexture(NULL, 1);

                RwD3D8SetTextureStageState(1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE);
                RwD3D8SetTextureStageState(1, D3DTSS_TEXCOORDINDEX, 1);
            }
            else
            {
                ApplyEnvMapTextureMatrix(D3DTS_TEXTURE0, envMapData->frame);
                
                /* Set the envmap texture */
                RwD3D8SetTexture(envMapData->texture, 0);

                /* Set pixel shader */
                RwD3D8SetPixelShader(EnvMapNoBaseTexturePixelShader);

                /* Generate spheremap texture coords from the position */
                RwD3D8SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2);
                RwD3D8SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACENORMAL);

                RwD3D8DrawIndexedPrimitive((D3DPRIMITIVETYPE)instancedData->primType,
                                      0, instancedData->numVertices,
                                      0, instancedData->numIndices);

                RwD3D8SetTexture(NULL, 0);

                RwD3D8SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE);
                RwD3D8SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, 0);
            }
        }
        else if (useMultitexture)
        {
            if (hasBaseTexture || shinny != 0xFF)
            {
                ApplyEnvMapTextureMatrix(D3DTS_TEXTURE1, envMapData->frame);

                /* Set the envmap texture */
                RwD3D8SetTexture(envMapData->texture, 1);

                /* Set the shinny factor and the correct texture stages */
                shinny = ((shinny << 24) |
                          (shinny << 16) |
                          (shinny << 8) |
                           shinny);

                RwD3D8SetRenderState(D3DRS_TEXTUREFACTOR, shinny);

                RwD3D8SetTextureStageState(1, D3DTSS_COLOROP,   D3DTOP_MULTIPLYADD);
                RwD3D8SetTextureStageState(1, D3DTSS_COLORARG0, D3DTA_CURRENT);
                RwD3D8SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE);
                RwD3D8SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_TFACTOR);

                /* Generate spheremap texture coords from the position */
                RwD3D8SetTextureStageState(1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2);
                RwD3D8SetTextureStageState(1, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACENORMAL);

                RwD3D8DrawIndexedPrimitive((D3DPRIMITIVETYPE)instancedData->primType,
                                      0, instancedData->numVertices,
                                      0, instancedData->numIndices);

                RwD3D8SetTexture(NULL, 1);

                RwD3D8SetTextureStageState(1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE);
                RwD3D8SetTextureStageState(1, D3DTSS_TEXCOORDINDEX, 1);
            }
            else
            {
                ApplyEnvMapTextureMatrix(D3DTS_TEXTURE0, envMapData->frame);

                /* Set the envmap texture */
                RwD3D8SetTexture(envMapData->texture, 0);

                RwD3D8SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_ADD);

                /* Generate spheremap texture coords from the position */
                RwD3D8SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2);
                RwD3D8SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACENORMAL);

                RwD3D8DrawIndexedPrimitive((D3DPRIMITIVETYPE)instancedData->primType,
                                      0, instancedData->numVertices,
                                      0, instancedData->numIndices);

                RwD3D8SetTexture(NULL, 0);

                RwD3D8SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE);
                RwD3D8SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, 0);
            }
        }
        else
        {
            RwBool      alphaBlend;
            RwBool      lighting;
            RwBool      zWriteEnable;

            ApplyEnvMapTextureMatrix(D3DTS_TEXTURE0, envMapData->frame);

            /* Set the envmap texture */
            RwD3D8SetTexture(envMapData->texture, 0);

            RwD3D8GetRenderState(D3DRS_ALPHABLENDENABLE, (void *)&alphaBlend);

            if (!alphaBlend)
            {
                RwD3D8SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
                RwD3D8SetRenderState(D3DRS_ALPHATESTENABLE, TRUE);
            }

            /* Set needed blending modes for envmap */
            RwD3D8SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
            RwD3D8SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);

            /* Set the shinnyness */
            shinny = ((shinny << 24) |
                      (shinny << 16) |
                      (shinny << 8) |
                       shinny);

            if (shinny<0xFFFFFFFF)
            {
                RwD3D8SetRenderState(D3DRS_TEXTUREFACTOR, shinny);

                RwD3D8SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_MODULATE);
                RwD3D8SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
                RwD3D8SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TFACTOR);
            }
            else
            {
                RwD3D8SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_SELECTARG1);
                RwD3D8SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
            }

            /* Generate spheremap texture coords from the position */
            RwD3D8SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2);
            RwD3D8SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACENORMAL);

            RwD3D8GetRenderState(D3DRS_LIGHTING, (void *)&lighting);
            RwD3D8GetRenderState(D3DRS_ZWRITEENABLE, (void *)&zWriteEnable);

            RwD3D8SetRenderState(D3DRS_LIGHTING, FALSE);
            RwD3D8SetRenderState(D3DRS_ZWRITEENABLE, FALSE);

            RwD3D8DrawIndexedPrimitive((D3DPRIMITIVETYPE)instancedData->primType,
                                          0, instancedData->numVertices,
                                          0, instancedData->numIndices);

            RwD3D8SetRenderState(D3DRS_ZWRITEENABLE, zWriteEnable);
            RwD3D8SetRenderState(D3DRS_LIGHTING, lighting);

            if (shinny<0xFFFFFFFF)
            {
                RwD3D8SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
            }
            else
            {
                RwD3D8SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_MODULATE);
            }

            if (!alphaBlend)
            {
                RwD3D8SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
                RwD3D8SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);

                RwD3D8SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
                RwD3D8SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);
            }
            else
            {
                RwD3D8SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
                RwD3D8SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
            }

            RwD3D8SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE);
            RwD3D8SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU);
        }
    }
    else
    {
        if (pass == rpSECONDPASS)
        {
            D3D8AtomicMatFXDefaultRender(instancedData, flags);
        }
    }

    RWRETURNVOID();
}

/****************************************************************************
 D3D8AtomicMatFXBumpMapRender
 
 Purpose:
 
 On entry:
                
 On exit:
 */
static void
D3D8AtomicMatFXBumpMapRender(RxD3D8InstanceData *instancedData,
                             RwUInt32 flags,
                             RwBool useEnvMap)
{
    MatFXBumpMapData    *bumpmap;

    RWFUNCTION(RWSTRING("D3D8AtomicMatFXBumpMapRender"));

    /*
     * Draw Bump Map
     */
    bumpmap = MATFXD3D8BUMPMAPGETDATA(instancedData->material);

    if (bumpmap && bumpmap->texture)
    {
        RwUInt32      vbBumpMapOffset;
        LPDIRECT3DVERTEXBUFFER8 vertexBufferBumpMap;
        RxD3D8BumpMapVertex *bufferMem;

        /* Fill Vertex Buffer */
        if (RwD3D8DynamicVertexBufferLock(sizeof(RxD3D8BumpMapVertex),
                                                instancedData->numVertices,
                                                (void**)&vertexBufferBumpMap,
                                                (void**)&bufferMem,
                                                &vbBumpMapOffset))
        {
            RwBool  alphaBlend;
            RwBool  zWriteEnable;

            CalculatePerturbedUVs(instancedData, bufferMem);

            RwD3D8DynamicVertexBufferUnlock(vertexBufferBumpMap);

            /*
             * Set base textures
             */
            RwD3D8SetTexture(bumpmap->texture, 0);

            /*
             * Vertex shader
             */
            RwD3D8SetVertexShader(BUMPMAP_FVF);

            /*
             * Set the stream source
             */
            RwD3D8SetStreamSource(0, vertexBufferBumpMap, sizeof(RxD3D8BumpMapVertex));

            /*
             * Set Indices
             */
            RwD3D8SetIndices(instancedData->indexBuffer, vbBumpMapOffset);

            /*
             * Draw effect
             */
            if ( (useEnvMap && BumpEnvMapPixelShader) || BumpMapPixelShader )
            {
                RwD3D8SetTexture(bumpmap->texture, 1);

                RwD3D8GetRenderState(D3DRS_ALPHABLENDENABLE, (void *)&alphaBlend);

                if (alphaBlend)
                {
                    RwD3D8SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
                    RwD3D8SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
                }

                /*
                 * Pixel shader
                 */
                if (useEnvMap && BumpEnvMapPixelShader)
                {
                    MatFXEnvMapData *envMapData;
                    RwUInt32        shinny;

                    envMapData = MATFXD3D8ENVMAPGETDATA(instancedData->material, rpTHIRDPASS);

                    RWASSERT(NULL != envMapData->texture);

                    shinny = ((RwUInt32)(envMapData->coef * 255)) & 0xFF;

                    if (shinny && envMapData->texture)
                    {
                        RwReal ShinyFloats[4]={envMapData->coef, envMapData->coef, envMapData->coef, envMapData->coef};

                        ApplyEnvMapTextureMatrix(D3DTS_TEXTURE2, envMapData->frame);

                        /* Set the envmap texture */
                        RwD3D8SetTexture(envMapData->texture, 2);

                       /* Set pixel shader */
                        RwD3D8SetPixelShader(BumpEnvMapPixelShader);

                        RwD3D8SetPixelShaderConstant(0, ShinyFloats, 1);

                        /* Generate spheremap texture coords from the position */
                        RwD3D8SetTextureStageState(2, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2);
                        RwD3D8SetTextureStageState(2, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACENORMAL);

                        RwD3D8DrawIndexedPrimitive((D3DPRIMITIVETYPE)instancedData->primType,
                                              0, instancedData->numVertices,
                                              0, instancedData->numIndices);

                        RwD3D8SetTexture(NULL, 2);

                        RwD3D8SetTextureStageState(2, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE);
                        RwD3D8SetTextureStageState(2, D3DTSS_TEXCOORDINDEX, 2);
                    }
                    else
                    {
                        RwD3D8SetPixelShader(BumpMapPixelShader);

                        /*
                         * Draw the indexed primitive
                         */
                        RwD3D8DrawIndexedPrimitive((D3DPRIMITIVETYPE)instancedData->primType,
                                                      0, instancedData->numVertices,
                                                      0, instancedData->numIndices);
                    }
                }
                else
                {
                    RwD3D8SetPixelShader(BumpMapPixelShader);

                    /*
                     * Draw the indexed primitive
                     */
                    RwD3D8DrawIndexedPrimitive((D3DPRIMITIVETYPE)instancedData->primType,
                                                  0, instancedData->numVertices,
                                                  0, instancedData->numIndices);
                }

                /*
                 * Restore default values
                 */
                RwD3D8SetTexture(NULL, 1);

                if (alphaBlend)
                {
                    RwD3D8SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
                    RwD3D8SetRenderState(D3DRS_ALPHATESTENABLE, TRUE);

                    RwD3D8SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
                    RwD3D8SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
                }
            }
            else
            {
                /*
                 * Set the default Pixel shader
                 */
                RwD3D8SetPixelShader(0);

                /*
                 * First pass
                 */
                RwD3D8GetRenderState(D3DRS_ALPHABLENDENABLE, (void *)&alphaBlend);

                if (!alphaBlend)
                {
                    RwD3D8SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
                }

                RwD3D8SetRenderState(D3DRS_SRCBLEND, D3DBLEND_INVSRCALPHA);
                RwD3D8SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);

                RwD3D8SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);

                /*
                 * Draw the indexed primitive
                 */
                RwD3D8DrawIndexedPrimitive((D3DPRIMITIVETYPE)instancedData->primType,
                                              0, instancedData->numVertices,
                                              0, instancedData->numIndices);

                RwD3D8SetRenderState(D3DRS_ALPHATESTENABLE, TRUE);

                /*
                 * Second pass
                 */
                RwD3D8SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
                RwD3D8SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);

                RwD3D8GetRenderState(D3DRS_ZWRITEENABLE, (void *)&zWriteEnable);
                RwD3D8SetRenderState(D3DRS_ZWRITEENABLE, FALSE);

                RwD3D8SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, 1);

                /*
                 * Draw the indexed primitive
                 */
                RwD3D8DrawIndexedPrimitive((D3DPRIMITIVETYPE)instancedData->primType,
                                              0, instancedData->numVertices,
                                              0, instancedData->numIndices);

                RwD3D8SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, 0);

                RwD3D8SetRenderState(D3DRS_ZWRITEENABLE, zWriteEnable);

                /* Set standar blending mode */
                if(!alphaBlend)
                {
                    RwD3D8SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
                    RwD3D8SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);

                    RwD3D8SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
                    RwD3D8SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);
                }
                else
                {
                    RwD3D8SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
                    RwD3D8SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
                }
            }
        }
    }
    else
    {
        /*
         * Vertex shader
         */
        RwD3D8SetVertexShader(instancedData->vertexShader);

        /*
         * Set the stream source
         */
        RwD3D8SetStreamSource(0, instancedData->vertexBuffer,
                                  instancedData->stride);
        /*
         * Set Indices
         */
        RwD3D8SetIndices(instancedData->indexBuffer, instancedData->baseIndex);

        if (flags & (rxGEOMETRY_TEXTURED|rpGEOMETRYTEXTURED2))
        {
            RwD3D8SetTexture(instancedData->material->texture, 0);
        }
        else
        {
            RwD3D8SetTexture(NULL, 0);
        }

        if (instancedData->vertexAlpha ||
            (0xFF != instancedData->material->color.alpha))
        {
            RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE);
        }
        else
        {
            RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE);
        }

        if (instancedData->vertexAlpha)
        {
            RwD3D8SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1);
        }
        else
        {
            RwD3D8SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_MATERIAL);
        }

        /*
         * Draw the indexed primitive
         */
        RwD3D8DrawIndexedPrimitive((D3DPRIMITIVETYPE)instancedData->primType,
                                      0, instancedData->numVertices,
                                      0, instancedData->numIndices);
    }

    RWRETURNVOID();
}

/****************************************************************************
 D3D8AtomicMatFXRenderCallback
 
 Purpose:
 
 On entry:
                
 On exit:
 */
static void
D3D8AtomicMatFXRenderCallback(RwResEntry *repEntry,
                              void *object,
                              RwUInt8 type,
                              RwUInt32 flags)
{
    RxD3D8ResEntryHeader    *resEntryHeader;
    RxD3D8InstanceData      *instancedData;
    rpMatFXMaterialData     *matFXData;
    RpMatFXMaterialFlags     effectType;
    RwInt32                  numMeshes;
    RwBool                  lighting;
    RwBool                  forceBlack;

    RWFUNCTION(RWSTRING("D3D8AtomicMatFXRenderCallback"));

    if (flags & rxGEOMETRY_PRELIT)
    {
        /* Emmisive color from the vertex colors */
        RwD3D8SetRenderState(D3DRS_COLORVERTEX, TRUE);
        RwD3D8SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_COLOR1);
    }
    else
    {
        /* Emmisive color from material, set to black in the submit node */
        RwD3D8SetRenderState(D3DRS_COLORVERTEX, FALSE);
        RwD3D8SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL);
    }

    /* Enable clipping */
    if (type == rpATOMIC)
    {
        RpAtomic                *atomic;
        RwCamera                *cam;
        const RwSphere          *boundingSphere;

        atomic = (RpAtomic *)object;

        cam = RwCameraGetCurrentCamera();
        RWASSERT(cam);
    
        boundingSphere = RpAtomicGetWorldBoundingSphere(atomic);

        if (RwD3D8CameraIsSphereFullyInsideFrustum(cam, boundingSphere))
        {
            RwD3D8SetRenderState(D3DRS_CLIPPING, FALSE);
        }
        else
        {
            RwD3D8SetRenderState(D3DRS_CLIPPING, TRUE);
        }
    }
    else
    {
        RpWorldSector   *worldSector;
        RwCamera        *cam;

        worldSector = (RpWorldSector *)object;

        cam = RwCameraGetCurrentCamera();
        RWASSERT(cam);

        if (RwD3D8CameraIsBBoxFullyInsideFrustum(cam, RpWorldSectorGetBBox(worldSector)))
        {
            RwD3D8SetRenderState(D3DRS_CLIPPING, FALSE);
        }
        else
        {
            RwD3D8SetRenderState(D3DRS_CLIPPING, TRUE);
        }
    }

    /* Get lighting state */
    RwD3D8GetRenderState(D3DRS_LIGHTING, &lighting);

    if (lighting || (flags & rxGEOMETRY_PRELIT) != 0)
    {
        forceBlack = FALSE;
    }
    else
    {
        forceBlack = TRUE;

        RwD3D8SetTexture(NULL, 0);

        RwD3D8SetRenderState(D3DRS_TEXTUREFACTOR, 0xff000000);

        RwD3D8SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_SELECTARG2);
        RwD3D8SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TFACTOR);
    }

    /* Get the instanced data */
    resEntryHeader = (RxD3D8ResEntryHeader *)(repEntry + 1);
    instancedData = (RxD3D8InstanceData *)(resEntryHeader + 1);

    /* Get the number of meshes */
    numMeshes = resEntryHeader->numMeshes;
    while (numMeshes--)
    {
        if (!forceBlack)
        {
            RwD3D8SetSurfaceProperties(&instancedData->material->color,
                                    &instancedData->material->surfaceProps,
                                    (flags & rxGEOMETRY_MODULATE));

            /*
             * Render
             */
            matFXData = *MATFXMATERIALGETDATA(instancedData->material);
            if (NULL == matFXData)
            {
                /* This material hasn't been set up for MatFX so we
                 * treat it as is it were set to rpMATFXEFFECTNULL */
                effectType = rpMATFXEFFECTNULL;
            }
            else
            {
                effectType = matFXData->flags;
            }

            switch (effectType)
            {
            case rpMATFXEFFECTBUMPMAP:
                D3D8AtomicMatFXBumpMapRender(instancedData, flags, FALSE);
                break;

            case rpMATFXEFFECTENVMAP:
                D3D8AtomicMatFXEnvRender(instancedData, flags, rpSECONDPASS);
                break;

            case rpMATFXEFFECTBUMPENVMAP:
                if (BumpEnvMapPixelShader)
                {
                    D3D8AtomicMatFXBumpMapRender(instancedData, flags, TRUE);
                }
                else
                {
                    D3D8AtomicMatFXBumpMapRender(instancedData, flags, FALSE);
                    D3D8AtomicMatFXEnvRender(instancedData, flags, rpTHIRDPASS);
                }
                break;

            case rpMATFXEFFECTDUAL:
                D3D8AtomicMatFXDualPassRender(instancedData, flags);
                break;

            default:
                D3D8AtomicMatFXDefaultRender(instancedData, flags);
                break;
            }
        }
        else
        {
            D3D8AtomicMatFXRenderBlack(instancedData);
        }

        /* Move onto the next instancedData */
        instancedData++;
    }

    if (forceBlack)
    {
        RwD3D8SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_SELECTARG2);
        RwD3D8SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
    }

    RWRETURNVOID();
}

/*--- Create and destory pipelines ------------------------------------------*/

/****************************************************************************
 AtomicMatFxPipelineCreate
 
 Purpose:
 
 On entry:
                
 On exit:
 */
static RxPipeline *
AtomicMatFxPipelineCreate(void)
{
    RxPipeline  *pipe;

    RWFUNCTION(RWSTRING("AtomicMatFxPipelineCreate"));

    pipe = RxPipelineCreate();
    if (pipe)
    {
        RxLockedPipe    *lpipe;

        pipe->pluginId = rwID_MATERIALEFFECTSPLUGIN;
        lpipe = RxPipelineLock(pipe);
        if (NULL != lpipe)
        {
            RxNodeDefinition    *instanceNode;

            /*
             * Get the instance node definition
             */
            instanceNode = RxNodeDefinitionGetD3D8AtomicAllInOne();

            /*
             * Add the node to the pipeline
             */
            lpipe = RxLockedPipeAddFragment(lpipe, NULL, instanceNode, NULL);

            /*
             * Unlock the pipeline
             */
            lpipe = RxLockedPipeUnlock(lpipe);

            RWRETURN(pipe);
        }

        RxPipelineDestroy(pipe);
    }

    RWRETURN(NULL);
}

/****************************************************************************
 rpWorldSectorMatFxPipelineCreate
 
 Purpose:
 
 On entry:
                
 On exit:
 */
static RxPipeline *
WorldSectorMatFxPipelineCreate(void)
{
    RxPipeline  *pipe;

    RWFUNCTION(RWSTRING("WorldSectorMatFxPipelineCreate"));

    pipe = RxPipelineCreate();
    if (pipe)
    {
        RxLockedPipe    *lpipe;

        pipe->pluginId = rwID_MATERIALEFFECTSPLUGIN;

        lpipe = RxPipelineLock(pipe);
        if (NULL != lpipe)
        {
            RxNodeDefinition    *instanceNode;

            /*
             * Get the instance node definition
             */
            instanceNode = RxNodeDefinitionGetD3D8WorldSectorAllInOne();

            /*
             * Add the node to the pipeline
             */
            lpipe = RxLockedPipeAddFragment(lpipe, NULL, instanceNode, NULL);

            /*
             * Unlock the pipeline
             */
            lpipe = RxLockedPipeUnlock(lpipe);

            RWRETURN(pipe);
        }

        RxPipelineDestroy(pipe);
    }

    RWRETURN(NULL);
}

RwBool
_rpMatFXPipelinesCreate(void)
{
    RxNodeDefinition    *instanceNode;
    RxPipelineNode      *node;

    RWFUNCTION(RWSTRING("_rpMatFXPipelinesCreate"));

    MatFXAtomicPipe = AtomicMatFxPipelineCreate();
    RWASSERT(NULL != MatFXAtomicPipe);

    /*
     * Get the instance node definition
     */
    instanceNode = RxNodeDefinitionGetD3D8AtomicAllInOne();
    RWASSERT(NULL != instanceNode);

    /*
     * Set the pipeline specific data
     */
    node = RxPipelineFindNodeByName(MatFXAtomicPipe, instanceNode->name, NULL, NULL);
    RWASSERT(NULL != node);

    /*
     * Set the MatFX render callback
     */
    RxD3D8AllInOneSetRenderCallBack(node, D3D8AtomicMatFXRenderCallback);

    /* 
     * And likewise for world sectors:
     */
    MatFXWorldSectorPipe = WorldSectorMatFxPipelineCreate();
    RWASSERT(NULL != MatFXWorldSectorPipe);

    instanceNode = RxNodeDefinitionGetD3D8WorldSectorAllInOne();
    RWASSERT(NULL != instanceNode);

    node = RxPipelineFindNodeByName(MatFXWorldSectorPipe, instanceNode->name, NULL, NULL);
    RWASSERT(NULL != node);

    RxD3D8AllInOneSetRenderCallBack(node, D3D8AtomicMatFXRenderCallback);

    /*
     * Get some video card capabilities
     */
    VideoCardMaxTextureBlendStages = ((const D3DCAPS8 *)RwD3D8GetCaps())->MaxTextureBlendStages;

    if (VideoCardMaxTextureBlendStages >= 2)
    {
        VideoCardSupportsMultitexture = (((const D3DCAPS8 *)RwD3D8GetCaps())->MaxSimultaneousTextures >= 2);
        VideoCardSupportsMultiplyAdd = (((const D3DCAPS8 *)RwD3D8GetCaps())->TextureOpCaps & D3DTEXOPCAPS_MULTIPLYADD);
    }
    else
    {
        VideoCardSupportsMultitexture = FALSE;
        VideoCardSupportsMultiplyAdd = FALSE;
    }

    /*
     * Try to create the pixel shaders
     */
    if ( (((const D3DCAPS8 *)RwD3D8GetCaps())->PixelShaderVersion & 0xffff) >= 0x0100)
    {
        RwD3D8CreatePixelShader(dwBumpMapPShaderPixelShader, &BumpMapPixelShader);

        RwD3D8CreatePixelShader(dwEnvMapPShaderPixelShader, &EnvMapPixelShader);

        RwD3D8CreatePixelShader(dwEnvMapNoBaseTexturePShaderPixelShader, &EnvMapNoBaseTexturePixelShader);

        RwD3D8CreatePixelShader(dwBumpEnvMapPShaderPixelShader, &BumpEnvMapPixelShader);
    }
    else
    {
        BumpMapPixelShader = 0;

        EnvMapPixelShader = 0;
        EnvMapNoBaseTexturePixelShader = 0;

        BumpEnvMapPixelShader = 0;
    }

#if defined(RWDEBUG)
    if (BumpMapPixelShader &&
        EnvMapPixelShader &&
        EnvMapNoBaseTexturePixelShader &&
        BumpEnvMapPixelShader)
    {
        RwDebugSendMessage(rwDEBUGMESSAGE, "MatFX plugin", "Device supports pixel shaders and using them.");
    }
    else
    {
        RwDebugSendMessage(rwDEBUGMESSAGE, "MatFX plugin", "Device doesn't support pixel shaders, using fixed function pipeline.");
    }
#endif

    RWRETURN(TRUE);
}

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

    if (MatFXAtomicPipe)
    {
        RxPipelineDestroy(MatFXAtomicPipe);
        MatFXAtomicPipe = NULL;
    }

    if (MatFXWorldSectorPipe)
    {
        RxPipelineDestroy(MatFXWorldSectorPipe);
        MatFXWorldSectorPipe = NULL;
    }

    if (BumpMapPixelShader)
    {
        RwD3D8DeletePixelShader(BumpMapPixelShader);
        BumpMapPixelShader = 0;
    }

    if (EnvMapPixelShader)
    {
        RwD3D8DeletePixelShader(EnvMapPixelShader);
        EnvMapPixelShader = 0;
    }

    if (EnvMapNoBaseTexturePixelShader)
    {
        RwD3D8DeletePixelShader(EnvMapNoBaseTexturePixelShader);
        EnvMapNoBaseTexturePixelShader = 0;
    }

    if (BumpEnvMapPixelShader)
    {
        RwD3D8DeletePixelShader(BumpEnvMapPixelShader);
        BumpEnvMapPixelShader = 0;
    }

    RWRETURN(TRUE);
}

/*--- Attach pipelines ------------------------------------------------------*/
RpAtomic *
_rpMatFXPipelineAtomicSetup(RpAtomic *atomic)
{
    RWFUNCTION(RWSTRING("_rpMatFXPipelineAtomicSetup"));
    RWASSERT(atomic);

    RpAtomicSetPipeline(atomic, MatFXAtomicPipe);

    RWRETURN(atomic);
}

RpWorldSector *
_rpMatFXPipelineWorldSectorSetup(RpWorldSector *worldSector)
{
    RWFUNCTION(RWSTRING("_rpMatFXPipelineWorldSectorSetup"));
    RWASSERT(worldSector);

    RpWorldSectorSetPipeline(worldSector, MatFXWorldSectorPipe);

    RWRETURN(worldSector);
}

/*--- 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);
}

/*--- Upload texture --------------------------------------------------------*/

/*--- Device data fucntions -------------------------------------------------*/
RwBool
_rpMatFXSetupDualRenderState(MatFXDualData *dualData __RWUNUSEDRELEASE__,
                             RwRenderState nState __RWUNUSED__ )
{
    RWFUNCTION(RWSTRING("_rpMatFXSetupDualRenderState"));
    RWASSERT(dualData);
    RWRETURN(TRUE);
}

RwTexture *
_rpMatFXSetupBumpMapTexture(const RwTexture *baseTexture,
                            const RwTexture *effectTexture)
{
    RwTexture *texture;
    RWFUNCTION(RWSTRING("_rpMatFXSetupBumpMapTexture"));

    texture = _rpMatFXTextureMaskCreate(baseTexture, effectTexture);

    RWRETURN(texture);
}
