//////////////////////////////////////////////////////////////////////////////
//
// Asura_Platform_RenderSkin.cpp
//
// Copyright (c) 2004-2005 Rebellion. All rights reserved.
//
// Description:
//
//     PS2 EE-side rendering code for smooth skins. Essentially a customised
//     (and much simplified) version of the GSM renderer (see
//     Asura_PS2_GSMRenderer.cpp.)
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header.
#include "Rendering/Asura_Platform_RenderSkin.h"

// Stdlib platform-specific.
#include <eestruct.h>
#include <libgraph.h>

// Engine independent.
#include "Animation/Asura_Animation.h"
#include "Animation/Asura_Hierarchy.h"
#include "Camera/Asura_Camera.h"
#include "Core/Asura_Material_System.h"
#include "Core/Asura_TextureManagement.h"
#include "Light/Asura_GI_Solution.h"

// Engine platform-specific.
#include "Animation/Asura_PS2_Hierarchy.h"
#include "Animation/Asura_PS2_SkinTypes.h"
#include "Core/Asura_PS2_DMA.h"
#include "Debugging/Asura_PS2_DebugInfo.h"
#include "Profiling/Asura_PS2_Profiling.h"
#include "Rendering/Asura_PlatformRendering.h"
#include "Rendering/Asura_PlatformRenderStates.h"
#include "Rendering/Asura_PlatformTextureManagement.h"
#include "Rendering/Asura_PS2_DeferredRendering.h"
#include "Rendering/Asura_PS2_GS.h"
#include "Rendering/Asura_PS2_RenderingOptions.h"
#include "Rendering/Asura_PS2_Shadows.h"
#include "Rendering/Asura_PS2_ShadowBuffer.h"
#include "Rendering/Asura_PS2_SmoothSkinDefines.h"
#include "Rendering/Asura_PS2_VU1.h"

// T Y P E S /////////////////////////////////////////////////////////////////

// Just to tidy up the list of memory layout defines below.
typedef const Asura_Animation*              SS_AnimPtr;
typedef const Asura_PS2_SmoothSkin_SkinHdr* SS_SkinHdrPtr;

// C O N S T A N T S /////////////////////////////////////////////////////////

// This value is tools-side and should never be used by us and should get
// masked out when we examine material flags.
static const u_int SS_UNINITIALISED_EE_MATERIAL_FLAGS = AsuraMaterialFlag_SubdividePolygon;

// For the VU, we only have 10 bits so just use a value outside that range.
static const u_int SS_UNINITIALISED_VU_MATERIAL_FLAGS = 1 << 10;

// G L O B A L S /////////////////////////////////////////////////////////////

DEBUGVAR bool  dbg_bDrawSmoothSkinBBs              = false;
DEBUGVAR bool  dbg_bDrawSmoothSkinBoneBBs          = false;
DEBUGVAR int   dbg_iSpecificSmoothSkinBoneBBToDraw = -1;
DEBUGVAR float dbg_fDistSqdToDegradeSFX            = 12.5f * 12.5f;
//#if ASURA_PS2_SS__REDUCE_WEIGHTINGS_WITH_DISTANCE
//DEBUGVAR float dbg_fDistSqdToClampAt1Weighting     = 20.0f * 20.0f;
//DEBUGVAR float dbg_fDistSqdToClampAt2Weightings    = 12.5f * 12.5f;
//#endif
DEBUGVAR bool  dbg_bSSDisableSpecularPass          = false;
DEBUGVAR bool  dbg_bSSJustShowSpecularPass         = false;
DEBUGVAR bool  dbg_bSSDisableTextureAnimation      = false;
DEBUGVAR bool  dbg_bSSDisableAllMaterialEffects    = false;

// General.
#define g_pxAnim                 (*reinterpret_cast<SS_AnimPtr*     >(0x70000000))
#define g_pxSkinHdr              (*reinterpret_cast<SS_SkinHdrPtr*  >(0x70000004))
// Camera.
#define g_fDistFromNearestCamSqd (*reinterpret_cast<float*          >(0x70000008))
#define g_uEECamVisFlags         (*reinterpret_cast<u_int*          >(0x7000000C))
#define g_uNumCamsThatCanSee     (*reinterpret_cast<u_int*          >(0x70000010))
#define g_uCurVUTexMatBfr        (*reinterpret_cast<u_int*          >(0x70000014))
// Matrices.
#define g_bBonesInWorldspace     (*reinterpret_cast<bool*           >(0x70000018))
#define g_pxO2W                  ( reinterpret_cast<Asura_PS2_QW*   >(0x70000020))
#define g_pxMatTempBfr           ( reinterpret_cast<Asura_PS2_QW*   >(0x70000060)) // Workspace for two 4x4 matrices.
#define g_pxO2VMats              ( reinterpret_cast<Asura_PS2_QW*   >(0x700000E0)) // Space for a 4x4 matrix for each camera.
// Renderstate
#define g_bAnyReflective         (*reinterpret_cast<bool*           >(0x700001E0))
#define g_bAnySpecular           (*reinterpret_cast<bool*           >(0x700001E4))
#define g_uCurVURSBfr            (*reinterpret_cast<u_int*          >(0x700001E8))
#define g_uNumPassesPerTri       (*reinterpret_cast<u_int*          >(0x700001EC))
#define g_uCurEEMaterialFlags    (*reinterpret_cast<u_int*          >(0x700001F0))
#define g_uCurVUMaterialFlags    (*reinterpret_cast<u_int*          >(0x700001F4))
#define g_iCurBasePassTexture    (*reinterpret_cast<int*            >(0x700001F8))
#define g_bReflectiveTexUploaded (*reinterpret_cast<bool*           >(0x700001FC))
#define g_pxRSBfr                ( reinterpret_cast<Asura_PS2_QW*   >(0x70000200)) // Up to 10 QWs of RS GS cmds.
#define g_pxTexAnimDataBfr       ( reinterpret_cast<Asura_PS2_QW*   >(0x700002A0))
// Shadows.
#define g_pxShadowMatBfr         ( reinterpret_cast<Asura_PS2_QW*   >(0x700002B0)) // 4x4 matrix.
#define g_fShadowLightLevel      (*reinterpret_cast<float*          >(0x700002F0))
#define g_bShadow                (*reinterpret_cast<bool*           >(0x700002F4))
// Bounding sphere.
#define g_xBSCentre              (*reinterpret_cast<Asura_Vector_3* >(0x70000300))
#define g_fBSRadius              (*reinterpret_cast<float*          >(0x7000030C))
// Total.
#define TOTAL_SPR_QWS_REQD                                           (0x0000031 )

// Compile-time checks against stuff in the header.
#if TOTAL_SPR_QWS_REQD > ASURA_PS2_SMOOTHSKIN_SPR_QWS_RESERVED
    #error Out of sync with header.
#endif

// Subroutines on the VU.
extern u_int VU1_SS_INIT[]                                  __attribute__(( section(".vudata") ));
extern u_int VU1_SS_BEGIN_NEW_SKIN[]                        __attribute__(( section(".vudata") ));
//#if ASURA_PS2_SS__REDUCE_WEIGHTINGS_WITH_DISTANCE
//extern u_int VU1_SS_SKIN_SET_MAX_WEIGHTINGS_PER_VTX_CLAMP[] __attribute__(( section(".vudata") ));
//#endif
extern u_int VU1_SS_LOAD_CAM_FLAGS[]                        __attribute__(( section(".vudata") ));
extern u_int VU1_SS_SWITCH_TEX_MAT_BFR[]                    __attribute__(( section(".vudata") ));
extern u_int VU1_SS_BEGIN_MATERIAL[]                        __attribute__(( section(".vudata") ));
extern u_int VU1_SS_READY_FOR_NEXT_STRIP[]                  __attribute__(( section(".vudata") ));
extern u_int VU1_SS_SKIN_VERTS_1_BONE[]                     __attribute__(( section(".vudata") ));
extern u_int VU1_SS_SKIN_VERTS_2_BONES[]                    __attribute__(( section(".vudata") ));
extern u_int VU1_SS_SKIN_VERTS_3_BONES[]                    __attribute__(( section(".vudata") ));
extern u_int VU1_SS_SKIN_VERTS_4_BONES[]                    __attribute__(( section(".vudata") ));

// P R O T O T Y P E S   A N D   I N L I N E   F U N C T I O N S /////////////

static bool SS_CheckForShadow( void );

static inline u_int SS_NextRSBfrOnVU( void )
{
    if( g_uCurVURSBfr )
    {
        g_uCurVURSBfr = 0;
        return ASURA_PS2_SS__ADDR__RENDERSTATE_BFR_0;
    }
    else
    {
        g_uCurVURSBfr = 1;
        return ASURA_PS2_SS__ADDR__RENDERSTATE_BFR_1;
    }
}

