//////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_SkinRenderer.cpp
//
// Copyright (c) 2009 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

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

#include "Core/Asura.h"

#include "Rendering/Asura_Xbox360_SkinRenderer.h"

// Asura Platform-independent
#include "Animation/Asura_Animation.h"
#include "Animation/Asura_Hierarchy.h"
#include "Camera/Asura_Camera.h"
#include "Core/Memory/Asura_Mem.h"
#include "Core/Resources/Asura_ResourceSet.h"
#include "Core/Asura_MaterialResponse_System.h"
#include "Core/Asura_Maths.h"
#include "Debugging/Asura_Assert.h"
#include "Interface/Asura_CommandConsole.h"
#include "Light/Asura_GI_Solution.h"
#include "Rendering/Asura_Fog.h"
#include "Rendering/Asura_Render.h"
#include "Rendering/Asura_SortedRender.h"
#include "Shadows/Asura_Shadow.h"

// Asura Xbox360
#include "Core/Asura_Platform_MemoryManagement.h"
#include "Debugging/Asura_Platform_GPUProfile.h"
#include "Rendering/Asura_Xbox360_Fog.h"
#include "Rendering/Asura_Xbox360_Graphics.h"
#include "Rendering/Asura_Xbox360_MaterialResponse_System.h"

// Shaders
#include "Skin_VS.h"
#include "Skin_Shadow_VS.h"

// Misc
#include <xgraphics.h>
#include <xtl.h>

/******************************************************************************/
/* Asura_Xbox360_SkinRenderer Static Class Data                               */
/******************************************************************************/

// Must match below vertex declaration, stream 0 only
const u_int Asura_Xbox360_SkinRenderer::uVERTEX_SIZE = 40;

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

// Remember to update Asura_Xbox360_SkinRenderer::uVERTEX_SIZE if this changes
const Direct3D_VertexElement axVD[] =
{
	// Mesh vertices themselves in first stream
	{ 0,  0, D3DDECLTYPE_FLOAT16_4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION,     0 },
	{ 0,  8, D3DDECLTYPE_UBYTE4,    D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BLENDINDICES, 0 },
	{ 0, 12, D3DDECLTYPE_UBYTE4N,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BLENDWEIGHT,  0 },
	{ 0, 16, D3DDECLTYPE_DEC3N,     D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TANGENT,      0 },
	{ 0, 20, D3DDECLTYPE_DEC3N,     D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BINORMAL,     0 },
	{ 0, 24, D3DDECLTYPE_DEC3N,     D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,       0 },
	{ 0, 28, D3DDECLTYPE_FLOAT16_4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,     0 },
	{ 0, 36, D3DDECLTYPE_FLOAT1,    D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,     1 },

	// Bones stored in secondary vertex stream. Update uBONE_SIZE if this changes
	{ 1,  0, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 1 },
	{ 1, 16, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 2 },
	{ 1, 32, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 3 },

	D3DDECL_END()
};

const u_int uBONE_SIZE = 48;

ASURA_STATIC_ASSERT( Asura_Xbox360_SkinRenderer::uVERTEX_SIZE == 40 );

#if !defined ASURA_XBOX360_BONE_BUFFER_SIZE
	#define ASURA_XBOX360_BONE_BUFFER_SIZE (1 * 1024 * 1024)
#endif

// Physical allocs always multiples of 4 KB, so no point using anything else
ASURA_STATIC_ASSERT( ASURA_XBOX360_BONE_BUFFER_SIZE % 4096 == 0 );

// Simple PS for shadowmap creation
const char szSHADOW_PS[] =
	"float4 main( const float fDepth : TEXCOORD ) : COLOR { return fDepth; }";

const u_int uNUM_BONE_BUFFERS = 2;

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

static Direct3D_VertexShader* g_pxMainVS = NULL;
static Direct3D_VertexShader* g_pxShadowVS = NULL;
static Direct3D_PixelShader* g_pxShadowPS = NULL;

static Direct3D_VertexDeclaration* g_pxVD = NULL;

static Asura_ResourceSet< Asura_Xbox360_Skin > g_xSkins( "360Skin" );

static u_char* g_apucBoneBuffers[ uNUM_BONE_BUFFERS ] = {0};
static Direct3D_VertexBuffer g_axBoneVBs[ uNUM_BONE_BUFFERS ];
static u_int g_uBoneOffset = 0;
static u_int g_uCurrBoneBuffer = 0;

