//////////////////////////////////////////////////////////////////////////////
//
// Asura_Platform_CSM.cpp
//
// Copyright (c) 2008 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

/******************************************************************************/
/* Included Files                                                             */
/******************************************************************************/

#include "Core/Asura.h"

#if defined ASURA_USE_CSM

#include "Shadows/Asura_CSM.h"

// Asura Platform-independent
#include "Animation/Asura_TextureAnimation.h"
#include "Camera/Asura_Camera.h"
#include "Core/Asura_MaterialResponse_System.h"
#include "Core/Asura_Maths.h"
#include "Core/Asura_Timers.h"
#include "Debugging/Asura_Assert.h"
#include "Interface/Asura_CommandConsole.h"
#include "Rendering/Asura_Render.h"
#include "Rendering/Asura_Viewport.h"

// Asura Xbox 360
#include "Core/Asura_PlatformBasics.h"
#include "Rendering/Asura_Xbox360_Effect_System.h"
#include "Rendering/Asura_Xbox360_EnvironmentRenderer.h"
#include "Rendering/Asura_Xbox360_Graphics.h"
#include "Rendering/Asura_Xbox360_IndexBuffer.h"
#include "Rendering/Asura_Xbox360_VertexBuffer.h"

// Effects
#include "CSM.h"

// Misc
#include <xgraphics.h>

/******************************************************************************/
/* Constants                                                                  */
/******************************************************************************/

const u_int uPASS_CREATE       = 0;
const u_int uPASS_CREATE_TRANS = 1;
const u_int uPASS_BLUR_HORZ    = 2;
const u_int uPASS_BLUR_VERT    = 3;
const u_int uPASS_APPLY        = 4;

/******************************************************************************/
/* Global Variables                                                           */
/******************************************************************************/

// Our pixel shader only supports up to 5 maps at the moment, to speed up determining which map to
// use for each pixel. This could be extended if necessary, at the cost of some ALU ops

#if ASURA_CSM_MAX_MAPS > 5
	#error 360 CSM only works with up to 5 maps
#endif

static D3DXMATRIX g_axCSMMatrices[ ASURA_CSM_MAX_MAPS ];

DVSVAR float g_fSlopeScaleDepthBias = 0.0f;
DVSVAR float g_fDepthBias = 0.0f;
DVSVAR float g_fPower = 1.0f;
DVSVAR bool  g_bCullStrips = true;
DVSVAR bool  g_bStaggerUpdates = true;

static Direct3D_ArrayTexture* g_pxDepthArrayTexture = NULL;
static Direct3D_ArrayTexture* g_pxMomentsArrayTexture = NULL;

static Direct3D_Surface* g_pxBlurRT = NULL;
static Direct3D_Surface* g_pxCSMDS = NULL;

static Asura_Xbox360_Effect* g_pxCSMEffect = NULL;
static Asura_Xbox360_Effect_System::Handle g_xShadowViewProjHandle = 0;
static Asura_Xbox360_Effect_System::Handle g_xBlurSliceHandle = 0;
static Asura_Xbox360_Effect_System::Handle g_xShadowViewProjArrayHandle = 0;
static Asura_Xbox360_Effect_System::Handle g_xInvCamViewProjHandle = 0;
static Asura_Xbox360_Effect_System::Handle g_xCameraTanHalfFOVHandle = 0;
static Asura_Xbox360_Effect_System::Handle g_xSplitsHandle = 0;
static Asura_Xbox360_Effect_System::Handle g_xArrayCoeffsHandle = 0;
static Asura_Xbox360_Effect_System::Handle g_xPowerHandle = 0;
static Asura_Xbox360_Effect_System::Handle g_xStrengthHandle = 0;
static Asura_Xbox360_Effect_System::Handle g_xTexAnimHandle = 0;

/******************************************************************************/
/* Functions                                                                  */
/******************************************************************************/

static bool UpdateThisFrame( const u_int uMap )
{
	if( !g_bStaggerUpdates ) return true;

	const bool bEvenFrame = ((Asura_Timers::GetFrameNumber() & 1) == 0);

	// This assumes 4 maps, not a problem at the moment
	return bEvenFrame ? (uMap == 0 || uMap == 3) : (uMap == 1 || uMap == 2);
}