// C L A S S   I M P L E M E N T A T I O N S /////////////////////////////////

//
// Asura_Hierarchy_System.
//

void Asura_Hierarchy_System::Platform_RenderSkin( const Asura_Animation* pxAnim,
                                                  float fAlpha, int iTexIDOverride,
                                                  u_int uCamVisFlags, u_int uHierRenderFlags,
                                                  const Asura_Vector_3& xBSCentre, float fBSRadius )
{
// (MP - 27.06.2005) Not sure why we should need to do this again here. It's
// done in independent code when we're determining vis flags.
//  if( s_bJustRenderForCurCam )
//  {
//      uCamVisFlags &= (1u << Asura_Camera::GetCameraIndex());
//  }
// (MP - 27.06.2005) But I'll leave an assert for now.
    Asura_Assert( !s_bJustRenderForCurCam || !(uCamVisFlags & ~(1 << Asura_Camera::GetCameraIndex())), "" );

// (MP - 27.06.2005) Similarly...
//  if( !uCamVisFlags ) { return; }
    Asura_Assert( uCamVisFlags, "" );

    if( !Asura_PS2_SmoothSkin_Renderer::Begin(pxAnim, uCamVisFlags, !(uHierRenderFlags & ASURA_HIERARCHY_RENDER_NO_SHADOW), xBSCentre, fBSRadius) )
    {
        return;
    }

    u_int uNumMaterials;
    const int* piMaterialIDs;
    const Asura_PS2_SmoothSkin_MaterialHdr* pxMaterialHdr;
    u_int uChainAddr;

    Asura_PS2_SmoothSkin_Renderer::GetHdrData( &uNumMaterials, &piMaterialIDs, &pxMaterialHdr, &uChainAddr );

    // If texture hasn't been overridden by caller, just use the one from the
    // first material, since we assume they all use the same anyway.
	int iTexture = (ASURA_TEXTURE_NONE == iTexIDOverride) ? Asura_Material_System::GetTextureID(piMaterialIDs[0]) : iTexIDOverride;
	if (iTexture != ASURA_TEXTURE_NONE) 
	{
		//If the texture referred to in piMaterialIDs was lost this would cause a crash
		Asura_PS2_SmoothSkin_Renderer::SetTexture( iTexture );		
	}

    Asura_PS2_SmoothSkin_Renderer::UploadBoneData();

    Asura_PS2_SmoothSkin_Renderer::UploadCamData();

    Asura_PS2_SmoothSkin_Renderer::UploadLightData( Asura_GI_Solution::s_xSHForCurrentRender, fAlpha );

    Asura_PS2_SmoothSkin_Renderer::UploadShadowData(); 
    
    for( ; uNumMaterials; --uNumMaterials )
    {
        // Switch to this next material.
        {
            int   iNextMaterialID = *piMaterialIDs++;
            u_int uMaterialFlags  = Asura_Material_System::GetMaterialFlags( iNextMaterialID );

			if(pxAnim->GetAlpha() != 1.0f)
			{
				uMaterialFlags |= AsuraMaterialFlag_Transparent;
			}

            Asura_PS2_SmoothSkin_Renderer::ChangeMaterialAndSendRS( iNextMaterialID, uMaterialFlags );
        }

        Asura_PS2_SmoothSkin_Renderer::RenderForMaterial( &pxMaterialHdr, &uChainAddr );
    }

    Asura_PS2_SmoothSkin_Renderer::End();
}

void Asura_Hierarchy_System::Platform_RenderSkinShadow( const Asura_Animation& xAnim, const Asura_Shadow& xShadow, const u_int uCamVisFlags, const u_int uRenderFlags )
{
}

//
// Asura_PS2_SmoothSkin_Renderer.
//