static u_int g_uNumRendered = 0;

static bool g_bOwnsRegisters = false;

// This can be removed when the old renderers are deleted
#if defined ASURA_USE_NEW_SKIN_RENDERERS
	DVSVAR bool dvs_bEnabled = true;
#else
	DVSVAR bool dvs_bEnabled = false;
#endif

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

////////////////////////////////////////////////////////////////////////////////
//
// IsStripVisible()
//
// Helper function for checking whether strips are visible in this render pass
//
////////////////////////////////////////////////////////////////////////////////

static bool IsStripVisible( const Asura_Animation& xAnim, const int iElement,
	const Asura_MaterialResponse* const pxMaterial, const bool bDepthPass, const bool bShadow )
{
	// Check skin element isn't hidden
	#if defined ASURA_SUPPORT_SKIN_ELEMENTS
		if( !xAnim.GetSkinElementVisibility( iElement ) ) return false;
	#endif

	if( pxMaterial )
	{
		// Check if material is hidden
		if( bShadow && pxMaterial->GetDontCastShadow() ) return false;
		if( !bShadow && pxMaterial->GetDontRender() ) return false;

		// Check we're in the right transparency pass (not applicable for shadows)
		if( !bShadow && (pxMaterial->IsTransparent() != Asura_SortedRender::IsTransparentPass()) )
		{
			return false;
		}

		// Check we're in the right depth pass
		if( bDepthPass && !pxMaterial->GetZPrePass() ) return false;
	}

	return true;
}

////////////////////////////////////////////////////////////////////////////////
//
// SetMatrices()
//
// Uploads the world, world-view-proj, camera pos etc. to GPU registers
//
////////////////////////////////////////////////////////////////////////////////