////////////////////////////////////////////////////////////////////////////////
//
// CreateViewMatrix()
//
// Creates a D3DXMATRIX suitable for use as a view matrix, given translation and
// rotation components in Asura form. The rotation is transposed in the
// function, but the translation is assumed to already be negated
//
////////////////////////////////////////////////////////////////////////////////

static void CreateViewMatrix( D3DXMATRIX& xOut, const Asura_Vector_3& xAsuraTrans,
	const Asura_Matrix_3x3& xAsuraRot )
{
	D3DXMatrixTranslation( &xOut, xAsuraTrans.x, xAsuraTrans.y, xAsuraTrans.z );

	D3DXMATRIX xRot;
	D3DXMatrixIdentity( &xRot );

	// Fudging like this compensates for Asura's y-axis being upside-down. Also note there is an
	// implicit transpose here, as the naming is the other way round too

	xRot._11 =  xAsuraRot.e11;
	xRot._12 = -xAsuraRot.e12;
	xRot._13 =  xAsuraRot.e13;
	xRot._21 =  xAsuraRot.e21;
	xRot._22 = -xAsuraRot.e22;
	xRot._23 =  xAsuraRot.e23;
	xRot._31 =  xAsuraRot.e31;
	xRot._32 = -xAsuraRot.e32;
	xRot._33 =  xAsuraRot.e33;

	xOut *= xRot;
}

////////////////////////////////////////////////////////////////////////////////
//
// Blur()
//
// Takes the depth texture from the 'create shadows' step, computes the squares
// of the texels, and gaussian blurs the result, storing in the RG16 texture
//
////////////////////////////////////////////////////////////////////////////////

static void Blur( const u_int uNumMaps )
{
	SCOPEDPIXEVENT(( 0, "Blur" ));

	Direct3D_Device* const pxDevice = Asura_Xbox360_Graphics::GetDevice();
	if( !pxDevice || !g_pxCSMEffect ) return;

	pxDevice->SetVertexDeclaration( Asura_Xbox360_Graphics::GetFullScreenVD() );
	Asura_Xbox360_Graphics::GetFullScreenVB().Bind();

	pxDevice->SetRenderTarget( 0, g_pxBlurRT );
	pxDevice->SetDepthStencilSurface( NULL );

	Asura_Render::CurrentStates.RequestAlphaTest( false );
	Asura_Render::CurrentStates.RequestCullMode( ASURA_CULLMODE_NONE );
	Asura_Render::CurrentStates.RequestZBufferEnabled( false );

	// Setting this means we can forget about the half-pixel offset in the shader
	DWORD uOldOffset = FALSE;
	pxDevice->GetRenderState( D3DRS_HALFPIXELOFFSET, &uOldOffset );
	pxDevice->SetRenderState_Inline( D3DRS_HALFPIXELOFFSET, TRUE );

	for( u_int u = 0; u < 2; ++u )
	{
		const u_int uPass = (u == 0 ? uPASS_BLUR_HORZ : uPASS_BLUR_VERT);

		SCOPEDPIXEVENT(( 0, "%s", uPass == uPASS_BLUR_HORZ ? "Horizontal" : "Vertical" ));

		g_pxCSMEffect->BeginPass( uPass );

		// NB going backwards, so we don't overwrite the next bit
		for( int v = uNumMaps - 1; v >= 0; --v )
		{
			if( !UpdateThisFrame(v) ) continue;

			SCOPEDPIXEVENT(( 0, "Map %u", v ));

			// Using unnormalised texcoords, so don't divide by num slices. Also, don't add the
			// half texel offset, because D3DRS_HALFPIXELOFFSET is on
			const float fBlurSlice = static_cast< float >(v);
			g_pxCSMEffect->SetFloat( g_xBlurSliceHandle, fBlurSlice );

			g_pxCSMEffect->Commit();

			pxDevice->DrawVertices( D3DPT_RECTLIST, 0, 3 );

			pxDevice->Resolve( D3DRESOLVE_EXPONENTBIAS(10), NULL, g_pxMomentsArrayTexture, NULL, 0,
				v, NULL, 1.0f, 0, NULL );
		}
	}

	pxDevice->SetRenderState_Inline( D3DRS_HALFPIXELOFFSET, uOldOffset );
}