void Asura_PS2_SmoothSkin_Renderer::ChangeMaterialAndSendRS( int iMaterialID, u_int uMaterialFlags, RSCallback pfnModifyCB )
{
    // TODO: There's going to be a bit of a stall here since we'll MSCAL way
    // before the last strip of the previous material has finished rendering
    // and then afterwards have to wait through an entire new UNPACK before
    // we can start rendering again. I think we want to split off the first
    // strip of each material chain, much like in the environment stuff.

    // TODO: Much of this will be unnecessary for most material changes. For
    // example, going from self-illuminating to standard opaque is just a
    // change of material flags.

    u_int uOldEEMaterialFlags = g_uCurEEMaterialFlags;
    u_int uNumPassesPerTri    = 1;

    // Mask off material flags that we don't care about.
    uMaterialFlags &= ( AsuraMaterialFlag_SelfIlluminate
                        | AsuraMaterialFlag_PS2AdditiveAlpha
                        | AsuraMaterialFlag_Transparent
                        | AsuraMaterialFlag_Reflective
                        | AsuraMaterialFlag_Specular_Soft
                        | AsuraMaterialFlag_Specular_Sharp
                        | AsuraMaterialFlag_TextureAnim );

    // Debugging stuff.
    if( dbg_bSSDisableAllMaterialEffects )
    {
        uMaterialFlags = 0;
    }
    else
    {
        if( dbg_bSSDisableSpecularPass )
        {
            uMaterialFlags &= ~(AsuraMaterialFlag_Specular_Soft | AsuraMaterialFlag_Specular_Sharp);
        }
    
        if( dbg_bSSDisableTextureAnimation )
        {
            uMaterialFlags &= ~AsuraMaterialFlag_TextureAnim;
        }
    }

//  // Some material effects are pretty expensive so don't bother at range.
//  // TODO: May well need to allow some project / model-specific / effect-
//  // specific control over this. Probably will need different thresholds
//  // for different effects, eg. specular is turned off later than
//  // reflective pass.
//  if( g_fDistFromNearestCamSqd > dbg_fDistSqdToDegradeSFX )
//  {
//      uMaterialFlags &= ~(AsuraMaterialFlag_Reflective | AsuraMaterialFlag_Specular_Soft | AsuraMaterialFlag_Specular_Sharp);
//  }

    // Check something's actually changed.
    if( uMaterialFlags == uOldEEMaterialFlags )
    {
        // For material effects that depend upon additional parameters, we
        // also need to update renderstate if they've stayed on since the
        // last material, as the additional parameters will probably have
        // changed instead. Similarly, if we have a RS-modifying callback,
        // we need to ensure that gets called below.
        if( !(uMaterialFlags & AsuraMaterialFlag_TextureAnim) && !pfnModifyCB )
        {
            return;
        }
    }

    u_long uTEST            = SCE_GS_SET_TEST( false, 0, 0, 0, false, 0, true, SCE_GS_ZGEQUAL );
    u_int  uVUMaterialFlags = 0;
    u_int  uNumQWsOfRS      = 5;
    
    if( uMaterialFlags & AsuraMaterialFlag_SelfIlluminate )
    {
        // Pass 0: Cs
        (g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_0_ALPHA)->u64s[0] =
            SCE_GS_SET_ALPHA(SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_FIX, SCE_GS_ALPHA_CS, 0);
        // Pass 1: Cs * As + Cd
        (g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_1_ALPHA)->u64s[0] =
            SCE_GS_SET_ALPHA( SCE_GS_ALPHA_CS, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_AS, SCE_GS_ALPHA_CD, 0 );

        uVUMaterialFlags |= ASURA_PS2_SS__FLAG__SELFILLUMINATED;
        uNumQWsOfRS = 6;
        ++uNumPassesPerTri;
    }
    else
    {
        if( uMaterialFlags & AsuraMaterialFlag_PS2AdditiveAlpha )
        {
            // Pass 0: Cs * As + Cd
            (g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_0_ALPHA)->u64s[0] =
                SCE_GS_SET_ALPHA( SCE_GS_ALPHA_CS, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_AS, SCE_GS_ALPHA_CD, 0 );
            // Disable z writes.
            uTEST = SCE_GS_SET_TEST( true, SCE_GS_ALPHA_NEVER, 0, SCE_GS_AFAIL_RGB_ONLY, false, 0, true, SCE_GS_ZGEQUAL );
        }
        else if( uMaterialFlags & AsuraMaterialFlag_Transparent )
        {
            // Pass 0: Cs * As + Cd * (1 - As)
            (g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_0_ALPHA)->u64s[0] =
                SCE_GS_SET_ALPHA( SCE_GS_ALPHA_CS, SCE_GS_ALPHA_CD, SCE_GS_ALPHA_AS, SCE_GS_ALPHA_CD, 0 );
            // For stuff that's more or less opaque, write to both the frame
            // and z buffers. For everything else, only update the frame
            // buffer.
			uTEST = SCE_GS_SET_TEST( true, SCE_GS_ALPHA_GEQUAL, 124, SCE_GS_AFAIL_FB_ONLY, false, 0, true, SCE_GS_ZGEQUAL );
        }
        else
        {
            // Just regular opaque then.
            // Pass 0: Cs
            (g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_0_ALPHA)->u64s[0] =
                SCE_GS_SET_ALPHA( SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_FIX, SCE_GS_ALPHA_CS, 0 );
        }
    }

    (g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_0_TEST)->u64s[0] = uTEST;

    bool bSendTexMat = false;
    if( uMaterialFlags & AsuraMaterialFlag_Reflective )
    {
        if( Asura_PS2_RenderingOptions::GetOption(Asura_PS2_RenderingOptions::OPTION_ENABLE_REFLECTION) )
        {
            // If reflective flag wasn't previously on, setup tex matrix now.
            if( !(uOldEEMaterialFlags & ASURA_PS2_SS__FLAG__REFLECTIVE) )
            {
                bSendTexMat = true;
            }

            // Pass 2: Cd += Cs * Ad
            (g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_2_ALPHA)->u64s[0] =
                SCE_GS_SET_ALPHA( SCE_GS_ALPHA_CS, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_AD, SCE_GS_ALPHA_CD, 0 );

            uVUMaterialFlags |= ASURA_PS2_SS__FLAG__REFLECTIVE;
            uNumQWsOfRS = 10;
            ++uNumPassesPerTri;
        }
    }
    else if( uMaterialFlags & (AsuraMaterialFlag_Specular_Soft | AsuraMaterialFlag_Specular_Sharp) )
    {
        // Pass 2: Cd += Cs * Ad
        (g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_2_ALPHA)->u64s[0] =
            SCE_GS_SET_ALPHA( SCE_GS_ALPHA_CS, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_AD, SCE_GS_ALPHA_CD, 0 );

        if( dbg_bSSJustShowSpecularPass )
        {
            (g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_2_ALPHA)->u64s[0] =
                SCE_GS_SET_ALPHA( SCE_GS_ALPHA_CS, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_FIX, SCE_GS_ALPHA_ZERO, 128 );
        }

        uVUMaterialFlags |= ( uMaterialFlags & AsuraMaterialFlag_Specular_Soft ? ASURA_PS2_SS__FLAG__SPECULAR_SOFT : ASURA_PS2_SS__FLAG__SPECULAR_SHARP );
        uNumQWsOfRS = 7;
        ++uNumPassesPerTri;
    }

    bool bSendTexAnimData = false;
    if( uMaterialFlags & AsuraMaterialFlag_TextureAnim )
    {
        bSendTexAnimData = true;
        uVUMaterialFlags |= ASURA_PS2_SS__FLAG__TEX_ANIM;
    }

    // Modify RS buffer before we send it?
    if( pfnModifyCB )
    {
        pfnModifyCB( g_pxRSBfr );

        // We have to assume that's trashed all of our cached state.
        uMaterialFlags |= SS_UNINITIALISED_EE_MATERIAL_FLAGS;
        g_iCurBasePassTexture = ASURA_TEXTURE_NONE;
    }

    // Send to VU.
    {
        u_int uNumCams          = Asura_Camera::GetNumberOfCameras();
        u_int uNumTexMatDataQWs = uNumCams * 4;
        u_int uQWC              = uNumQWsOfRS + 1 + (bSendTexMat ? uNumTexMatDataQWs : 0) + (bSendTexAnimData ? 1 : 0);
        u_int uTotalQWs         = 1 + uQWC;

        Asura_PS2_QW* const pxBase = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
        Asura_PS2_QW* pxOut = pxBase;

        (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut))->Set( uQWC, Asura_PS2_DmaTag::PCE_NOCHANGE, Asura_PS2_DmaTag::ID_CNT, false, 0, false );

        // Renderstate.
        {
            const u_int uRSDestAddr = SS_NextRSBfrOnVU();

            pxOut->u32s[2] = SCE_VIF1_SET_STCYCL( 1, 1, false );
            pxOut->u32s[3] = SCE_VIF1_SET_UNPACK( uRSDestAddr, uNumQWsOfRS, Asura_PS2_VU1::VIFPK_V4_32, false );
            ++pxOut;

            const u_long128* puSrc = &g_pxRSBfr->u128;
            for( u_int u = uNumQWsOfRS; u; --u ) { (pxOut++)->u128 = *puSrc++; }
        }

        // And the texture coordinate matrix, if required.
        if( bSendTexMat )
        {
            u_int uTexMatDestAddr;
            if( g_uCurVUTexMatBfr )
            {
                g_uCurVUTexMatBfr = 0;
                uTexMatDestAddr   = ASURA_PS2_SS__ADDR__TEX_MAT_BFR_0;
            }
            else
            {
                g_uCurVUTexMatBfr = 1;
                uTexMatDestAddr   = ASURA_PS2_SS__ADDR__TEX_MAT_BFR_1;
            }

            pxOut->u32s[0] = ASURA_PS2_MAKE_MSCAL_FROM_VU_PTR( VU1_SS_SWITCH_TEX_MAT_BFR );
            pxOut->u32s[1] = SCE_VIF1_SET_ITOP( uVUMaterialFlags, false );
            pxOut->u32s[2] = ASURA_PS2_MAKE_MSCAL_FROM_VU_PTR( VU1_SS_BEGIN_MATERIAL );

            sceVu0UnitMatrix( &g_pxMatTempBfr->Vu0FVec );
            g_pxMatTempBfr[0].f32s[0] = 0.5f;
            g_pxMatTempBfr[1].f32s[1] = 0.5f;
            g_pxMatTempBfr[3].f32s[0] = 0.5f;
            g_pxMatTempBfr[3].f32s[1] = 0.5f;

            pxOut->u32s[3] = SCE_VIF1_SET_UNPACK( uTexMatDestAddr, uNumTexMatDataQWs, Asura_PS2_VU1::VIFPK_V4_32, false );
            ++pxOut;
            for( u_int uCam = 0; uCam < uNumCams; ++uCam )
            {
                // Derive V->O rotation from O->V matrix by transposing and
                // removing translation component.
                Asura_PS2_VU1::CalcPS2MatInverseRotOnly( &g_pxMatTempBfr[4], g_pxO2VMats + (uCam << 2) );
            
                // Concatenate with texture coordinate matrix.
                sceVu0MulMatrix( &pxOut->f32s, &g_pxMatTempBfr[0].Vu0FVec, &g_pxMatTempBfr[4].Vu0FVec );
                pxOut += 4;
            }
        }
        else
        {
            pxOut->u32s[0] = SCE_VIF1_SET_ITOP( uVUMaterialFlags, false );
            pxOut->u32s[1] = ASURA_PS2_MAKE_MSCAL_FROM_VU_PTR( VU1_SS_BEGIN_MATERIAL );
            pxOut->u64s[1] = 0uL;
            ++pxOut;
        }

        if( bSendTexAnimData )
        {
            // Note that even though this data is single-buffered, we're ok
            // to go ahead and send it at this point since we've just MSCAL'd
            // into VU1_SS_BEGIN_MATERIAL which will have stalled if the
            // previous strip was still rendering.
            Asura_TextureAnimationOutput xTexAnimState;
            Asura_Material_System::GetTextureAnimationState( iMaterialID, g_pxAnim->GetTextureAnimController(), &xTexAnimState );

            pxOut->u32s[0] = SCE_VIF1_SET_UNPACK( ASURA_PS2_SS__ADDR__TEX_ANIM_DATA, 1, Asura_PS2_VU1::VIFPK_V2_32, false );
            pxOut->f32s[1] = xTexAnimState.m_fUOffset;
            pxOut->f32s[2] = xTexAnimState.m_fVOffset;
            pxOut->u32s[3] = SCE_VIF1_SET_NOP( false );
            ++pxOut;
        }

        Asura_Assert( static_cast<u_int>(pxOut - pxBase) == uTotalQWs, "" );
    }

    g_uCurEEMaterialFlags = uMaterialFlags;
    g_uCurVUMaterialFlags = uVUMaterialFlags;
    g_uNumPassesPerTri    = uNumPassesPerTri;
}