static void SetMatrices( Asura_AMatrix4x4_InParam xWorldMat, const Asura_Shadow* const pxShadow )
{
	Direct3D_Device* const pxDevice = Asura_Xbox360_Graphics::GetDevice();

	D3DXMATRIX xWorldViewProj;
	xWorldMat.StoreColsUnaligned( &xWorldViewProj._11 );

	D3DVECTOR4* pxConstants = NULL;
	const HRESULT xResult = pxDevice->GpuBeginVertexShaderConstantF4( 4, &pxConstants, 12 );

	Asura_Assert( pxConstants, "Failed to begin vertex shader constants for skin." );

	if( SUCCEEDED( xResult ) && pxConstants )
	{
		// Must use SafeCopy() as destination is write-combined memory

		if( pxShadow )
		{
			// Shadow frustum first
			const float fNear = pxShadow->GetFrustum().GetNearPlane();
			const float fFar = pxShadow->GetFrustum().GetFarPlane();
			pxConstants[0].x = fNear;
			pxConstants[0].y = 1.0f / (fFar - fNear);

			// Light position
			const Asura_Vector_3& xLightPos = pxShadow->GetLightPosition();
			D3DXMATRIX xShadowMat;
			D3DXMatrixTranslation( &xShadowMat, -xLightPos.x, -xLightPos.y, -xLightPos.z );
			xWorldViewProj *= xShadowMat;

			// Light Orientation
			Asura_Matrix_3x3 xLightOri;
			pxShadow->GetLightOrientation( xLightOri );
			Asura_Mem::Set( &xShadowMat, 0, sizeof( D3DXMATRIX ) );

			for( u_int u = 0; u < 3; ++u )
			{
				for( u_int v = 0; v < 3; ++v )
				{
					xShadowMat.m[u][v] = xLightOri.Element[u][v];

					if( v == 1 )
					{
						// Account for y-axis being inverted between D3D and Asura
						xShadowMat.m[u][v] = -xShadowMat.m[u][v];
					}
				}
			}

			xShadowMat._44 = 1.0f;
			xWorldViewProj *= xShadowMat;

			// Projection
			const float fVertFOV =
				Asura_Maths::DegToRad( pxShadow->GetFrustum().GetVerticalFOVInDegrees() );

			D3DXMatrixPerspectiveFovLH( &xShadowMat, fVertFOV, 1.0f, fNear, fFar );
			xWorldViewProj *= xShadowMat;

			Asura_Mem::SafeCopy( &pxConstants[4], &xWorldViewProj, sizeof( D3DXMATRIX ) );
		}
		else
		{
			// World matrix first
			Asura_Mem::SafeCopy( &pxConstants[0], &xWorldViewProj, sizeof( D3DXMATRIX ) );

			// Then world-view-proj
			xWorldViewProj *= Asura_Xbox360_Graphics::GetViewProjectionMatrix();
			Asura_Mem::SafeCopy( &pxConstants[4], &xWorldViewProj, sizeof( D3DXMATRIX ) );

			// Camera position in world space
			pxConstants[8].x = Asura_Camera::GetPosition()->x;
			pxConstants[8].y = Asura_Camera::GetPosition()->y;
			pxConstants[8].z = Asura_Camera::GetPosition()->z;
			pxConstants[8].w = 1.0f;
		}

		pxDevice->GpuEndVertexShaderConstantF4();
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// SetFog()
//
// Sets up fogging for transparent strips
//
////////////////////////////////////////////////////////////////////////////////

static void SetFog()
{
	Direct3D_Device* const pxDevice = Asura_Xbox360_Graphics::GetDevice();

	const BOOL bFogEnabled = Asura_Fog::IsFogEnabled();
	pxDevice->SetPixelShaderConstantB( 0, &bFogEnabled, 1 );

	// Constants must be set in batches of four using this method, so don't put anything directly
	// after the fog params unless we can set it here
	D3DVECTOR4* pxConstants = NULL;
	pxDevice->GpuBeginPixelShaderConstantF4( 8, &pxConstants, 4 );

	if( pxConstants )
	{
		pxConstants[0].x = Asura_Fog::GetFogNearPlane();
		pxConstants[0].y = Asura_Fog::GetOneOverFogPlaneSeparation();

		pxDevice->GpuEndPixelShaderConstantF4();
	}

	pxDevice->SetTexture( 1, Asura_Xbox360_Fog::GetRenderTexture() );

	// Actual fog colour is set per strip, since additive strips need to always fog to black
}

////////////////////////////////////////////////////////////////////////////////
//
// SetSH()
//
// Helper function for setting SH-related shader constants
//
////////////////////////////////////////////////////////////////////////////////

static void SetSH( const Asura_MaterialResponse* const pxMaterial )
{
	Direct3D_Device* const pxDevice = Asura_Xbox360_Graphics::GetDevice();

	D3DVECTOR4* pxConstants = NULL;
	pxDevice->GpuBeginPixelShaderConstantF4( 128, &pxConstants, 8 );

	if( pxConstants )
	{
		Asura_SphericalHarmonics_Order2_RGB xSH;

		if( pxMaterial )
		{
			// Set SH lighting by considering static and dynamic components separately

			xSH = Asura_GI_Solution::s_xStaticSHForCurrentRender;

			Asura_RGBA xColour = pxMaterial->GetAmbientColour();

			const float fPrelightingColourise = pxMaterial->GetPrelightingColourise();
			xSH.Colourise( xColour.m_fR, xColour.m_fG, xColour.m_fB, fPrelightingColourise );

			const float fAmbientContribution = pxMaterial->GetAmbientContribution();
			xSH.Scale( fAmbientContribution );

			if( pxMaterial->IsTransparent() )
			{	
				xSH += Asura_GI_Solution::s_xDynamicSHForCurrentRender;
			}
		}
		else
		{
			// send through basic SH
			xSH = Asura_GI_Solution::s_xSHForCurrentRender;
		}

		for( u_int u = 0; u < 3; ++u )
		{
			// Need to swizzle because the constant term is stored first in the struct
			pxConstants[u].x = xSH[ u*4 + 1 ];
			pxConstants[u].y = xSH[ u*4 + 2 ];
			pxConstants[u].z = xSH[ u*4 + 3 ];
			pxConstants[u].w = xSH[ u*4 + 0 ];
		}

		// If rendering transparent strips, need to know the brightest light direction & colour
		if( pxMaterial && pxMaterial->IsTransparent() )
		{
			Asura_Vector_3 xLightDirection;
			xSH.GetBrightestDirection( xLightDirection );

			if( xLightDirection.ApproximatelyEquals( Asura_Maths::ZeroVector3 ) )
			{
				// If there are no lights, we'll get a zero direction. Not much we can do here,
				// might as well consider the light as coming from above
				xLightDirection.y = -1.0f;
			}

			pxConstants[3].x = xLightDirection.x;
			pxConstants[3].y = xLightDirection.y;
			pxConstants[3].z = xLightDirection.z;
			pxConstants[3].w = 0.0f;

			// Evaluate the colour in that direction
			Asura_RGBA xLightColour;
			xSH.EvaluateInDirection( xLightDirection, xLightColour );

			// Use the same method to calculate light brightness as the photon shaders. Should
			// probably use standard luminance calculation instead
			const float fLightBrightness =
				xLightColour.m_fR / 3.0f + xLightColour.m_fG / 3.0f + xLightColour.m_fB / 3.0f;

			pxConstants[4].x = xLightColour.m_fR;
			pxConstants[4].y = xLightColour.m_fG;
			pxConstants[4].z = xLightColour.m_fB;
			pxConstants[4].w = fLightBrightness;

			// Fog colour is set per strip, since additive materials need to always fog to black
			if( pxMaterial->GetBlendMode() == Asura_MaterialResponse::BLEND_MODE_ADDITIVE )
			{
				pxConstants[5].x = pxConstants[5].y = pxConstants[5].z = pxConstants[5].w = 0.0f;
			}
			else
			{
				pxConstants[5].x = Asura_Fog::GetFogColour().m_fR;
				pxConstants[5].y = Asura_Fog::GetFogColour().m_fG;
				pxConstants[5].z = Asura_Fog::GetFogColour().m_fB;
				pxConstants[5].w = Asura_Fog::GetFogColour().m_fA;
			}
		}

		pxDevice->GpuEndPixelShaderConstantF4();
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// RenderStrip()
//
// Helper function for rendering a single strip from a skin
//
////////////////////////////////////////////////////////////////////////////////

static void RenderStrip( const Asura_Xbox360_Skin& xSkin, const u_int uStrip,
	const Asura_Animation& xAnim, const u_int uStartIndex, const bool bDepthPass,
	const bool bShadow )
{
	Direct3D_Device* const pxDevice = Asura_Xbox360_Graphics::GetDevice();

	const Asura_Xbox360_SkinStrip& xStrip = xSkin.GetStrip( uStrip );

	const Asura_MaterialResponse* const pxMaterial1 =
		Asura_MaterialResponse_System::GetMaterialByHashID( xStrip.m_auMaterials[0] );

	// Skip strip if appropriate
	if( !IsStripVisible( xAnim, xStrip.m_iElementID, pxMaterial1, bDepthPass, bShadow ) ) return;

	SCOPEDPIXEVENT(( 0, "Strip %u", uStrip ));

	// Number of bone weights per vertex
	{
		// See SetVertexShaderConstantI() docs for loop register format
		const int aiLoops[4] = { xStrip.m_uNumWeights, 0, 1, 0 };
		pxDevice->SetVertexShaderConstantI( 0, aiLoops, 1 );
	}

	if( !bDepthPass && !bShadow )
	{
		const Asura_MaterialResponse* const pxMaterial2 =
			Asura_MaterialResponse_System::GetMaterialByHashID( xStrip.m_auMaterials[1] );

		const Asura_Hash_ID uCombinedHash = Asura_MaterialResponse_System::CombineShaderHashes(
			pxMaterial1, pxMaterial2, true );

		Direct3D_PixelShader* const pxPS =
			Asura_Xbox360_MaterialResponse_System::GetPixelShader( uCombinedHash );

		pxDevice->SetPixelShader( pxPS );

		// Re-acquire regs in case assert fired in GetPixelShader(). Need better way of doing this
		Asura_Xbox360_SkinRenderer::OwnRegisters();

		Asura_Xbox360_MaterialResponse_System::SetMaterialConstants( pxMaterial1, pxMaterial2,
			xStrip.m_auMaterials[0], &xAnim );

		SetSH( pxMaterial1 );
		Asura_Xbox360_MaterialResponse_System::SetBlendStates( pxMaterial1 );

		Asura_Render::CurrentStates.RequestAlphaTest( true );

		if( Asura_MaterialResponse_System::GetMaterialProjectContextCallback() )
		{
			const u_int uContext1 = pxMaterial1
				? pxMaterial1->GetProjectContext()
				: ASURA_MATERIAL_RESPONSE_PROJECT_CONTEXT_INVALID;

			const u_int uContext2 = pxMaterial2
				? pxMaterial2->GetProjectContext()
				: ASURA_MATERIAL_RESPONSE_PROJECT_CONTEXT_INVALID;

			Asura_MaterialResponse_System::GetMaterialProjectContextCallback()( uContext1,
				uContext2 );
		}
	}
	else
	{
		Asura_Render::CurrentStates.RequestAlphaTest( false );
	}

	pxDevice->DrawIndexedVertices( D3DPT_TRIANGLESTRIP, 0, uStartIndex, xStrip.m_uNumIndices );
}

/******************************************************************************/
/* Asura_Xbox360_SkinRenderer Static Class Functions                          */
/******************************************************************************/

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_SkinRenderer::GetSkin()
//
// Retrieves rendering data for a skin by hash ID
//
////////////////////////////////////////////////////////////////////////////////

const Asura_Xbox360_Skin* Asura_Xbox360_SkinRenderer::GetSkin( const Asura_Hash_ID uHash )
{
	return g_xSkins.GetResource( uHash );
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_SkinRenderer::GetVertexDeclaration()
//
// Retrieves the skin renderer vertex declaration for external use
//
////////////////////////////////////////////////////////////////////////////////

Direct3D_VertexDeclaration* Asura_Xbox360_SkinRenderer::GetVertexDeclaration()
{
	return g_pxVD;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_SkinRenderer::Initialise()
//
// Initialises the 360 skin renderer. Called at app startup
//
////////////////////////////////////////////////////////////////////////////////

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

	// Create vertex shaders

	pxDevice->CreateVertexShader( g_auSkin_VS, &g_pxMainVS );
	pxDevice->CreateVertexShader( g_auSkin_Shadow_VS, &g_pxShadowVS );
	Asura_Assert( g_pxMainVS && g_pxShadowVS, "Failed to create skin vertex shader." );

	// Create shadowmap pixel shader

	ID3DXBuffer* pxBuffer = NULL;
	D3DXCompileShader( szSHADOW_PS, sizeof( szSHADOW_PS ), NULL, NULL, "main", "ps_3_0", 0,
		&pxBuffer, NULL, NULL );

	Asura_Assert( pxBuffer, "Failed to compile skin shadow pixel shader." );
	if( pxBuffer )
	{
		const DWORD* const puFunction = static_cast< const DWORD* >( pxBuffer->GetBufferPointer() );
		pxDevice->CreatePixelShader( puFunction, &g_pxShadowPS );
		Asura_Assert( g_pxShadowPS, "Failed to create skin shadow pixel shader." );

		SAFE_RELEASE( pxBuffer );
	}

	// Bind to vertex declaration

	pxDevice->CreateVertexDeclaration( axVD, &g_pxVD );

	Asura_Assert( g_pxVD, "Failed to create skin vertex declaration." );

	const DWORD auStrides[2] = { uVERTEX_SIZE, uBONE_SIZE };
	g_pxMainVS->Bind( 0, g_pxVD, auStrides, NULL );
	g_pxShadowVS->Bind( 0, g_pxVD, auStrides, g_pxShadowPS );

	// Allocate a vertex buffer for bone matrices. Use double buffering

	for( u_int u = 0; u < uNUM_BONE_BUFFERS; ++u )
	{
		void* const pMem = Asura_Xbox360_MemoryManagement::AllocatePhysical(
			Asura_Xbox360_MemoryManagement::MT_READ_WRITE_COMBINE, ASURA_XBOX360_BONE_BUFFER_SIZE,
			XALLOC_PHYSICAL_ALIGNMENT_DEFAULT );

		Asura_Assert( pMem, "Failed to allocate bone buffer." );

		XGSetVertexBufferHeader( ASURA_XBOX360_BONE_BUFFER_SIZE, 0, D3DPOOL_DEFAULT, 0,
			&g_axBoneVBs[u] );

		XGOffsetResourceAddress( &g_axBoneVBs[u], pMem );

		g_apucBoneBuffers[u] = static_cast< u_char* >( pMem );
	}

	// Set up toggle DVS var. This can be removed when the old renderers are deleted

	#if defined ASURA_DEBUG_VARIABLE_SYSTEM
		Asura_CommandConsole::AddVar( L"Render.UseNewSkins", &dvs_bEnabled,
			Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
	#endif
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_SkinRenderer::Reset()
//
// Resets the 360 skin renderer. Typically called between levels
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_SkinRenderer::Reset()
{
	// Nothing to do here. Skins will be destroyed when the project clears the resource bank
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_SkinRenderer::Shutdown()
//
// Shuts down the 360 skin renderer. Probably never actually called, since we
// don't really quit on Xbox 360
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_SkinRenderer::Shutdown()
{
	for( u_int u = 0; u < uNUM_BONE_BUFFERS; ++u )
	{
		Asura_Xbox360_MemoryManagement::FreePhysical(
			Asura_Xbox360_MemoryManagement::MT_READ_WRITE_COMBINE, g_apucBoneBuffers[u],
			XALLOC_ALIGNMENT_DEFAULT );

		g_apucBoneBuffers[u] = NULL;
	}

	SAFE_RELEASE( g_pxMainVS );
	SAFE_RELEASE( g_pxShadowVS );
	SAFE_RELEASE( g_pxVD );
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_SkinRenderer::IsEnabled()
//
// Queries whether the new skin renderer or old ones should be used. This can be
// removed when the old renderers are deleted
//
////////////////////////////////////////////////////////////////////////////////

bool Asura_Xbox360_SkinRenderer::IsEnabled()
{
	return dvs_bEnabled;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_SkinRenderer::ResetCount()
//
// Resets the rendered skin count
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_SkinRenderer::ResetCount()
{
	g_uNumRendered = 0;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_SkinRenderer::ResetBoneBuffer()
//
// Resets the bone buffer pointer back to the beginning
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_SkinRenderer::ResetBoneBuffer()
{
	// Switch to the other buffer for the next frame, while the GPU uses the previous one
	g_uCurrBoneBuffer = (g_uCurrBoneBuffer + 1) % uNUM_BONE_BUFFERS;
	g_uBoneOffset = 0;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_SkinRenderer::OwnRegisters()
//
// Takes ownership of the registers needed for skin rendering from D3D. Called
// when the sorted render starts, and also from project skin rendering hooks
// which have modifed ownership for their own needs
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_SkinRenderer::OwnRegisters()
{
	if( !g_bOwnsRegisters )
	{
		Asura_Xbox360_Graphics::GetDevice()->GpuOwnVertexShaderConstantF( 4, 12 );
		Asura_Xbox360_Graphics::GetDevice()->GpuOwnPixelShaderConstantF( 0, 12 );
		Asura_Xbox360_Graphics::GetDevice()->GpuOwnPixelShaderConstantF( 128, 8 );

		g_bOwnsRegisters = true;
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_SkinRenderer::DisownRegisters()
//
// Disowns the GPU registers needed for skin rendering and resets the flag
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_SkinRenderer::DisownRegisters()
{
	Asura_Xbox360_Graphics::GetDevice()->GpuDisownAll();
	g_bOwnsRegisters = false;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_SkinRenderer::CreateSkin()
//
// Allocates a new 360 render skin and adds it to the current resource bank.
// Passes back a bool stating whether a skin with that name already existed
//
////////////////////////////////////////////////////////////////////////////////

Asura_Xbox360_Skin* Asura_Xbox360_SkinRenderer::CreateSkin( const Asura_Hash_ID uHash,
	bool& bAlreadyExisted )
{
	bAlreadyExisted = false;

	if( g_xSkins.GetResource( uHash ) )
	{
		bAlreadyExisted = true;
		return NULL;
	}

	Asura_Xbox360_Skin* const pxSkin = new Asura_Xbox360_Skin;
	if( !pxSkin ) return NULL;

	if( !g_xSkins.AddResource( uHash, *pxSkin ) )
	{
		delete pxSkin;
		return NULL;
	}

	return pxSkin;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_SkinRenderer::RenderSkin()
//
// Renders a skin on 360. Used for both normal rendering and shadowmap creation
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_SkinRenderer::RenderSkin( const Asura_Animation& xAnimation,
	const Asura_Hierarchy_Skin& xHierarchySkin, Asura_AMatrix4x4_InParam xWorldMat,
	const Asura_AMatrix4x4* const pxBoneMats, const Asura_Shadow* const pxShadow /*= NULL*/ )
{
	const Asura_Xbox360_Skin* const pxSkin = g_xSkins.GetResource( xHierarchySkin.GetHashID() );

	Asura_Assertf( pxSkin, ("Failed to find mesh for skin '%s'.", xHierarchySkin.GetName()) );
	if( !pxSkin ) return;

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

	SCOPEDPIXEVENT(( 0, "Skin %u - '%s'", ++g_uNumRendered, xHierarchySkin.GetName() ));
	ASURA_GPU_PROFILE_START( ASURA_GPUPROFILE_SKINS );

	// Set up
	{
		OwnRegisters(); // Re-acquire ownership in case an assert fired after starting entities

		// Blending and z-write states set up per strip, others here
		Asura_Render::CurrentStates.RequestCullMode( ASURA_CULLMODE_CW );
		Asura_Render::CurrentStates.RequestStencilEnabled( false );
		Asura_Render::CurrentStates.RequestZBufferEnabled( true );
		Asura_Render::CurrentStates.RequestZBufferCompareMode( ASURA_ZBUFFERCMP_LESSEQUAL );

		pxDevice->SetVertexDeclaration( NULL ); // VD already bound to VS

		pxDevice->SetVertexShader( pxShadow ? g_pxShadowVS : g_pxMainVS );

		if( pxShadow )
		{
			pxDevice->SetPixelShader( g_pxShadowPS );
			Asura_Render::CurrentStates.RequestTranslucencyMode( ASURA_TRANSLUCENCY_OFF );
			Asura_Render::CurrentStates.RequestZBufferWriteEnabled( true );
		}

		pxSkin->GetIB().Bind();
		pxSkin->GetVB().Bind();

		SetMatrices( xWorldMat, pxShadow );

		// Bone matrices
		if( !CopyBones( pxBoneMats, xHierarchySkin.NumberOfBones, xAnimation ) )
		{
			ASURA_GPU_PROFILE_END( ASURA_GPUPROFILE_SKINS );
			return;
		}

		if( Asura_SortedRender::IsTransparentPass() )
		{
			SetFog();
		}

		// Clamp the materials and fog textures, but wrap the artist-selected ones

		for( u_int u = 0; u < 2; ++u )
		{
			pxDevice->SetSamplerState_Inline( u, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP );
			pxDevice->SetSamplerState_Inline( u, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP );
		}

		for( u_int u = 2; u <= 19; ++u )
		{
			pxDevice->SetSamplerState_Inline( u, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP );
			pxDevice->SetSamplerState_Inline( u, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP );
		}
	}

	const u_int uNumStrips = pxSkin->GetNumStrips();
	const bool bShadow = (pxShadow != NULL);
	const bool bDoDepthPass = Asura_SortedRender::IsTransparentPass() && !bShadow;

	// Would prefer to do depth prepass using the D3D's BeginZPass(), but that doesn't work with
	// stencil buffering which projects might be setting up through context callbacks

	for( u_int u = (bDoDepthPass ? 0 : 1); u < 2; ++u )
	{
		const bool bIsDepthPass = (u == 0);

		if( bDoDepthPass )
		{
			// Don't bother with the PIX event if we're only doing one pass
			PIXBeginNamedEvent( 0, (bIsDepthPass ? "Depth Pass" : "Colour Pass") );

			if( bIsDepthPass )
			{
				pxDevice->SetPixelShader( NULL );
				Asura_Render::CurrentStates.RequestZBufferWriteEnabled( true );
			}
		}

		u_int uStartIndex = 0;

		for( u_int v = 0; v < uNumStrips; ++v )
		{
			RenderStrip( *pxSkin, v, xAnimation, uStartIndex, bIsDepthPass, bShadow );
			uStartIndex += pxSkin->GetStrip(v).m_uNumIndices;
		}

		if( bDoDepthPass )
		{
			PIXEndNamedEvent(); // Depth Pass / Colour Pass
		}
	}

	// Cleanup
	{
		if( pxShadow )
		{
			Asura_Render::AddToTriPassesRendered( pxSkin->GetNumTriangles() );
		}
		else
		{
			Asura_Render::AddToTrianglesRendered( pxSkin->GetNumTriangles() );
		}

		// Many renderers don't request this, so turn it back on
		Asura_Render::CurrentStates.RequestZBufferWriteEnabled( true );

		Asura_Xbox360_Graphics::ResetDepthBias();
		pxDevice->SetRenderState_Inline( D3DRS_HIGHPRECISIONBLENDENABLE, FALSE );

		// "Blend into albedo" materials might have modified these
		pxDevice->SetRenderState_Inline( D3DRS_COLORWRITEENABLE,  D3DCOLORWRITEENABLE_ALL );
		pxDevice->SetRenderState_Inline( D3DRS_COLORWRITEENABLE1, D3DCOLORWRITEENABLE_ALL );
		pxDevice->SetRenderState_Inline( D3DRS_COLORWRITEENABLE2, D3DCOLORWRITEENABLE_ALL );
		pxDevice->SetRenderState_Inline( D3DRS_COLORWRITEENABLE3, D3DCOLORWRITEENABLE_ALL );
	}

	ASURA_GPU_PROFILE_END( ASURA_GPUPROFILE_SKINS );
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_SkinRenderer::CopyBones()
//
// Copies bone matrices for a skin into the bone buffer
//
////////////////////////////////////////////////////////////////////////////////

bool Asura_Xbox360_SkinRenderer::CopyBones( const Asura_AMatrix4x4* const pxBoneMats,
	const u_int uNumBones, const Asura_Animation& xAnim )
{
	const u_int uNumBytes = uBONE_SIZE * uNumBones;

	if( uNumBytes <= ASURA_XBOX360_BONE_BUFFER_SIZE - g_uBoneOffset )
	{
		u_char* const pucBoneBuffer = g_apucBoneBuffers[ g_uCurrBoneBuffer ];
		float* pxBoneBuffer = reinterpret_cast< float* >( pucBoneBuffer + g_uBoneOffset );

		for( u_int u = 0; u < uNumBones; ++u )
		{
			// HLSL compiler deals with row-major matrices much better than column-major
			// ones, so transpose them here and invert the transform in the shader

			Asura_AVector4 axColumns[] =
			{
				pxBoneMats[u].GetColumn0(), pxBoneMats[u].GetColumn1(),
				pxBoneMats[u].GetColumn2(), pxBoneMats[u].GetColumn3(),
			};

			// We pack per-bone damage values into the matrix to save space, as we can reconstruct
			// the blatted row in the vertex shader (orthogonal rows). Invert here since 1 means
			// fully damaged, but in texturing terms that's material 0
			axColumns[2].SetZ( 1.0f - xAnim.GetBoneDamageMapValue(u) );

			for( u_int v = 0; v < 3; ++v )
			{
				for( int w = 0; w < 4; ++w )
				{
					(*pxBoneBuffer++) = axColumns[w].GetElement(v);
				}
			}
		}

		Direct3D_VertexBuffer& xVB = g_axBoneVBs[ g_uCurrBoneBuffer ];
		Asura_Xbox360_Graphics::GetDevice()->SetStreamSource( 1, &xVB, g_uBoneOffset, uBONE_SIZE );

		g_uBoneOffset += uNumBytes;

		return true;
	}
	else
	{
		Asura_Assert( false,
			"Not enough space in bone buffer. Increase ASURA_XBOX360_BONE_BUFFER_SIZE." );

		return false;
	}
}

/******************************************************************************/
/* Asura_Xbox360_Skin Class Functions                                         */
/******************************************************************************/

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Skin::Asura_Xbox360_Skin()
//
// Constructor
//
////////////////////////////////////////////////////////////////////////////////

Asura_Xbox360_Skin::Asura_Xbox360_Skin()
	: m_pxStrips( NULL )
	, m_uNumStrips(0)
	, m_uNumTriangles(0)
{
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Skin::~Asura_Xbox360_Skin()
//
// Destructor
//
////////////////////////////////////////////////////////////////////////////////

Asura_Xbox360_Skin::~Asura_Xbox360_Skin()
{
	delete[] m_pxStrips;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Skin::IsAnyMaterialTransparent()
//
// Scans all strips to see if any of them are using a transparent material
//
////////////////////////////////////////////////////////////////////////////////

bool Asura_Xbox360_Skin::IsAnyMaterialTransparent() const
{
	for( u_int u = 0; u < m_uNumStrips; ++u )
	{
		const Asura_MaterialResponse* const pxMaterial =
			Asura_MaterialResponse_System::GetMaterialByHashID( GetStrip(u).m_auMaterials[0] );

		if( pxMaterial && pxMaterial->IsTransparent() ) return true;
	}

	return false;
}