////////////////////////////////////////////////////////////////////////////////
//
// RenderGeometry()
//
// Renders the environment geometry using the shadow creation passes
//
////////////////////////////////////////////////////////////////////////////////

static void RenderGeometry( const Asura_Vector_3& xBSCentre, const float fBSRadius,
	const float fExtraFrustumDepth )
{
	SCOPEDPIXEVENT(( 0, "Environment Geometry" ));

	Direct3D_Device* const pxDevice = Asura_Xbox360_Graphics::GetDevice();
	if( !pxDevice || !g_pxCSMEffect ) return;

	pxDevice->SetVertexDeclaration( Asura_Xbox360_EnvironmentRenderer::GetVD() );
	Asura_Xbox360_EnvironmentRenderer::GetIB().Bind();

	// Construct an OBB around the CSM split bounding sphere. Could get tighter by just surrounding
	// the view frustum split section, but don't have OBB functions for that right now
	Asura_OBB xShadowOBB;

	// Enable toggling for perf testing. Once satisfied this bool can go
	if( g_bCullStrips )
	{
		Asura_Matrix_3x3 xCSMOrient;
		Asura_Maths::OrientationFromDirection( xCSMOrient, Asura_CSM::GetShadowDir() );

		xShadowOBB.SetOrientation( Asura_AMatrix3x3( xCSMOrient ) );

		// Offset centre to account for "extra frustum depth"
		Asura_AVector3 xCentre( xBSCentre );
		xCentre -= Asura_AVector3( Asura_CSM::GetShadowDir() ) * (fExtraFrustumDepth * 0.5f);
		xShadowOBB.SetCentre( xCentre );

		// Same for OBB depth
		const float fHalfExtentZ = fBSRadius + fExtraFrustumDepth * 0.5f;
		const Asura_AVector3 xHalfExtents( fBSRadius, fBSRadius, fHalfExtentZ );
		xShadowOBB.SetHalfExtents( xHalfExtents );
	}

	const u_int uNumStrips = Asura_Xbox360_EnvironmentRenderer::GetNumStrips();

	int iCurrPass = -1;

	for( u_int u = 0; u < uNumStrips; ++u )
	{
		const Asura_Xbox360_EnvironmentRenderer_Strip& xStrip =
			Asura_Xbox360_EnvironmentRenderer::GetStrip(u);

		// Enable toggling for perf testing. Once satisfied this bool can go
		if( g_bCullStrips )
		{
			// Only render strips within the map's bounding OBB
			if( !Asura_OBB( xStrip.m_xBoundingBox ).Intersects( xShadowOBB ) ) continue;
		}

		const Asura_MaterialResponse* const pxMaterial =
			Asura_MaterialResponse_System::GetMaterialByHashID(
			xStrip.m_auMaterialResponseHashIDs[0] );

		// Check for strips we should skip

		if( pxMaterial->GetDontCastShadow()
		 || pxMaterial->IsTransparent()
		 || pxMaterial->GetSpecialFXMode() != Asura_MaterialResponse::SPECIALFX_MODE_NONE )
		{
			continue;
		}

		// Bind the vertices
		Asura_Xbox360_EnvironmentRenderer::GetVB(u).Bind();

		// Set the transparency mask from the material

		const int iTransparencyMask = pxMaterial
			? pxMaterial->GetTexture( Asura_MaterialResponse::TRANSPARENCY_MAP )
			: ASURA_TEXTURE_NONE;

		Asura_Render::CurrentStates.RequestTextureIndex( 0, iTransparencyMask );

		// Select the correct pass

		const int iNewPass =
			(iTransparencyMask == ASURA_TEXTURE_NONE ? uPASS_CREATE : uPASS_CREATE_TRANS);

		if( iNewPass == uPASS_CREATE_TRANS )
		{
			// Texture animation if using transparent pass

			Asura_TextureAnimationOutput xTexAnim;
			Asura_TextureAnimation_System::GetAnimationState( pxMaterial->GetTextureAnim(), NULL,
				&xTexAnim );

			const FXLVECTOR xVec = { xTexAnim.m_fUOffset, xTexAnim.m_fVOffset, 0.0f, 0.0f };
			g_pxCSMEffect->SetVector( g_xTexAnimHandle, xVec );
		}

		if( iNewPass != iCurrPass )
		{
			g_pxCSMEffect->BeginPass( iNewPass );
			g_pxCSMEffect->Commit();
			iCurrPass = iNewPass;
		}
		else if( iNewPass == uPASS_CREATE_TRANS )
		{
			// Always need to commit for the texture animation parameters
			g_pxCSMEffect->Commit();
		}

		// Finally we can draw

		pxDevice->DrawIndexedVertices( D3DPT_TRIANGLESTRIP, 0, xStrip.m_uStartIndex,
			xStrip.m_uNumberOfIndices );
	}
}