bool Asura_PS2_SmoothSkin_Renderer::Begin( const Asura_Animation* pxAnim,
                                           u_int uCamVisFlags,
                                           bool bRenderShadow,
                                           const Asura_Vector_3& xBSCentre,
                                           float fBSRadius )
{
    if( !Asura_PS2_RenderingOptions::GetOption(Asura_PS2_RenderingOptions::OPTION_DRAW_SMOOTH_SKINS) ) { return false; }

    const Asura_PS2_SmoothSkin_SkinHdr* pxSkinHdr;

    // Fetch skin hdr.
    {
        const Asura_PS2_SkinEntry* pxSkinEntry = Asura_PS2_SkinSystem::GetSkinEntry( Asura_Hierarchy_System::s_pxSkin->m_uNameHashID );
        if( !pxSkinEntry ) { return false; }

        pxSkinHdr = Asura_PS2_SkinSystem::GetSmoothSkinHdr( pxSkinEntry,
                                                            Asura_Hierarchy_System::GetWorldspaceBonePos(0),
                                                            uCamVisFlags,
                                                            NULL ); //&g_fDistFromNearestCamSqd );
        if( !pxSkinHdr ) { return false; }
    }

    // Verify that the value specified in the skin data is valid wrt current
    // code.
    Asura_Assert( pxSkinHdr->usMaxVertsPerStrip <= ASURA_PS2_SS__MAX_VERTS_PER_STRIP(Asura_Hierarchy_System::s_pxSkin->NumberOfBones), "" );

    (void) Asura_PS2_SPR_Alloc( 0, TOTAL_SPR_QWS_REQD );

	ASURA_PS2_PROFILE__ADD_VIF_START( ASURA_PS2_PROFILE__SKINNED_MARKER );

    g_pxAnim             = pxAnim;
    g_pxSkinHdr          = pxSkinHdr;
    g_uEECamVisFlags     = uCamVisFlags;
    g_xBSCentre          = xBSCentre;
    g_fBSRadius          = fBSRadius;
    g_bBonesInWorldspace = Asura_Hierarchy_System::s_bBonesInWorldspace;

    const u_int uNumMaterials = pxSkinHdr->usNumMaterials;
    const int*  piMaterialIDs = reinterpret_cast<const int*>( pxSkinHdr + 1);

    // Is this skin visible to more than one camera?
    {
        Asura_Assert( uCamVisFlags, "" );

        u_int uNumCamsThatCanSee = 0;

        const u_int uNumCams = Asura_Camera::GetNumberOfCameras();
        for( u_int uCam = 0; uCam < uNumCams; ++uCam )
        {
            if( uCamVisFlags & (1 << uCam) )
            {
                ++uNumCamsThatCanSee;
            }
        }

        g_uNumCamsThatCanSee = uNumCamsThatCanSee;
    }

    // Setup O->W matrix.
    if( g_bBonesInWorldspace )
    {
        sceVu0UnitMatrix( &g_pxO2W->Vu0FVec );
    }
    else
    {
        Asura_PS2_VU1::SetupPS2MatFromAsuraData( g_pxO2W->f32s, *Asura_Hierarchy_System::s_pxOri, *Asura_Hierarchy_System::s_pxPos );
    }

    // Determine whether or not there are any reflective materials in this
    // skin.
    g_bAnyReflective = false;
    g_bAnySpecular   = false;
    for( u_int u = 0; u < uNumMaterials; ++u )
    {
        const u_int uMaterialFlags = Asura_Material_System::GetMaterialFlags( piMaterialIDs[u] );
        if( uMaterialFlags & AsuraMaterialFlag_Reflective )
        {
            g_bAnyReflective = true;
        }
        else if( uMaterialFlags & (AsuraMaterialFlag_Specular_Soft | AsuraMaterialFlag_Specular_Sharp) )
        {
            g_bAnySpecular = true;
        }
    }

    g_bShadow = ( bRenderShadow && SS_CheckForShadow() );

    // Upload renderer.
    bool bNeedFullSetup = Asura_PS2_VU1::LoadRenderer( Asura_PS2_VU1::RENDERER_SMOOTHSKIN );

    const u_int uNumCams       = Asura_Camera::GetNumberOfCameras();
    const u_int uNumQWsPerCam  = 5;
    const u_int uNumCamDataQWs = uNumCams * uNumQWsPerCam;
    const u_int uNumGifTagQWs  = 1 + ASURA_PS2_SS__SIZE__GIF_TAGS;
    const u_int uNumShadowQWs  = 1 + ASURA_PS2_SS__SIZE__CONST_SHADOW_DATA;
    const u_int uQWC           = (bNeedFullSetup ? 2 + uNumCamDataQWs + uNumGifTagQWs + uNumShadowQWs : 0) + 1;
    const u_int uTotalQWs      = 1 + uQWC;

    Asura_PS2_QW* const pxBase = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
    Asura_PS2_QW* pxOut = pxBase;

    (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut))->Set( uQWC, Asura_PS2_DmaTag::PCE_NOCHANGE, Asura_PS2_DmaTag::ID_CNT, false, 0, false );

    // Setup VIF1 double buffering for this skin's max strip length.
    {
        // See Asura_PS2_SmoothSkinDefines.h for details of memory layout
        // and what's going on here.
        const u_int uV                  = pxSkinHdr->usMaxVertsPerStrip;
        const u_int uOffsetFromEndOfMem = 5 + 18 * uV;
        const u_int uInputBfrSize       = 5 * uV;

        pxOut->u32s[2] = SCE_VIF1_SET_BASE( 1024 - uOffsetFromEndOfMem, false );
        pxOut->u32s[3] = SCE_VIF1_SET_OFFSET( uInputBfrSize, false );
        ++pxOut;
    }

    // Need to do some additional VU1 setup if we're starting from scratch.
    if( bNeedFullSetup )
    {
        pxOut->u32s[0] = SCE_VIF1_SET_STCYCL( 1, 1, false );

        // Setup jump table. Note that we'll trash xyz of each QW here as
        // well but currently that doesn't matter.
        pxOut->u32s[1] = SCE_VIF1_SET_UNPACKU( ASURA_PS2_SS__ADDR__SKIN_SUB_LOOKUP_TABLE, 4, Asura_PS2_VU1::VIFPK_S_16, false );
        pxOut->u16s[4] = ASURA_PS2_CONVERT_VU_EXTERN_TO_JMP_DST( VU1_SS_SKIN_VERTS_1_BONE  );
        pxOut->u16s[5] = ASURA_PS2_CONVERT_VU_EXTERN_TO_JMP_DST( VU1_SS_SKIN_VERTS_2_BONES );
        pxOut->u16s[6] = ASURA_PS2_CONVERT_VU_EXTERN_TO_JMP_DST( VU1_SS_SKIN_VERTS_3_BONES );
        pxOut->u16s[7] = ASURA_PS2_CONVERT_VU_EXTERN_TO_JMP_DST( VU1_SS_SKIN_VERTS_4_BONES );
        ++pxOut;

        // Run INIT µprogram.
        pxOut->u32s[0] = ASURA_PS2_MAKE_MSCAL_FROM_VU_PTR( VU1_SS_INIT );

        // UNPACK constant camera data.
        pxOut->u32s[1] = SCE_VIF1_SET_NOP( false );
        pxOut->u32s[2] = SCE_VIF1_SET_STCYCL( uNumQWsPerCam, ASURA_PS2_SS__OFFSET__CAM_DATA_NEXT_CAM, false );
        pxOut->u32s[3] = SCE_VIF1_SET_UNPACK( ASURA_PS2_SS__ADDR__CAM_DATA, uNumCamDataQWs, Asura_PS2_VU1::VIFPK_V4_32, false );
        ++pxOut;
        for( u_int uCam = 0; uCam < uNumCams; ++uCam )
        {
            pxOut = Asura_PS2_GS::GetViewportSetupGifCmds( uCam, Asura_PS2_GS::CONTEXT_1, pxOut );

            (pxOut++)->u128 = reinterpret_cast<const u_long128&>( Asura_PS2_VU1::GetGBClipToScrScaleVec(uCam)     );
            (pxOut++)->u128 = reinterpret_cast<const u_long128&>( Asura_PS2_VU1::GetGBClipToScrTransVec(uCam)     );
            (pxOut++)->u128 = reinterpret_cast<const u_long128&>( Asura_PS2_VU1::GetGBClipToScrClipScaleVec(uCam) );
        }

        // Setup UNPACK of the GIF tags.
        pxOut->u64s[0] = 0uL; // NOPs.
        pxOut->u32s[2] = SCE_VIF1_SET_STCYCL( 1, 1, false );
        pxOut->u32s[3] = SCE_VIF1_SET_UNPACK( ASURA_PS2_SS__ADDR__GIF_TAGS, ASURA_PS2_SS__SIZE__GIF_TAGS, Asura_PS2_VU1::VIFPK_V4_32, false );
        ++pxOut;

        // Geometry setup GIF tag. The default NLOOP should be for the base
        // pass; we'll fix it up on VU1 for anything else.
        {
            Asura_PS2_GifTag* pxGeomSetupGifTag = reinterpret_cast<Asura_PS2_GifTag*>( pxOut++ );
            pxGeomSetupGifTag->SetExceptRegs( ASURA_PS2_SS__RS_OUT_0_NLOOP, true, false, 0, SCE_GIF_PACKED, 1 );
            pxGeomSetupGifTag->uAllRegs = SCE_GIF_PACKED_AD;
        }
        
        // Geometry draw GIF tags. Note that NLOOP is setup in µcode.
        {
            Asura_PS2_GifTag* pxGifTag = reinterpret_cast<Asura_PS2_GifTag*>( pxOut );

            u_int uPrimType = SCE_GS_PRIM_TRISTRIP;
            bool  bTME      = true;

#ifndef ASURA_PS2_SONY_SUBMISSION
            if( Asura_Render::GetWireframe() )
            {
                uPrimType = SCE_GS_PRIM_LINESTRIP;
                bTME      = false;
            }
#endif

            const u_int uNormalPRIM = SCE_GS_SET_PRIM( uPrimType, 1, bTME, false, true, false, 0, 0, 0 );
            pxGifTag->SetExceptRegs( 0, true, true, uNormalPRIM, SCE_GIF_PACKED, 3 );
            pxGifTag->uAllRegs = (SCE_GS_ST << 0) | (SCE_GS_RGBAQ << 4) | (SCE_GS_XYZ2 << 8);

            ++pxGifTag;

            const u_int uSpecPRIM = SCE_GS_SET_PRIM( uPrimType, 1, false, false, true, false, 0, 0, 0 );
            pxGifTag->SetExceptRegs( 0, true, true, uSpecPRIM, SCE_GIF_PACKED, 2 );
            pxGifTag->uAllRegs = (SCE_GS_RGBAQ << 0) | (SCE_GS_XYZ2 << 4);

            pxOut += 2;
        }

        // Shadow setup GIF tag. EOP isn't required since shadow geometry
        // will immediately follow.
        {
            Asura_PS2_GifTag* pxShadowSetupGifTag = reinterpret_cast<Asura_PS2_GifTag*>( pxOut++ );
            pxShadowSetupGifTag->SetExceptRegs( 1, false, false, 0, SCE_GIF_PACKED, 1 );
            pxShadowSetupGifTag->uAllRegs = SCE_GIF_PACKED_AD;
        }

        // Shadow draw GIF tag. Again, NLOOP is setup in µcode.
        {
            Asura_PS2_GifTag* pxShadowDrawGifTag = reinterpret_cast<Asura_PS2_GifTag*>( pxOut++ );
            pxShadowDrawGifTag->SetExceptRegs( 0, true, true, Asura_PS2_Shadows::Proj_GetRenderToBfrPRIMSetting(), SCE_GIF_PACKED, 1 );
            pxShadowDrawGifTag->uAllRegs = SCE_GS_XYZ2;
        }

        // Send the const shadow data.
        {
            pxOut->u128 = ASURA_PS2_INIT_ULONG128( 0uLL ); // NOPs.
            pxOut->u32s[3] = SCE_VIF1_SET_UNPACK( ASURA_PS2_SS__ADDR__CONST_SHADOW_DATA, 1, Asura_PS2_VU1::VIFPK_V4_32, false );
            ++pxOut;

            pxOut->u64s[0] = Asura_PS2_Shadows::Proj_GetRenderToBfrRGBAQ();
            pxOut->u64s[1] = SCE_GS_RGBAQ;
            ++pxOut;
        }
    }

    // Run setup for new skin.
    {
        // Send skin flags and run BEGIN_NEW_SKIN µprogram.
        {
            u_int uSkinFlags = pxSkinHdr->usMaxVertsPerStrip;
            if( g_bShadow )
            {
                uSkinFlags |= ASURA_PS2_SS__FLAG__SHADOW;
            }
        
            pxOut->u32s[0] = SCE_VIF1_SET_ITOP( uSkinFlags, false );
            pxOut->u32s[1] = ASURA_PS2_MAKE_MSCAL_FROM_VU_PTR( VU1_SS_BEGIN_NEW_SKIN );
        }

        // Set weightings-per-vertex clamp.
        {
//#if ASURA_PS2_SS__REDUCE_WEIGHTINGS_WITH_DISTANCE
//          // TODO: May well need to provide project / model-specific
//          // control for this.
//          if( g_fDistFromNearestCamSqd >= dbg_fDistSqdToClampAt2Weightings )
//          {
//              u_int uMaxWeightings = ( g_fDistFromNearestCamSqd >= dbg_fDistSqdToClampAt1Weighting ? 1 : 2 );
//              pxOut->u32s[2] = SCE_VIF1_SET_ITOP( uMaxWeightings, false );
//              pxOut->u32s[3] = ASURA_PS2_MAKE_MSCAL_FROM_VU_PTR( VU1_SS_SKIN_SET_MAX_WEIGHTINGS_PER_VTX_CLAMP );
//          }
//          else
//#endif
            {
                // Default is 4 anyway so don't need to do anything.
                pxOut->u64s[1] = 0uL; // NOPs.
            }
            ++pxOut;
        }       
    }
    
    Asura_Assert( static_cast<u_int>(pxOut - pxBase) == uTotalQWs, "" );

    // If we're starting from scratch, we also need to reset some EE-side
    // state.
    if( bNeedFullSetup )
    {
        // Reset buffer indices. We assume these are still in sync if the
        // renderer was already present on VU; the only time they'll change
        // is when we run one of the VU1_SS_SWITCH... subroutines.
        g_uCurVURSBfr     = 0;
        g_uCurVUTexMatBfr = 0;

        // Reset EE-side renderstate.
        g_uCurEEMaterialFlags    = SS_UNINITIALISED_EE_MATERIAL_FLAGS;
        g_uCurVUMaterialFlags    = SS_UNINITIALISED_VU_MATERIAL_FLAGS;
        g_iCurBasePassTexture    = ASURA_TEXTURE_NONE;
        g_bReflectiveTexUploaded = false;

        // Fill in constant elements of renderstate bfr.
        (g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_0_ALPHA)->u64s[1] = SCE_GS_ALPHA_1;
        (g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_0_TEX1   )->u64s[1] = SCE_GS_TEX1_1;
        (g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_0_MIPTBP1)->u64s[1] = SCE_GS_MIPTBP1_1;
        (g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_0_TEX0   )->u64s[1] = SCE_GS_TEX0_1;
        (g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_0_TEST )->u64s[1] = SCE_GS_TEST_1;
        (g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_1_ALPHA)->u64s[1] = SCE_GS_ALPHA_1;
        (g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_2_ALPHA)->u64s[1] = SCE_GS_ALPHA_1;
        (g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_2_TEX1   )->u64s[1] = SCE_GS_TEX1_1;
        (g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_2_MIPTBP1)->u64s[1] = SCE_GS_MIPTBP1_1;
        (g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_2_TEX0   )->u64s[1] = SCE_GS_TEX0_1;

        // Setup texture buffer layout. Use the 2+1 layout; since we only
        // have to cope with the reflective pass as an extra texture, we can
        // double buffer the base texture. Note that the shadow buffering is
        // done in spare bits of the z buffer so we don't have to worry about
        // that.
        Asura_PS2_TextureManagement::SetBufferLayout( ASURA_PS2_TEXBFRLAYOUT_2_MEDIUM_1_SMALL, true, 0, 1 );

        // Ensure renderstate is reset to defaults.
        Asura_PlatformRenderStates::CreateDefaultRenderStatePacket( Asura_PlatformRenderStates::CONTEXT_1 );
        Asura_PlatformRenderStates::ApplyRenderStates();
    }

    return true;
}

void Asura_PS2_SmoothSkin_Renderer::UploadBoneData( void )
{
    const Asura_Hierarchy_Skin* pxSkin      = Asura_Hierarchy_System::s_pxSkin;
    const Asura_Matrix_3x3*     pxBoneOris  = Asura_Hierarchy_System::s_axBoneOri;
    const Asura_Vector_3*       pxBonePosns = Asura_Hierarchy_System::s_axBonePos;

    // Smooth skins must have at least one other bone, since no vertices are
    // supposed to be weighted to bone 0.
    const u_int uNumBones = pxSkin->NumberOfBones;
    Asura_Assert( uNumBones > 1, "" );

    // Since nothing should be weighted to bone 0, there should be no need to
    // upload it.
    const u_int uNumBonesToSend = uNumBones - 1;

    const u_int uNumBoneDataWords = uNumBonesToSend * 12;
    const u_int uNumDataWords     = 2 + uNumBoneDataWords;
    const u_int uQWC              = ((uNumDataWords - 2) + 3) >> 2; // 2 words will go in the DMA tag QW, plus we need to account for alignment.
    const u_int uTotalQWs         = 1 + uQWC;

    Asura_PS2_QW* const pxBase = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
    Asura_PS2_QW* pxOut = pxBase;

    reinterpret_cast<Asura_PS2_DmaTag*>(pxOut)->Set( uQWC, Asura_PS2_DmaTag::PCE_NOCHANGE, Asura_PS2_DmaTag::ID_CNT, false, 0, false );

    pxOut->u32s[2] = SCE_VIF1_SET_STCYCL( 1, 1, false );

    const u_int uNumQWsUNPACKed = uNumBonesToSend << 2;
    const u_int uDestAddr       = ASURA_PS2_SS__ADDR__BONE_MATRIX_BFR;

    pxOut->u32s[3] = SCE_VIF1_SET_UNPACK( uDestAddr, uNumQWsUNPACKed, Asura_PS2_VU1::VIFPK_V3_32, false );
    ++pxOut;

    float* const pfBoneDataBase = &pxOut->f32s[0];
    float* pfBoneDataOut = pfBoneDataBase;

    // Calculate and write out bone data.
    {
        const Asura_Matrix_3x3* pxBoneRestOris      = pxSkin->BoneRestOrientation;
        const Asura_Vector_3*   pxBoneRestPositions = pxSkin->BoneRestPosition;

        Asura_Matrix_3x3 xOriTmp;
        Asura_Vector_3   xPosTmp;

        for( u_int uBone = 1; uBone < uNumBones; ++uBone )
        {
            xOriTmp = pxBoneOris[uBone] * pxBoneRestOris[uBone];
            for( u_int u = 0; u < 3; ++u )
            { 
                for( u_int v = 0; v < 3; ++v )
                {
                    *pfBoneDataOut++ = xOriTmp[v][u];
                }
            }
            
            xPosTmp = pxBoneRestPositions[uBone] * xOriTmp;
            for( u_int u = 0; u < 3; ++u )
            {
                *pfBoneDataOut++ = pxBonePosns[uBone][u] - xPosTmp[u];
            }
        }
    }

    // Fix up output ptr and pad with NOPs.
    {
        Asura_Assert( static_cast<u_int>(pfBoneDataOut - pfBoneDataBase) == uNumBoneDataWords, "" );

        pxOut += uNumBoneDataWords >> 2;

        u_int uPad = uNumBoneDataWords & 3;
        if( uPad )
        {
            for( ; uPad < 4; ++uPad )
            {
                pxOut->u32s[uPad] = SCE_VIF1_SET_NOP( false );
            }
            ++pxOut;
        }
    }

    Asura_Assert( static_cast<u_int>(pxOut - pxBase) == uTotalQWs, "" );
}