/******************************************************************************/
/* Asura_CSM Static Class Function                                            */
/******************************************************************************/

////////////////////////////////////////////////////////////////////////////////
//
// Asura_CSM::Platform_Initialise()
//
// Initialises the CSM system on 360
//
////////////////////////////////////////////////////////////////////////////////

void Asura_CSM::Platform_Initialise()
{
	Direct3D_Device* const pxDevice = Asura_Xbox360_Graphics::GetDevice();
	if( !pxDevice ) return;

	// Create array textures to store the shadow maps
	{
		// The first texture is D24, containing just depth

		pxDevice->CreateArrayTexture( ASURA_CSM_SIZE, ASURA_CSM_SIZE, ASURA_CSM_MAX_MAPS, 1, 0,
			D3DFMT_D24X8, D3DPOOL_DEFAULT, &g_pxDepthArrayTexture, NULL );

		// After the blurring and squaring, we'll resolve the moments back into the same memory

		g_pxMomentsArrayTexture = new Direct3D_ArrayTexture;

		XGSetArrayTextureHeader( ASURA_CSM_SIZE, ASURA_CSM_SIZE, ASURA_CSM_MAX_MAPS, 1, 0,
			D3DFMT_G16R16_SIGNED_INTEGER, D3DPOOL_DEFAULT, 0, 0, 0, g_pxMomentsArrayTexture,
			NULL, NULL );

		g_pxMomentsArrayTexture->Format.BaseAddress = g_pxDepthArrayTexture->Format.BaseAddress;

		// When sampling, convert the integer into [-1,1] range by dividing by 32768
		g_pxMomentsArrayTexture->Format.ExpAdjust = -15;
	}

	// Create render target
	{
		const D3DSURFACE_PARAMETERS& xParams =
			Asura_Xbox360_Graphics::GetSurfaceParams( Asura_Xbox360_Graphics::SP_CSM );

		pxDevice->CreateRenderTarget( ASURA_CSM_SIZE, ASURA_CSM_SIZE, D3DFMT_G16R16_EDRAM,
			D3DMULTISAMPLE_NONE, 0, FALSE, &g_pxBlurRT, &xParams );

		Asura_Assert( g_pxBlurRT, "Failed to create cascaded shadow map render target." );
	}

	// Create depth stencil surface
	{
		const D3DSURFACE_PARAMETERS& xParams =
			Asura_Xbox360_Graphics::GetSurfaceParams( Asura_Xbox360_Graphics::SP_CSM_DEPTH );

		pxDevice->CreateDepthStencilSurface( ASURA_CSM_SIZE, ASURA_CSM_SIZE, D3DFMT_D24S8,
			D3DMULTISAMPLE_NONE, 0, FALSE, &g_pxCSMDS, &xParams );

		Asura_Assert( g_pxCSMDS, "Failed to create cascaded shadow map depth-stencil surface." );
	}

	// Create effect
	{
		g_pxCSMEffect = Asura_Xbox360_Effect_System::CreateEffectFromMemory( g_auCSM );

		if( g_pxCSMEffect )
		{
			g_xShadowViewProjHandle = g_pxCSMEffect->GetParameterHandle( "g_xShadowViewProj" );
			g_xBlurSliceHandle = g_pxCSMEffect->GetParameterHandle( "g_fBlurSlice" );
			g_xShadowViewProjArrayHandle = g_pxCSMEffect->GetParameterHandle( "g_axShadowViewProj" );
			g_xInvCamViewProjHandle = g_pxCSMEffect->GetParameterHandle( "g_xInvCamViewProj" );
			g_xCameraTanHalfFOVHandle = g_pxCSMEffect->GetParameterHandle( "g_xCameraTanHalfFOV" );
			g_xSplitsHandle = g_pxCSMEffect->GetParameterHandle( "g_xSplits" );
			g_xArrayCoeffsHandle = g_pxCSMEffect->GetParameterHandle( "g_xArrayCoeffs" );
			g_xPowerHandle = g_pxCSMEffect->GetParameterHandle( "g_fPower" );
			g_xStrengthHandle = g_pxCSMEffect->GetParameterHandle( "g_afStrengths" );
			g_xTexAnimHandle = g_pxCSMEffect->GetParameterHandle( "g_xTexAnim" );

			// The following parameters never change, so set them all now

			Asura_Xbox360_Effect_System::Handle xHandle;

			const float afTwoOverScreenDims[2] =
			{
				2.0f / static_cast< float >( Asura_Viewport::GetWidth() ),
				2.0f / static_cast< float >( Asura_Viewport::GetHeight() )
			};

			xHandle = g_pxCSMEffect->GetParameterHandle( "g_xTwoOverScreenDims" );
			g_pxCSMEffect->SetVector( xHandle, afTwoOverScreenDims );

			xHandle = g_pxCSMEffect->GetParameterHandle( "g_xDepthSampler" );
			g_pxCSMEffect->SetTexture( xHandle, Asura_Xbox360_Graphics::GetDepthMtlTexture() );

			xHandle = g_pxCSMEffect->GetParameterHandle( "g_xShadowMapArray" );
			g_pxCSMEffect->SetTexture( xHandle, g_pxMomentsArrayTexture );

			xHandle = g_pxCSMEffect->GetParameterHandle( "g_xBlurSampler" );
			g_pxCSMEffect->SetTexture( xHandle, g_pxDepthArrayTexture );
		}
	}

	#if defined ASURA_DEBUG_VARIABLE_SYSTEM
		Asura_CommandConsole::AddVar( L"Render.CSM.SlopeScaleDepthBias", &g_fSlopeScaleDepthBias,
			-FLT_MAX, FLT_MAX, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );

		Asura_CommandConsole::AddVar( L"Render.CSM.DepthBias", &g_fDepthBias,
			-FLT_MAX, FLT_MAX, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );

		Asura_CommandConsole::AddVar( L"Render.CSM.Power", &g_fPower, 0.1f, 100.0f,
			Asura_CommandConsole::REMOVE_AT_SHUTDOWN );

		Asura_CommandConsole::AddVar( L"Render.CSM.EnableCull", &g_bCullStrips,
			Asura_CommandConsole::REMOVE_AT_SHUTDOWN );

		Asura_CommandConsole::AddVar( L"Render.CSM.StaggerUpdates", &g_bStaggerUpdates,
			Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
	#endif
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_CSM::Platform_Reset()
//
// Resets the CSM system on 360
//
////////////////////////////////////////////////////////////////////////////////

void Asura_CSM::Platform_Reset()
{
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_CSM::Platform_Shutdown()
//
// Shuts the CSM system down on 360
//
////////////////////////////////////////////////////////////////////////////////

void Asura_CSM::Platform_Shutdown()
{
	Asura_Xbox360_Effect_System::Destroy( g_pxCSMEffect );
	g_pxCSMEffect = NULL;

	SAFE_RELEASE( g_pxCSMDS );
	SAFE_RELEASE( g_pxBlurRT );

	SAFE_RELEASE( g_pxMomentsArrayTexture );
	SAFE_RELEASE( g_pxDepthArrayTexture );
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_CSM::Platform_CreateShadowMaps()
//
// Constructs cascaded shadow maps on 360, based on camera 0 pos & ori
//
////////////////////////////////////////////////////////////////////////////////

void Asura_CSM::Platform_CreateShadowMaps()
{
	SCOPEDPIXEVENT(( 0, "Create Cascaded Shadow Maps" ));

	Direct3D_Device* const pxDevice = Asura_Xbox360_Graphics::GetDevice();
	if( !pxDevice || !g_pxCSMEffect ) return;

	Direct3D_Surface* pxOldRT = NULL;
	Direct3D_Surface* pxOldDepthStencil = NULL;

	// Set up
	{
		// Ensure the shadow state is valid
		Asura_Render::CurrentStates.RequestCullMode( ASURA_CULLMODE_NONE );

		Asura_Render::CurrentStates.RequestAlphaTest( true );
		Asura_Render::CurrentStates.RequestCullMode( ASURA_CULLMODE_CW );
		Asura_Render::CurrentStates.RequestDitheringMode( false );
		Asura_Render::CurrentStates.RequestDynamicLights( false );
		Asura_Render::CurrentStates.RequestStencilEnabled( false );
		Asura_Render::CurrentStates.RequestTranslucencyMode( ASURA_TRANSLUCENCY_OFF );
		Asura_Render::CurrentStates.RequestZBufferCompareMode( ASURA_ZBUFFERCMP_LESSEQUAL );
		Asura_Render::CurrentStates.RequestZBufferEnabled( true );
		Asura_Render::CurrentStates.RequestZBufferWriteEnabled( true );

		pxDevice->GetRenderTarget( 0, &pxOldRT );
		pxDevice->GetDepthStencilSurface( &pxOldDepthStencil );

		pxDevice->SetRenderTarget( 0, NULL );
		pxDevice->SetDepthStencilSurface( g_pxCSMDS );

		g_pxCSMEffect->SetVector( g_xSplitsHandle, &s_afSplits[1] );

		g_pxCSMEffect->Begin( 0, uPASS_CREATE );

		const DWORD uSSDB = *reinterpret_cast< const DWORD* >( &g_fSlopeScaleDepthBias );
		pxDevice->SetRenderState_Inline( D3DRS_SLOPESCALEDEPTHBIAS, uSSDB );

		const DWORD uDB = *reinterpret_cast< const DWORD* >( &g_fDepthBias );
		pxDevice->SetRenderState_Inline( D3DRS_DEPTHBIAS, uDB );
	}

	// Create each cascaded shadow texture. This step just writes depth

	for( u_int u = 0; u < s_uNumMaps; ++u )
	{
		if( !UpdateThisFrame(u) ) continue;

		SCOPEDPIXEVENT(( 0, "Map %u", u ));

		// Hardware can't clear HiZ on resolve, so we have to do it separately
		pxDevice->Clear( 0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0) , 1.0f, 0 );

		CreateViewMatrix( g_axCSMMatrices[u], s_axShadowViewMatrixTrans[u],
			s_axShadowViewMatrixOrients[u] );

		D3DXMATRIX xProj;
		const float fDiameter = s_afBSRadii[u] * 2.0f;

		// Push the near plane back out to catch shadow casters that aren't in frustum
		D3DXMatrixOrthoLH( &xProj, fDiameter, fDiameter, -s_fExtraFrustumDepth, fDiameter );

		g_axCSMMatrices[u] *= xProj;

		// Find out where the origin will project to in the shadow map
		D3DXVECTOR4 xOrigin( 0.0f, 0.0f, 0.0f, 1.0f );
		D3DXVec4Transform( &xOrigin, &xOrigin, &g_axCSMMatrices[u] );

		float fOriginX = (xOrigin.x * 0.5f) * ASURA_CSM_SIZE;
		float fOriginY = (xOrigin.y * 0.5f) * ASURA_CSM_SIZE;

		float fPixelDiffX = floorf( fOriginX ) - fOriginX;
		float fPixelDiffY = floorf( fOriginY ) - fOriginY;

		float fRoundingX = (fPixelDiffX / ASURA_CSM_SIZE) * 2.0f;
		float fRoundingY = (fPixelDiffY / ASURA_CSM_SIZE) * 2.0f;

		D3DXMATRIX xRounding;
		D3DXMatrixTranslation( &xRounding, fRoundingX, fRoundingY, 0.0f );

		g_axCSMMatrices[u] *= xRounding;

		g_pxCSMEffect->SetMatrix( g_xShadowViewProjHandle, g_axCSMMatrices[u] );

		RenderGeometry( s_axBSCentres[u], s_afBSRadii[u], s_fExtraFrustumDepth );

		pxDevice->Resolve( D3DRESOLVE_DEPTHSTENCIL, NULL, g_pxDepthArrayTexture, NULL, 0, u,
			NULL, 1.0f, 0, NULL );
	}

	// Calculate the squares of the depths, and gaussian blur
	Blur( s_uNumMaps );

	// Clean up
	{
		pxDevice->SetRenderState_Inline( D3DRS_SLOPESCALEDEPTHBIAS, 0 );
		pxDevice->SetRenderState_Inline( D3DRS_DEPTHBIAS, 0 );

		g_pxCSMEffect->End();

		pxDevice->SetRenderTarget( 0, pxOldRT );
		pxDevice->SetDepthStencilSurface( pxOldDepthStencil );

		SAFE_RELEASE( pxOldRT );
		SAFE_RELEASE( pxOldDepthStencil );
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_CSM::Platform_ApplyToScene()
//
// Applies CSM data to the scene on 360, as a darkening pass
//
////////////////////////////////////////////////////////////////////////////////

void Asura_CSM::Platform_ApplyToScene()
{
	SCOPEDPIXEVENT(( 0, "Apply Cascaded Shadow Maps" ));

	Direct3D_Device* const pxDevice = Asura_Xbox360_Graphics::GetDevice();
	if( !pxDevice || !g_pxCSMEffect ) return;

	DWORD uOldOffset;

	// Set up
	{
		Asura_Render::CurrentStates.RequestAlphaTest( false );
		Asura_Render::CurrentStates.RequestCullMode( ASURA_CULLMODE_CW );
		Asura_Render::CurrentStates.RequestDitheringMode( false );
		Asura_Render::CurrentStates.RequestDynamicLights( false );
		Asura_Render::CurrentStates.RequestStencilEnabled( false );
		Asura_Render::CurrentStates.RequestTranslucencyMode( ASURA_TRANSLUCENCY_NORMAL);
		Asura_Render::CurrentStates.RequestZBufferEnabled( false );

		pxDevice->SetVertexDeclaration( Asura_Xbox360_Graphics::GetFullScreenVD() );
		Asura_Xbox360_Graphics::GetFullScreenVB().Bind();

		{
			const D3DXMATRIX& xCamViewProj = Asura_Xbox360_Graphics::GetViewMatrix();

			D3DXMATRIX xInvCamViewProj;
			D3DXMatrixInverse( &xInvCamViewProj, NULL, &xCamViewProj );

			g_pxCSMEffect->SetMatrix( g_xInvCamViewProjHandle, xInvCamViewProj );
		}

		{
			const float afTanHalfFOV[2] =
			{
				1.0f / Asura_Camera::GetOneOverTanHalfFovX(),
				1.0f / Asura_Camera::GetOneOverTanHalfFovY(),
			};

			g_pxCSMEffect->SetVector( g_xCameraTanHalfFOVHandle, afTanHalfFOV );
		}

		{
			// See "Array Textures" XDK doc page
			const float afArrayCoeffs[2] =
				{ 1.0f / (2.0f * ASURA_CSM_MAX_MAPS), 1.0f / ASURA_CSM_MAX_MAPS };

			g_pxCSMEffect->SetVector( g_xArrayCoeffsHandle, afArrayCoeffs );
		}

		g_pxCSMEffect->GetInterface()->SetMatrixArrayF( g_xShadowViewProjArrayHandle,
			g_axCSMMatrices[0], 4 );

		g_pxCSMEffect->SetFloat( g_xPowerHandle, g_fPower );
		g_pxCSMEffect->GetInterface()->SetScalarArrayF( g_xStrengthHandle, &s_afStrengths[0], 4 );

		g_pxCSMEffect->Begin( 0, uPASS_APPLY );

		// Use half-pixel offset render state to make shaders easier to read
		pxDevice->GetRenderState( D3DRS_HALFPIXELOFFSET, &uOldOffset );
		pxDevice->SetRenderState_Inline( D3DRS_HALFPIXELOFFSET, TRUE );
	}

	// Apply to scene. Complications arise from predicated tiling
	for( int i = 0; i < Asura_Xbox360_Graphics::GetNumberOfPredicatedTilePasses(); ++i )
	{
		pxDevice->SetPredication( D3DPRED_TILE(i) );
		Asura_Xbox360_Graphics::CorrectVPOSForTiling(i);
		g_pxCSMEffect->Commit();

		pxDevice->DrawVertices( D3DPT_RECTLIST, 0, 3 );
	}

	// Clean up
	{
		pxDevice->SetPredication(0);

		g_pxCSMEffect->End();

		pxDevice->SetRenderState_Inline( D3DRS_HALFPIXELOFFSET, uOldOffset );
	}
}

#endif // defined ASURA_USE_CSM