void Asura_PS2_SmoothSkin_Renderer::InsertWorldspaceTransform( const Asura_PS2_QW* pxTransform )
{
    sceVu0MulMatrix( &g_pxO2W->f32s, &(const_cast<Asura_PS2_QW*>(pxTransform))->f32s, &g_pxO2W->f32s );
}

void Asura_PS2_SmoothSkin_Renderer::UploadCamData( void )
{
    const u_int uCamVisFlags       = g_uEECamVisFlags;
    const u_int uNumCams           = Asura_Camera::GetNumberOfCameras();
    const bool  bNeedO2VMat        = g_bAnyReflective;
    const bool  bNeedObjSpCamPos   = g_bAnySpecular;
    const bool  bBonesInWorldspace = g_bBonesInWorldspace;

    const u_int uBaseQWC        = 1 + uNumCams * 4;
    const u_int uO2VQWC         = ( bNeedO2VMat ? 1 + uNumCams * 4 : 0 );
    const u_int uObjSpCamPosQWC = ( bNeedObjSpCamPos ? 1 + uNumCams * 1 : 0 );
    const u_int uQWC            = uBaseQWC + uO2VQWC + uObjSpCamPosQWC;
    const u_int uTotalQWs       = 1 + uQWC;

    Asura_PS2_QW* const pxBase = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
    Asura_PS2_QW* pxOut = pxBase;

    (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++))->SetWithNOPs( uQWC,
                                                                 Asura_PS2_DmaTag::PCE_NOCHANGE,
                                                                 Asura_PS2_DmaTag::ID_CNT,
                                                                 false,
                                                                 0,
                                                                 false );

    // Keep ptr to first word so we can come back and fill in the vis-and-
    // cull flags ITOP cmd once we've calculated them.
    u_int* const puVisAndCullFlagsITOP = &pxOut->u32s[0];

    // MSCAL for VU1 to read those flags.
    pxOut->u32s[1] = ASURA_PS2_MAKE_MSCAL_FROM_VU_PTR( VU1_SS_LOAD_CAM_FLAGS );

    // Setup O->GBC matrix unpack.
    pxOut->u32s[2] = SCE_VIF1_SET_STCYCL( 4, ASURA_PS2_SS__OFFSET__CAM_DATA_NEXT_CAM, false );
    pxOut->u32s[3] = SCE_VIF1_SET_UNPACK( ASURA_PS2_SS__ADDR__CAM_DATA + ASURA_PS2_SS__OFFSET__CAM_DATA_O2GBC, uNumCams * 4, Asura_PS2_VU1::VIFPK_V4_32, false );
    ++pxOut;

    u_int uVUCamFlags = ( g_uNumCamsThatCanSee > 1 ? ASURA_PS2_SS__FLAG__VIS_FOR_MORE_THAN_ONE_CAM : 0 );

    // Write out matrices. Calculate vis and cull flags for VU as we go.
    {
        const Asura_Vector_3& xBSCentre = g_xBSCentre;
        const float           fBSRadius = g_fBSRadius;

        u_int uEEVisBit = 1 << 0;
        u_int uVUVisBit = 1 << 7;

        for( u_int uCam = 0; uCam < uNumCams; ++uCam )
        {
            if( uCamVisFlags & uEEVisBit )
            {
                Asura_Camera::SetCameraIndex( uCam );

                // Update flags for VU.
                uVUCamFlags |= uVUVisBit;
                if( !Asura_PS2_VU1::IsSphereEntirelyWithinGuardbandFrustum(xBSCentre, fBSRadius) )
                {
                    uVUCamFlags |= uVUVisBit >> 1;
                }

                // Write out O->GBC matrix for this camera.
                sceVu0MulMatrix( &pxOut->f32s, Asura_PS2_VU1::GetWorldToClipMatGuardband(), &g_pxO2W->f32s );
            }

            pxOut      += 4;
            uEEVisBit <<= 1;
            uVUVisBit >>= 2;
        }
    }
    
    // Write out vis-and-cull flags ITOP cmd now.
    *puVisAndCullFlagsITOP = SCE_VIF1_SET_ITOP( uVUCamFlags, false );

    // Deal with O->V matrices, if required.
    if( bNeedO2VMat )
    {
        pxOut->u64s[0] = 0uL; // NOPs.
        pxOut->u32s[2] = SCE_VIF1_SET_STCYCL( 4, ASURA_PS2_SS__OFFSET__CAM_DATA_NEXT_CAM, false );
        pxOut->u32s[3] = SCE_VIF1_SET_UNPACK( ASURA_PS2_SS__ADDR__CAM_DATA + ASURA_PS2_SS__OFFSET__CAM_DATA_O2V, uNumCams * 4, Asura_PS2_VU1::VIFPK_V4_32, false );
        ++pxOut;

        u_int uEEVisBit = 1 << 0;
        
        for( u_int uCam = 0; uCam < uNumCams; ++uCam )
        {
            if( uCamVisFlags & uEEVisBit )
            {
                // Keep a copy of the O->V matrices for setting up
                // texture coordinate matrices later.
                Asura_PS2_QW* const pxO2V = g_pxO2VMats + (uCam << 2);

                sceVu0MulMatrix( &pxO2V->f32s, Asura_PS2_VU1::GetWorldToViewMat(uCam), &g_pxO2W->f32s );
                Asura_PS2_VU1::CopyPS2Mat( pxOut, pxO2V );
            }

            pxOut      += 4;
            uEEVisBit <<= 1;
        }
    }

    // And the object space camera positions.
    if( bNeedObjSpCamPos )
    {
        pxOut->u64s[0] = 0uL; // NOPs.
        pxOut->u32s[2] = SCE_VIF1_SET_STCYCL( 1, ASURA_PS2_SS__OFFSET__CAM_DATA_NEXT_CAM, false );
        pxOut->u32s[3] = SCE_VIF1_SET_UNPACK( ASURA_PS2_SS__ADDR__CAM_DATA + ASURA_PS2_SS__OFFSET__CAM_DATA_OBJ_SPACE_POS, uNumCams * 1, Asura_PS2_VU1::VIFPK_V4_32, false );
        ++pxOut;

        u_int uEEVisBit = 1 << 0;

        for( u_int uCam = 0; uCam < uNumCams; ++uCam )
        {
            if( uCamVisFlags & uEEVisBit )
            {
                Asura_Camera::SetCameraIndex( uCam );

                Asura_Vector_3 xObjSpCamPos = *Asura_Camera::GetPosition();
                if( !bBonesInWorldspace )
                {
                    xObjSpCamPos -= *Asura_Hierarchy_System::s_pxPos;
                    xObjSpCamPos = Asura_Hierarchy_System::s_pxOri->ApplyTranspose( xObjSpCamPos );
                }

                pxOut->f32s[0] = xObjSpCamPos.x;
                pxOut->f32s[1] = xObjSpCamPos.y;
                pxOut->f32s[2] = xObjSpCamPos.z;
                pxOut->f32s[3] = 1.0f;
            }

            ++pxOut;
            uEEVisBit <<= 1;
        }
    }

    Asura_Assert( static_cast<u_int>(pxOut - pxBase) == uTotalQWs, "" );
}

void Asura_PS2_SmoothSkin_Renderer::SetTexture( int iTexID )
{
    // Bilinear.
    const int iMMAG = SCE_GS_LINEAR;
    const int iMMIN = SCE_GS_LINEAR_MIPMAP_NEAREST;

    // First upload the reflective texture, if necessary. We just leave this
    // resident in buffer 2 once it's been done once for the first skin that
    // requires it.
    if( g_bAnyReflective && !g_bReflectiveTexUploaded )
    {
        const u_int uReflectiveBfr = 2;

        const int iReflectiveTexID = Asura_Render::GetReflectanceMap();

		bool bUploaded = Asura_PS2_TextureManagement::UploadTexture( iReflectiveTexID, uReflectiveBfr );

        Asura_PS2_TextureManagement::SetupTextureEnvironment( bUploaded,
                                                              ASURA_PS2_TEXREGFLAG_TEXFLUSH,
                                                              iReflectiveTexID,
                                                              uReflectiveBfr );

        Asura_PS2_TextureManagement::GetTEX1Setting( &(g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_2_TEX1)->u64s[0], iReflectiveTexID, iMMAG, iMMIN );
        Asura_PS2_TextureManagement::GetMIPTBP1Setting( &(g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_2_MIPTBP1)->u64s[0], iReflectiveTexID, uReflectiveBfr );
        Asura_PS2_TextureManagement::GetTEX0Setting( &(g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_2_TEX0)->u64s[0], iReflectiveTexID, uReflectiveBfr );

        g_bReflectiveTexUploaded = true;
    }

    // Check if we've already been using the texture in question. If so,
    // there's no work required here.
    if( g_iCurBasePassTexture == iTexID ) { return; }

	bool bUploaded = Asura_PS2_TextureManagement::UploadTexture( iTexID, Asura_PS2_TextureManagement::BFR_AUTO, 0 );

    Asura_PS2_TextureManagement::SetupTextureEnvironment( bUploaded,
                                                          ASURA_PS2_TEXREGFLAG_TEXFLUSH,
                                                          iTexID,
                                                          Asura_PS2_TextureManagement::BFR_AUTO,
                                                          iMMAG, iMMIN );

    // Copy the base pass texture register settings into our local RS bfr.
    Asura_PS2_TextureManagement::GetTEX1Setting( &(g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_0_TEX1)->u64s[0], iTexID, iMMAG, iMMIN );
    Asura_PS2_TextureManagement::GetMIPTBP1Setting( &(g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_0_MIPTBP1)->u64s[0], iTexID );
    Asura_PS2_TextureManagement::GetTEX0Setting( &(g_pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_0_TEX0)->u64s[0], iTexID );

    g_iCurBasePassTexture = iTexID;

    // Ensure that we send RS for the next material change, regardless of
    // whether or not it's a new one.
    g_uCurEEMaterialFlags |= SS_UNINITIALISED_EE_MATERIAL_FLAGS;
}

void Asura_PS2_SmoothSkin_Renderer::UploadLightData( const Asura_SphericalHarmonics_Order2_RGB& xSH, float fAlpha )
{
    const u_int uQWC      = ASURA_PS2_SS__SIZE__LIGHT_DATA_BFR;
    const u_int uTotalQWs = 1 + uQWC;

    Asura_PS2_QW* const pxBase = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
    Asura_PS2_QW* pxOut = pxBase;

    (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++))->Set( uQWC,
                                                         Asura_PS2_DmaTag::PCE_NOCHANGE,
                                                         Asura_PS2_DmaTag::ID_CNT,
                                                         false,
                                                         0,
                                                         false,
                                                         SCE_VIF1_SET_STCYCL(1, 1, false),
                                                         SCE_VIF1_SET_UNPACK(ASURA_PS2_SS__ADDR__LIGHT_DATA_BFR, uQWC, Asura_PS2_VU1::VIFPK_V4_32, false) );

    // Need a local copy since we're going to be rotating it below.
	Asura_SphericalHarmonics_Order2_RGB xLocalSH = xSH;

    // If required, calculate a light level for the shadow.
    if( g_bShadow )
    {
        g_fShadowLightLevel = Asura_PS2_Shadows::CalculateShadowLightLevelFromSH( xSH, fAlpha );
    }

    // Output the SH matrix.
    {
        // We'll be working in modelspace on the VU.
        // (Though of course this may actually be worldspace.)
        if( !g_bBonesInWorldspace )
        {
            xLocalSH.RotateToModelspace( *Asura_Hierarchy_System::s_pxOri );
        }

        // We need to scale and transpose the coefficients from independent
        // code as we copy them.
        for( u_int u = 0; u < 4; ++u )
        {
            for( u_int v = 0; v < 3; ++v )
            {
                pxOut->f32s[v] = 255.0f * xLocalSH.m_afCoefficient[(v << 2) + u];
            }
            ++pxOut;
        }

        // The alpha value needs to be jammed in the row containing the
        // ambient terms.
        (pxOut - 4)->f32s[3] = 128.0f * fAlpha;

        // And we need to set the w component of the second row to 0 (due to
        // the way the VU1 LightVtxWithSH macro works - see
        // VU1_CommonMacros.i.)
        (pxOut - 3)->f32s[3] = 0.0f;
    }
    
    Asura_Assert( static_cast<u_int>(pxOut - pxBase) == uTotalQWs, "" );
}

void Asura_PS2_SmoothSkin_Renderer::UploadShadowData( void )
{
    if( !g_bShadow ) { return; }

    Asura_PS2_ShadowBuffer::s_pxCurrentShadowBuffer->JustSetupRenderToBfr( SCE_VIF1_SET_FLUSH(false) );
	Asura_PS2_ShadowBuffer::s_pxCurrentShadowBuffer->SetLightLevel( g_fShadowLightLevel );

    const u_int uQWsForUNPACK = ASURA_PS2_SS__SIZE__SHADOW_DATA_BFR;
    const u_int uTotalQWs     = 1 + uQWsForUNPACK;

    Asura_PS2_QW* const pxBase = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
    Asura_PS2_QW* pxOut = pxBase;

    (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++))->Set( uQWsForUNPACK,
                                                         Asura_PS2_DmaTag::PCE_NOCHANGE,
                                                         Asura_PS2_DmaTag::ID_CNT,
                                                         false,
                                                         0,
                                                         false,
                                                         SCE_VIF1_SET_STCYCL(1, 1, false),
                                                         SCE_VIF1_SET_UNPACK(ASURA_PS2_SS__ADDR__SHADOW_DATA_BFR, uQWsForUNPACK, Asura_PS2_VU1::VIFPK_V4_32, false) );

    // Projection matrix.
    {
        // Fix position of shadow-casting light a distance above the top of
        // the BS.
        Asura_Vector_3 xLightPos = g_xBSCentre;
        xLightPos.y -= g_fBSRadius;
        xLightPos.y += ASURA_PS2_SHADOW_CASTING_LIGHT_OFFSET_Y;

        Asura_PS2_ShadowBuffer::s_pxCurrentShadowBuffer->Proj_GetShadowProjMat( g_pxO2W->f32s, pxOut->f32s );
        pxOut += 4;
    }

    Asura_Assert( static_cast<u_int>(pxOut - pxBase) == uTotalQWs, "" );
}

void Asura_PS2_SmoothSkin_Renderer::End( void )
{
    // Final FLUSH.
    // TODO: We really only require a FLUSHE between skins, though it ought
    // to be a full FLUSH after the last one.
    {
        Asura_PS2_DmaTag* pxTag = reinterpret_cast<Asura_PS2_DmaTag*>( Asura_PS2_DeferredRendering::GetVifBuffer(1) );
        pxTag->Set( 0,
                    Asura_PS2_DmaTag::PCE_NOCHANGE,
                    Asura_PS2_DmaTag::ID_CNT,
                    false,
                    0,
                    false,
                    SCE_VIF1_SET_NOP(false),
                    SCE_VIF1_SET_FLUSH(false) );
    }

	Asura_PS2_ShadowBuffer::s_pxCurrentShadowBuffer = NULL;

	ASURA_PS2_PROFILE__ADD_VIF_END( ASURA_PS2_PROFILE__SKINNED_MARKER );

#ifndef ASURA_PS2_SONY_SUBMISSION
    if( dbg_bDrawSmoothSkinBBs )
    {
        Asura_PS2_DebugInfo::RenderBBForSphere( g_xBSCentre, g_fBSRadius );
    }

    if( dbg_bDrawSmoothSkinBoneBBs )
    {
        const Asura_Hierarchy_Skin* pxSkin = Asura_Hierarchy_System::s_pxSkin;
        const Asura_Hierarchy_SectionBoundingBox* pxBoneBBs = pxSkin->m_pxSectionBoundingBoxData;
        if( pxBoneBBs )
        {
            const u_int auColours[4] = { 0xFFFFFF00u, 0xFF00FFFFu, 0xFFFF00FFu, 0xFFFF0000u };

            Asura_Matrix_3x3 xOri;
            Asura_Vector_3   xPos;

            for( int i = 0; i < pxSkin->NumberOfBones; ++i )
            {
                if( !pxBoneBBs[i].m_uValid ) { continue; }

                if( -1 != dbg_iSpecificSmoothSkinBoneBBToDraw )
                {
                    if( i != dbg_iSpecificSmoothSkinBoneBBToDraw ) { continue; }
                }

                xOri = Asura_Hierarchy_System::GetWorldspaceBoneOri( i );
                xPos = Asura_Hierarchy_System::GetWorldspaceBonePos( i );

                Asura_Render::RenderBB( pxBoneBBs[i].m_xBB, xPos, xOri, auColours[i & 3] );
            }
        }
    }
#endif

    Asura_PS2_SPR_Dealloc( reinterpret_cast<Asura_PS2_QW*>(0x70000000) );
}

void Asura_PS2_SmoothSkin_Renderer::GetHdrData( u_int* puNumMaterials,
                                                const int** ppiMaterialIDs,
                                                const Asura_PS2_SmoothSkin_MaterialHdr** ppxFirstMaterialHdr,
                                                u_int* puFirstDataChainAddr )
{
    const Asura_PS2_SmoothSkin_SkinHdr* pxHdr = g_pxSkinHdr;

    u_int uNumMaterials      = pxHdr->usNumMaterials;
    const int* piMaterialIDs = reinterpret_cast<const int*>( pxHdr + 1);

    *puNumMaterials       = uNumMaterials;
    *ppiMaterialIDs       = piMaterialIDs;
    *ppxFirstMaterialHdr  = reinterpret_cast<const Asura_PS2_SmoothSkin_MaterialHdr*>( piMaterialIDs + uNumMaterials );
    *puFirstDataChainAddr = reinterpret_cast<u_int>(pxHdr) + (pxHdr->usQWOffsetToFirstStripDataChain << 4);
}

void Asura_PS2_SmoothSkin_Renderer::RenderForMaterial( const Asura_PS2_SmoothSkin_MaterialHdr** ppxMaterialHdr, u_int* puChainAddr )
{
    const Asura_PS2_SmoothSkin_MaterialHdr*   pxMaterialHdr   = *ppxMaterialHdr;
    const Asura_PS2_SmoothSkin_SubElementHdr* pxSubElementHdr = reinterpret_cast<const Asura_PS2_SmoothSkin_SubElementHdr*>( pxMaterialHdr + 1 );

    u_int uTrisRendered = 0;
    u_int uChainAddr    = *puChainAddr;

    for( u_int uNumSubElementsLeft = pxMaterialHdr->uNumElementsUsingThisMaterial; uNumSubElementsLeft; --uNumSubElementsLeft )
    {
//          if( should_be_rendered(pxSubElementHdr->usElementIdx) )
        {
            // CALL this sub-element's strip chain. Ensure VU1 PC is in
            // the correct place first.
            Asura_PS2_DmaTag* pxTag = reinterpret_cast<Asura_PS2_DmaTag*>( Asura_PS2_DeferredRendering::GetVifBuffer(1) );
            pxTag->Set( 0,
                        Asura_PS2_DmaTag::PCE_NOCHANGE,
                        Asura_PS2_DmaTag::ID_CALL,
                        false,
                        uChainAddr,
                        false,
                        SCE_VIF1_SET_NOP(false),
                        ASURA_PS2_MAKE_MSCAL_FROM_VU_PTR(VU1_SS_READY_FOR_NEXT_STRIP) );

            uTrisRendered += pxSubElementHdr->usTriCount;
        }

        uChainAddr += pxSubElementHdr->uByteOffsetToNextChain;
        ++pxSubElementHdr;
    }

    Asura_PS2_Render::UpdateTriStats( uTrisRendered * g_uNumCamsThatCanSee, g_uNumPassesPerTri, Asura_PS2_Render::TRICOUNT_SMOOTH_SKIN );

    *ppxMaterialHdr = reinterpret_cast<const Asura_PS2_SmoothSkin_MaterialHdr*>( pxSubElementHdr );
    *puChainAddr    = uChainAddr;
}

void Asura_PS2_SmoothSkin_Renderer::CB_JustWriteZ( Asura_PS2_QW* pxRSBfr )
{
    // Change TEST so that we only write to Z.
    (pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_0_TEST)->u64s[0] =
        SCE_GS_SET_TEST( true, SCE_GS_ALPHA_NEVER, 0, SCE_GS_AFAIL_ZB_ONLY,
                         false, 0,
                         true, SCE_GS_ZGEQUAL );

    // (MP - 28.06.2005) We could also modify TEX0 here to pretend we're
    // using some tiny texture, so we don't get any cache misses while
    // rendering this pass. I'm not sure we'll be GS bound at this point
    // though.
}

void Asura_PS2_SmoothSkin_Renderer::CB_IgnoreTextureAlpha( Asura_PS2_QW* pxRSBfr )
{
    // Just modify TEX0 to stop the GS using the alpha channel.
    (pxRSBfr + ASURA_PS2_SS__OFFSET__RS_IN_PASS_0_TEX0)->u64s[0] &= ~GS_TEX0_TCC_M;
}

// F U N C T I O N   I M P L E M E N T A T I O N S ///////////////////////////

static bool SS_CheckForShadow( void )
{
    // Shadows globally disabled for smooth skins?
    if( !Asura_PS2_RenderingOptions::GetOption(Asura_PS2_RenderingOptions::OPTION_ENABLE_SMOOTH_SKIN_SHADOWS) ) { return false; }

    const Asura_Animation* pxAnim       = g_pxAnim;
    const Asura_Vector_3&  xBSCentre    = g_xBSCentre;
    const float            fBSRadius    = g_fBSRadius;
    const u_int            uCamVisFlags = g_uEECamVisFlags;
    
    // Too far from any camera to require a shadow?
    float fMinDistFromCam;
    float fShadowAlpha = Asura_PS2_Shadows::CalcAlphaForMinDistFromCam( xBSCentre, fBSRadius, uCamVisFlags, &fMinDistFromCam );
    if( fShadowAlpha <= 0.0f ) { return false; }

	// Setup shadow buffer for this anim
	Asura_PS2_ShadowBuffer* pxShadowBuffer = Asura_PS2_ShadowBufferSystem::GetShadowBufferForAnim( pxAnim, xBSCentre, fBSRadius, uCamVisFlags );
	Asura_PS2_ShadowBuffer::s_pxCurrentShadowBuffer = pxShadowBuffer;

	// fail gracefully
	if (!pxShadowBuffer) { return false; }

	if (pxShadowBuffer->IsForcedFallback())
	{
        float fShadowY;
		Asura_PS2_Shadows::Proj_GetShadowYAndHeightForAnim( pxAnim, *Asura_Hierarchy_System::s_pxPos, &fShadowY, NULL );
		pxShadowBuffer->SetShadowBufferQuadHeight( fShadowY );
	}

    return true;
}
