//
// Tools_PS3_ShaderExport.cpp
//
// Created by Catalin Arsenescu - 20 February 2008
//
// Copyright (C) 2005 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

// This file's header.
#include "PS3/Tools_PS3_Export.h"

// PS3 Shader compile information
#include "./../../Game/PS3/Shaders/Project_ShaderConfiguration.fxh"

// Engine independent.
#include "SpecialFX/Asura_Skybox.h"
#include "Debugging/Asura_Logfile.h"

// Engine platform-specific.
#include "PC/FileSystem/Tools_FileSystem.h"
#include "PC/FileSystem/Tools_File_Wrapper.h"
#include "Core/Asura_TextureFlags.h"

// Engine PS3.
#include "PS3/ChunkSystem/Asura_Chunk_ResourceFile_PS3.h"
#include "PS3/ChunkSystem/Asura_Chunk_ResourceFile_PS3_Botanicals.h"

// Tools.
#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
#include "Materials/Tools_MaterialResponse_System.h"
#endif

#ifdef ASURA_USE_STATIC_DECAL_SYSTEM
#include "SpecialFX/Asura_Decal_Template.h"
#include "Decals/Tools_StaticDecal_System.h"
#endif

Asura_FixedArray< const char*, 40 >	Tools_PS3_ShaderExport::s_aszTEXTURE_DEFINES;
Asura_FixedArray< const char*, 40 >	Tools_PS3_ShaderExport::s_axUsedDefines;
Asura_String						Tools_PS3_ShaderExport::s_szCommandLine;

u_int								Tools_PS3_ShaderExport::s_uNumUsedDefines;

Asura_PS3_FPTree					Tools_PS3_ShaderExport::s_xBotanicalsShaderTree;
Asura_PS3_FPTree					Tools_PS3_ShaderExport::s_xEnvironmentShaderTree;
Asura_PS3_FPTree					Tools_PS3_ShaderExport::s_xObjectShaderTree;
Asura_PS3_FPTree					Tools_PS3_ShaderExport::s_xSmoothSkinShaderTree;
Asura_PS3_FPTree					Tools_PS3_ShaderExport::s_xOthersShadersTree;

bool								Tools_PS3_ShaderExport::s_bInitialized = false;

//////////////////////////////////////////////////////////////////////////
// Static functions
//////////////////////////////////////////////////////////////////////////

static void TreeReset( Asura_Hash_ID uHash, Asura_PS3_Shader** ppxFP_Shader )
{
	delete (*ppxFP_Shader);
	(*ppxFP_Shader) = NULL;
}

static void AddPercentageDefine( const float fValue, Asura_Hash_ID& uHash, u_int& uBit )
{
	// Tools materials store values as percentages. Use two bits to indicate a shader optimised for the 0% or 100% cases, or the unoptimised shader

	if( fValue <= 1.0f )
	{
		// Don't set the two bits
		uBit <<= 2;
	}
	else if( fValue >= 99.0f )
	{
		// Set the bits to 01
		uHash |= uBit;
		uBit <<= 2;
	}
	else
	{
		// Set the bits to 11
		uHash |= uBit;
		uBit <<= 1;
		uHash |= uBit;
		uBit <<= 1;
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// ExtractShaderResource()
//
// Callback function to extract an enumerated shader source file from resources
// and save it off into the export temp folder
//
////////////////////////////////////////////////////////////////////////////////

static BOOL CALLBACK ExtractShaderResource( HMODULE xModule, LPCTSTR szType, LPTSTR szName, LONG_PTR uParam )
{
	Asura_Assert( !IS_INTRESOURCE( szName ), "Expected a string resource name." );

	const HRSRC xResInfoHandle = FindResource( xModule, szName, szType );
	Asura_Assertf( xResInfoHandle, ("Failed to find resource '%s' of type '%s'.", szName, szType) );
	if( !xResInfoHandle ) return FALSE;

	const HGLOBAL xResHandle = LoadResource( xModule, xResInfoHandle );
	Asura_Assertf( xResHandle, ("Failed to load resource '%s' of type '%s'.", szName, szType) );
	if( !xResHandle ) return FALSE;

	const LPVOID pResData = LockResource( xResHandle );
	Asura_Assertf( pResData, ("Failed to lock resource '%s' of type '%s'.", szName, szType) );
	if( !pResData ) return FALSE;

	const DWORD uResSize = SizeofResource( xModule, xResInfoHandle );

	Asura_String xOutputFileName = Tools_Level_Export::GetTempDirectory();
	Tools_FileSystem::EnsureDirectoryExists( xOutputFileName.GetString() );
	xOutputFileName += "\\";
	xOutputFileName += szName;

	Tools_File_Wrapper xOutputFile;
	const bool bSuccess = xOutputFile.Create( xOutputFileName.GetString(), true );

	Asura_Assertf( bSuccess, ("Failed to open file '%s' for writing.", szName) );
	if( !bSuccess ) return FALSE;

	const u_int uNumBytesWritten = xOutputFile.Write( pResData, uResSize );
	Asura_Assertf( uNumBytesWritten == uResSize, ("Failed to write to '%s'.", szName) );

	xOutputFile.Close();
	return TRUE;
}

//////////////////////////////////////////////////////////////////////////
// Tools_PS3_ShaderExport implementation
//////////////////////////////////////////////////////////////////////////

bool Tools_PS3_ShaderExport::BeginShaderExport( void )
{
	if( s_bInitialized == false )
	{
		s_aszTEXTURE_DEFINES[ 0] = "-DUSE_ALBEDO_1 ";
		s_aszTEXTURE_DEFINES[ 1] = "-DUSE_ALBEDO_2 ";
		s_aszTEXTURE_DEFINES[ 2] = "-DUSE_NORMAL_1 ";
		s_aszTEXTURE_DEFINES[ 3] = "-DUSE_NORMAL_2 ";
		s_aszTEXTURE_DEFINES[ 4] = "-DUSE_SPECULAR_1 ";
		s_aszTEXTURE_DEFINES[ 5] = "-DUSE_SPECULAR_2 ";
		s_aszTEXTURE_DEFINES[ 6] = "-DUSE_TRANSPARENCY_1 ";
		s_aszTEXTURE_DEFINES[ 7] = "-DUSE_TRANSPARENCY_2 ";
		s_aszTEXTURE_DEFINES[ 8] = "-DUSE_SELFILLUMINATION_1 ";
		s_aszTEXTURE_DEFINES[ 9] = "-DUSE_SELFILLUMINATION_2 ";
		s_aszTEXTURE_DEFINES[10] = "-DUSE_ENVIRONMENT_1 ";
		s_aszTEXTURE_DEFINES[11] = "-DUSE_ENVIRONMENT_2 ";
		s_aszTEXTURE_DEFINES[12] = "-DUSE_REFLECTIVITY_1 ";
		s_aszTEXTURE_DEFINES[13] = "-DUSE_REFLECTIVITY_2 ";
		s_aszTEXTURE_DEFINES[14] = "-DUSE_PARALLAX_1 ";
		s_aszTEXTURE_DEFINES[15] = "-DUSE_PARALLAX_2 ";
		s_aszTEXTURE_DEFINES[16] = "-DUSE_ALBEDO_DETAIL_MAP_1 ";
		s_aszTEXTURE_DEFINES[17] = "-DUSE_ALBEDO_DETAIL_MAP_2 ";
		s_aszTEXTURE_DEFINES[18] = "-DUSE_NORMAL_DETAIL_MAP_1 ";
		s_aszTEXTURE_DEFINES[19] = "-DUSE_NORMAL_DETAIL_MAP_2 ";

		s_aszTEXTURE_DEFINES[20] = "-DCOLOURISE_DIFFUSE=0 ";
		s_aszTEXTURE_DEFINES[21] = "-DCOLOURISE_DIFFUSE=1 ";
		s_aszTEXTURE_DEFINES[22] = "-DCOLOURISE_SPECULAR=0 ";
		s_aszTEXTURE_DEFINES[23] = "-DCOLOURISE_SPECULAR=1 ";
		s_aszTEXTURE_DEFINES[24] = "-DMETALICITY=0 ";
		s_aszTEXTURE_DEFINES[25] = "-DMETALICITY=1 ";

		s_aszTEXTURE_DEFINES[26] = "-DUSE_BLEND_INTO_ALBEDO ";
		s_aszTEXTURE_DEFINES[27] = "-DUSE_RESPONSE=false ";
		s_aszTEXTURE_DEFINES[28] = "-DASURA_USE_HDR ";
		s_aszTEXTURE_DEFINES[29] = "-DUSE_FOG ";
		s_aszTEXTURE_DEFINES[30] = "-DUSE_DAMAGE_MAPS=true ";
		s_aszTEXTURE_DEFINES[31] = "-DASURA_PS3_USE_32BIT_RENDER_TARGET ";

		s_aszTEXTURE_DEFINES[32] = "-DUSE_SPECULAR=false ";

		s_bInitialized = true;
	}
	else
	{
		Cleanup();
	}

	// Extract the shader source files from resources and dump them in the temp folder
	EnumResourceNames( NULL, "PS3SHADER", ExtractShaderResource, 0 );

	return true;
}

bool Tools_PS3_ShaderExport::EndShaderExport( const HANDLE xExportHandle )
{
	ExportShaders( xExportHandle );
	Cleanup();
	return true;
}

Asura_Hash_ID Tools_PS3_ShaderExport::AddBotanicalsShader( const Asura_Hash_ID uMaterial1/*, const Asura_Hash_ID uMaterial2*/ )
{
	u_int			uNumMaterialResponseOverridesForExport	= Asura_MaterialResponse_System::GetNumOverrideMaps();
	Asura_Hash_ID*	pxMaterialResponseOverridesForExport	= Asura_MaterialResponse_System::CreateOverrideMapHashArray();

	for(u_int u = 0; u < uNumMaterialResponseOverridesForExport; u++)
	{
		const Asura_Hash_ID							uOverrideMapID	= pxMaterialResponseOverridesForExport[u];
		const Asura_MaterialResponse_OverrideMap*	pxOverrideMap	= Asura_MaterialResponse_System::GetOverrideMap(uOverrideMapID);
		const Asura_MaterialResponse_Override*		pxOverride1		= (pxOverrideMap == NULL) ? NULL : pxOverrideMap->GetOverride(uMaterial1);
		const Asura_Hash_ID							uOverrideID1	= (pxOverride1 == NULL) ? uMaterial1 : pxOverride1->GetOverrideHashID();
		if( uOverrideID1 != ASURA_HASH_ID_UNSET )
		{
			const Tools_MaterialResponse* pxMaterialT1 = Tools_MaterialResponse_System::GetMaterialByHashID( uOverrideID1 );
			const Asura_MaterialResponse* const pxAsuraMaterialT1 = Asura_MaterialResponse_System::GetMaterialByHashID( uOverrideID1 );

			Asura_Hash_ID uHash1 = CreatePartialHashID( pxMaterialT1, pxAsuraMaterialT1 );
			Asura_Hash_ID uHash = CombineHashIDs( uHash1, ASURA_HASH_ID_UNSET );

			AddToTree( s_xBotanicalsShaderTree, uHash, "Botanicals.ps3FX" );
		}
	}
	Asura_MaterialResponse_System::DestroyOverrideMapHashArray(pxMaterialResponseOverridesForExport);

	const Tools_MaterialResponse* pxMaterial1 = Tools_MaterialResponse_System::GetMaterialByHashID( uMaterial1 );
	const Asura_MaterialResponse* const pxAsuraMaterial1 = Asura_MaterialResponse_System::GetMaterialByHashID( uMaterial1 );

	Asura_Hash_ID uHash1 = CreatePartialHashID( pxMaterial1, pxAsuraMaterial1 );
	Asura_Hash_ID uHash = CombineHashIDs( uHash1, ASURA_HASH_ID_UNSET );

	AddToTree( s_xBotanicalsShaderTree, uHash, "Botanicals.ps3FX" );
	return uHash;
}

Asura_Hash_ID Tools_PS3_ShaderExport::AddEnvironmentShader( const Asura_Hash_ID uMaterial1, const Asura_Hash_ID uMaterial2 )
{
	u_int			uNumMaterialResponseOverridesForExport	= Asura_MaterialResponse_System::GetNumOverrideMaps();
	Asura_Hash_ID*	pxMaterialResponseOverridesForExport	= Asura_MaterialResponse_System::CreateOverrideMapHashArray();

	for(u_int u = 0; u < uNumMaterialResponseOverridesForExport; u++)
	{
		const Asura_Hash_ID							uOverrideMapID	= pxMaterialResponseOverridesForExport[u];
		const Asura_MaterialResponse_OverrideMap*	pxOverrideMap	= Asura_MaterialResponse_System::GetOverrideMap(uOverrideMapID);
		const Asura_MaterialResponse_Override*		pxOverride1		= (pxOverrideMap == NULL) ? NULL : pxOverrideMap->GetOverride(uMaterial1);
		const Asura_MaterialResponse_Override*		pxOverride2		= (pxOverrideMap == NULL) ? NULL : pxOverrideMap->GetOverride(uMaterial2);
		const Asura_Hash_ID							uOverrideID1	= (pxOverride1 == NULL) ? uMaterial1 : pxOverride1->GetOverrideHashID();
		const Asura_Hash_ID							uOverrideID2	= (pxOverride2 == NULL) ? uMaterial2 : pxOverride2->GetOverrideHashID();
		if( uOverrideID1 != ASURA_HASH_ID_UNSET || uOverrideID2 != ASURA_HASH_ID_UNSET )
		{
			const Tools_MaterialResponse* pxMaterialT1 = Tools_MaterialResponse_System::GetMaterialByHashID( uOverrideID1 );
			const Tools_MaterialResponse* pxMaterialT2 = Tools_MaterialResponse_System::GetMaterialByHashID( uOverrideID2 );
			const Asura_MaterialResponse* const pxAsuraMaterialT1 = Asura_MaterialResponse_System::GetMaterialByHashID( uOverrideID1 );
			const Asura_MaterialResponse* const pxAsuraMaterialT2 = Asura_MaterialResponse_System::GetMaterialByHashID( uOverrideID2 );

			Asura_Hash_ID uHash1 = CreatePartialHashID( pxMaterialT1, pxAsuraMaterialT1 );
			Asura_Hash_ID uHash2 = CreatePartialHashID( pxMaterialT2, pxAsuraMaterialT2 );

			Asura_Hash_ID uHash = CombineHashIDs( uHash1, uHash2, false );
			Asura_Hash_ID uHashFog = CombineHashIDs( uHash1, uHash2, true );

			if( uHash & ASURA_PS3_SHADER_WATER )
			{
				AddToTree( s_xEnvironmentShaderTree, uHash, "Env_Water_FP.ps3FX" );
				AddToTree( s_xEnvironmentShaderTree, uHashFog, "Env_Water_FP.ps3FX" );
			}
			else if( uHash & ASURA_PS3_SHADER_FORWARD )
			{
				AddToTree( s_xEnvironmentShaderTree, uHash, "Env_Forward_FP.ps3FX" );
				AddToTree( s_xEnvironmentShaderTree, uHashFog, "Env_Forward_FP.ps3FX" );
			}
			else
			{
				AddToTree( s_xEnvironmentShaderTree, uHash, "Env_FP.ps3FX" );
			}
		}
	}
	Asura_MaterialResponse_System::DestroyOverrideMapHashArray(pxMaterialResponseOverridesForExport);

	const Tools_MaterialResponse* pxMaterial1 = Tools_MaterialResponse_System::GetMaterialByHashID( uMaterial1 );
	const Tools_MaterialResponse* pxMaterial2 = Tools_MaterialResponse_System::GetMaterialByHashID( uMaterial2 );
	const Asura_MaterialResponse* const pxAsuraMaterial1 = Asura_MaterialResponse_System::GetMaterialByHashID( uMaterial1 );
	const Asura_MaterialResponse* const pxAsuraMaterial2 = Asura_MaterialResponse_System::GetMaterialByHashID( uMaterial2 );

	Asura_Hash_ID uHash1 = CreatePartialHashID( pxMaterial1, pxAsuraMaterial1 );
	Asura_Hash_ID uHash2 = CreatePartialHashID( pxMaterial2, pxAsuraMaterial2 );

	Asura_Hash_ID uHash = CombineHashIDs( uHash1, uHash2, false );
	Asura_Hash_ID uHashFog = CombineHashIDs( uHash1, uHash2, true );

	if( uHash & ASURA_PS3_SHADER_WATER )
	{
		if( !AddToTree( s_xEnvironmentShaderTree, uHash, "Env_Water_FP.ps3FX" ) ) return uHash;
		if( !AddToTree( s_xEnvironmentShaderTree, uHashFog, "Env_Water_FP.ps3FX" ) ) return uHash;
	}
	else if( uHash & ASURA_PS3_SHADER_FORWARD )
	{
		if( !AddToTree( s_xEnvironmentShaderTree, uHash, "Env_Forward_FP.ps3FX" ) ) return uHash;
		if( !AddToTree( s_xEnvironmentShaderTree, uHashFog, "Env_Forward_FP.ps3FX" ) ) return uHash;
	}
	else
	{
		if( !AddToTree( s_xEnvironmentShaderTree, uHash, "Env_FP.ps3FX" ) ) return uHash;
	}

	return uHash;
}

Asura_Hash_ID Tools_PS3_ShaderExport::AddObjectShader( const Asura_Hash_ID uMaterial1, const Asura_Hash_ID uMaterial2 )
{
	u_int			uNumMaterialResponseOverridesForExport	= Asura_MaterialResponse_System::GetNumOverrideMaps();
	Asura_Hash_ID*	pxMaterialResponseOverridesForExport	= Asura_MaterialResponse_System::CreateOverrideMapHashArray();

	for(u_int u = 0; u < uNumMaterialResponseOverridesForExport; u++)
	{
		const Asura_Hash_ID							uOverrideMapID	= pxMaterialResponseOverridesForExport[u];
		const Asura_MaterialResponse_OverrideMap*	pxOverrideMap	= Asura_MaterialResponse_System::GetOverrideMap(uOverrideMapID);
		const Asura_MaterialResponse_Override*		pxOverride1		= (pxOverrideMap == NULL) ? NULL : pxOverrideMap->GetOverride(uMaterial1);
		const Asura_MaterialResponse_Override*		pxOverride2		= (pxOverrideMap == NULL) ? NULL : pxOverrideMap->GetOverride(uMaterial2);
		const Asura_Hash_ID							uOverrideID1	= (pxOverride1 == NULL) ? uMaterial1 : pxOverride1->GetOverrideHashID();
		const Asura_Hash_ID							uOverrideID2	= (pxOverride2 == NULL) ? uMaterial2 : pxOverride2->GetOverrideHashID();
		if( uOverrideID1 != ASURA_HASH_ID_UNSET || uOverrideID2 != ASURA_HASH_ID_UNSET )
		{
			const Tools_MaterialResponse* pxMaterialT1 = Tools_MaterialResponse_System::GetMaterialByHashID( uOverrideID1 );
			const Tools_MaterialResponse* pxMaterialT2 = Tools_MaterialResponse_System::GetMaterialByHashID( uOverrideID2 );
			const Asura_MaterialResponse* const pxAsuraMaterialT1 = Asura_MaterialResponse_System::GetMaterialByHashID( uOverrideID1 );
			const Asura_MaterialResponse* const pxAsuraMaterialT2 = Asura_MaterialResponse_System::GetMaterialByHashID( uOverrideID2 );

			Asura_Hash_ID uHash1 = CreatePartialHashID( pxMaterialT1, pxAsuraMaterialT1 );
			Asura_Hash_ID uHash2 = CreatePartialHashID( pxMaterialT2, pxAsuraMaterialT2 );

			Asura_Hash_ID uHash = CombineHashIDs( uHash1, uHash2, false );
			Asura_Hash_ID uHashFog = CombineHashIDs( uHash1, uHash2, true );

			if( uHash & ASURA_PS3_SHADER_FORWARD )
			{
				AddToTree( s_xObjectShaderTree, uHash, "Object_Forward_FP.ps3FX" );
				AddToTree( s_xObjectShaderTree, uHashFog, "Object_Forward_FP.ps3FX" );
			}
			else
			{
				AddToTree( s_xObjectShaderTree, uHash, "Object_FP.ps3FX" );
			}
		}
	}
	Asura_MaterialResponse_System::DestroyOverrideMapHashArray(pxMaterialResponseOverridesForExport);

	const Tools_MaterialResponse* pxMaterial1 = Tools_MaterialResponse_System::GetMaterialByHashID( uMaterial1 );
	const Tools_MaterialResponse* pxMaterial2 = Tools_MaterialResponse_System::GetMaterialByHashID( uMaterial2 );
	const Asura_MaterialResponse* const pxAsuraMaterial1 = Asura_MaterialResponse_System::GetMaterialByHashID( uMaterial1 );
	const Asura_MaterialResponse* const pxAsuraMaterial2 = Asura_MaterialResponse_System::GetMaterialByHashID( uMaterial2 );

	Asura_Hash_ID uHash1 = CreatePartialHashID( pxMaterial1, pxAsuraMaterial1 );
	Asura_Hash_ID uHash2 = CreatePartialHashID( pxMaterial2, pxAsuraMaterial2 );

	Asura_Hash_ID uHash = CombineHashIDs( uHash1, uHash2, false );
	Asura_Hash_ID uHashFog = CombineHashIDs( uHash1, uHash2, true );

	if( uHash & ASURA_PS3_SHADER_FORWARD )
	{
		if( !AddToTree( s_xObjectShaderTree, uHash, "Object_Forward_FP.ps3FX" ) ) return uHash;
		if( !AddToTree( s_xObjectShaderTree, uHashFog, "Object_Forward_FP.ps3FX" ) ) return uHash;
	}
	else
	{
		if( !AddToTree( s_xObjectShaderTree, uHash, "Object_FP.ps3FX" ) ) return uHash;
	}

	return uHash;
}

Asura_Hash_ID Tools_PS3_ShaderExport::AddSmoothSkinShader( const Asura_Hash_ID uMaterial1, const Asura_Hash_ID uMaterial2 )
{
	u_int			uNumMaterialResponseOverridesForExport	= Asura_MaterialResponse_System::GetNumOverrideMaps();
	Asura_Hash_ID*	pxMaterialResponseOverridesForExport	= Asura_MaterialResponse_System::CreateOverrideMapHashArray();

	for(u_int u = 0; u < uNumMaterialResponseOverridesForExport; u++)
	{
		const Asura_Hash_ID							uOverrideMapID	= pxMaterialResponseOverridesForExport[u];
		const Asura_MaterialResponse_OverrideMap*	pxOverrideMap	= Asura_MaterialResponse_System::GetOverrideMap(uOverrideMapID);
		const Asura_MaterialResponse_Override*		pxOverride1		= (pxOverrideMap == NULL) ? NULL : pxOverrideMap->GetOverride(uMaterial1);
		const Asura_MaterialResponse_Override*		pxOverride2		= (pxOverrideMap == NULL) ? NULL : pxOverrideMap->GetOverride(uMaterial2);
		const Asura_Hash_ID							uOverrideID1	= (pxOverride1 == NULL) ? uMaterial1 : pxOverride1->GetOverrideHashID();
		const Asura_Hash_ID							uOverrideID2	= (pxOverride2 == NULL) ? uMaterial2 : pxOverride2->GetOverrideHashID();

		if( uOverrideID1 != ASURA_HASH_ID_UNSET || uOverrideID2 != ASURA_HASH_ID_UNSET )
		{
			const Tools_MaterialResponse* pxMaterialT1 = Tools_MaterialResponse_System::GetMaterialByHashID( uOverrideID1 );
			const Tools_MaterialResponse* pxMaterialT2 = Tools_MaterialResponse_System::GetMaterialByHashID( uOverrideID2 );
			const Asura_MaterialResponse* const pxAsuraMaterialT1 = Asura_MaterialResponse_System::GetMaterialByHashID( uOverrideID1 );
			const Asura_MaterialResponse* const pxAsuraMaterialT2 = Asura_MaterialResponse_System::GetMaterialByHashID( uOverrideID2 );

			Asura_Hash_ID uHash1 = CreatePartialHashID( pxMaterialT1, pxAsuraMaterialT1 );
			Asura_Hash_ID uHash2 = CreatePartialHashID( pxMaterialT2, pxAsuraMaterialT2 );

			Asura_Hash_ID uHash = CombineHashIDs( uHash1, uHash2, false, false );
			Asura_Hash_ID uHashFog = CombineHashIDs( uHash1, uHash2, true, false );
			Asura_Hash_ID uHashDM = CombineHashIDs( uHash1, uHash2, false, true );
			Asura_Hash_ID uHashDMFog = CombineHashIDs( uHash1, uHash2, true, true );

			if( uHash & ASURA_PS3_SHADER_FORWARD )
			{
				AddToTree( s_xSmoothSkinShaderTree, uHash,		"SmoothSkin_Forward_FP.ps3FX" );
				AddToTree( s_xSmoothSkinShaderTree, uHashFog,	"SmoothSkin_Forward_FP.ps3FX" );
				AddToTree( s_xSmoothSkinShaderTree, uHashDM,	"SmoothSkin_Forward_FP.ps3FX" );
				AddToTree( s_xSmoothSkinShaderTree, uHashDMFog,	"SmoothSkin_Forward_FP.ps3FX" );
			}
			else
			{
				AddToTree( s_xSmoothSkinShaderTree, uHash,		"SmoothSkin_FP.ps3FX" );
				AddToTree( s_xSmoothSkinShaderTree, uHashDM,	"SmoothSkin_FP.ps3FX" );
			}
		}
	}
	Asura_MaterialResponse_System::DestroyOverrideMapHashArray(pxMaterialResponseOverridesForExport);

	const Tools_MaterialResponse* pxMaterial1 = Tools_MaterialResponse_System::GetMaterialByHashID( uMaterial1 );
	const Tools_MaterialResponse* pxMaterial2 = Tools_MaterialResponse_System::GetMaterialByHashID( uMaterial2 );
	const Asura_MaterialResponse* const pxAsuraMaterial1 = Asura_MaterialResponse_System::GetMaterialByHashID( uMaterial1 );
	const Asura_MaterialResponse* const pxAsuraMaterial2 = Asura_MaterialResponse_System::GetMaterialByHashID( uMaterial2 );

	Asura_Hash_ID uHash1 = CreatePartialHashID( pxMaterial1, pxAsuraMaterial1 );
	Asura_Hash_ID uHash2 = CreatePartialHashID( pxMaterial2, pxAsuraMaterial2 );

	Asura_Hash_ID uHash = CombineHashIDs( uHash1, uHash2, false );
	Asura_Hash_ID uHashFog = CombineHashIDs( uHash1, uHash2, true );
	Asura_Hash_ID uHashDM = CombineHashIDs( uHash1, uHash2, false, true );
	Asura_Hash_ID uHashDMFog = CombineHashIDs( uHash1, uHash2, true, true );

	if( uHash & ASURA_PS3_SHADER_FORWARD )
	{
		if( !AddToTree( s_xSmoothSkinShaderTree, uHash,			"SmoothSkin_Forward_FP.ps3FX" ) ) return uHash;
		if( !AddToTree( s_xSmoothSkinShaderTree, uHashFog,		"SmoothSkin_Forward_FP.ps3FX" ) ) return uHash;
		if( !AddToTree( s_xSmoothSkinShaderTree, uHashDM,		"SmoothSkin_Forward_FP.ps3FX" ) ) return uHash;
		if( !AddToTree( s_xSmoothSkinShaderTree, uHashDMFog,	"SmoothSkin_Forward_FP.ps3FX" ) ) return uHash;
	}
	else
	{
		if( !AddToTree( s_xSmoothSkinShaderTree, uHash,		"SmoothSkin_FP.ps3FX" ) ) return uHash;
		if( !AddToTree( s_xSmoothSkinShaderTree, uHashDM,	"SmoothSkin_FP.ps3FX" ) ) return uHash;
	}

	return uHash;
}

Asura_Hash_ID Tools_PS3_ShaderExport::AddSkyboxShader( void )
{
	Asura_Hash_ID uHash	= static_cast<Asura_Hash_ID>( ASURA_PS3_SHADER_TYPE_HASH_SKYBOX );
	bool bDisableHaze1, bDisableHaze2, bMirror1, bMirror2, bDisableSun1, bDisableSun2;
	float af1[3], af2[3];

	s_uNumUsedDefines	= 0;
	if( Asura_Skybox::GetUseNewSkyboxShader() )
	{
		Asura_Skybox::GetHaze( af1, af2, bDisableHaze1, bDisableHaze2, bMirror1, bMirror2 );
		if( bDisableHaze1 )
		{
			s_axUsedDefines[ s_uNumUsedDefines++ ] = "-DDISABLE_HAZE=1 ";
		}
		if( bDisableHaze2 )
		{
			s_axUsedDefines[ s_uNumUsedDefines++ ] = "-DDISABLE_2HAZE=1 ";
		}

		Asura_Skybox::GetSun( af1, af2, bDisableSun1, bDisableSun2 );
		if( bDisableSun1 )
		{
			s_axUsedDefines[ s_uNumUsedDefines++ ] = "-DDISABLE_SUN=1 ";
		}
		if( bDisableSun2 )
		{
			s_axUsedDefines[ s_uNumUsedDefines++ ] = "-DDISABLE_2SUN=1 ";
		}

		if( bMirror1 )
		{
			s_axUsedDefines[ s_uNumUsedDefines++ ] = "-DMIRROR_BELOW_HORIZON1=true ";
		}
		if( bMirror2 )
		{
			s_axUsedDefines[ s_uNumUsedDefines++ ] = "-DMIRROR_BELOW_HORIZON2=true ";
		}

		s_axUsedDefines[ s_uNumUsedDefines++ ] = "-DUSE_NEW_SHADER=1 ";
	}
	else
	{
		Tools_Level_Export::AddMessage( Tools_Level_Export::MT_ERROR, "PS3 doesn't support old skybox shader" ); //BW we aren't using textured sky boxes any more.
	}

	if( !AddToTree( s_xOthersShadersTree, uHash, "SFX_Skybox_FP.ps3FX" ) ) return uHash;

	return uHash;
}


void Tools_PS3_ShaderExport::SetDefinesFromHash( Asura_Hash_ID uHash )
{
	//BW by doing this here rather than when we generate the hashes we confirm that the hash is unique for a particular set of shader constants
	// i.e. if you don't have the data you need to do it this way, then you need to put that data in the hash to avoid runtime shader confusion.
	
	//todo: make bits have a 1:1 mapping to texture defines, they already nearly do.
	
	s_uNumUsedDefines	= 0;

	// texture flags
	u_int uBit = 1;
	u_int uCurrDefine = 0;
	for( u_int u = 0; u < Asura_MaterialResponse::NUMBER_OF_TEXTURE_SLOTS; ++u )
	{
		// material 1
		if ( (uHash&uBit) != 0 ) s_axUsedDefines[ s_uNumUsedDefines++ ] = s_aszTEXTURE_DEFINES[ uCurrDefine ];
		++uCurrDefine;
		uBit <<= 1;

		// material 2
		if ( (uHash&uBit) != 0 ) s_axUsedDefines[ s_uNumUsedDefines++ ] = s_aszTEXTURE_DEFINES[ uCurrDefine ];
		++uCurrDefine;
		uBit <<= 1;
	}

	// percentage defines, for metalicity, diffuse colour, etc
	for ( u_int u = 0; u < 3; ++u )	
	{
		if ( (uHash & (3*uBit)) == 0 ) s_aszTEXTURE_DEFINES[ uCurrDefine ];
		if ( (uHash & (3*uBit)) == uBit ) s_aszTEXTURE_DEFINES[ uCurrDefine+1 ];
		uCurrDefine += 2;
		uBit <<= 2;
	}
	
	//specific flags
	if ( (uHash & uBit) != 0 ) s_axUsedDefines[ s_uNumUsedDefines++ ] = s_aszTEXTURE_DEFINES[32];
	if ( (uHash & ASURA_PS3_SHADER_FOG) != 0 ) s_axUsedDefines[ s_uNumUsedDefines++ ] = s_aszTEXTURE_DEFINES[ 29 ];
	if ( (uHash & ASURA_PS3_SHADER_BLEND_INTO_ALBEDO) != 0 ) s_axUsedDefines[ s_uNumUsedDefines++ ] = s_aszTEXTURE_DEFINES[ 26 ];
	if ( (uHash & ASURA_PS3_SHADER_DAMAGE_MAP) != 0 ) s_axUsedDefines[ s_uNumUsedDefines++ ] = s_aszTEXTURE_DEFINES[ 30 ];
	
#ifdef ASURA_USE_HDR
	s_axUsedDefines[ s_uNumUsedDefines++ ] = s_aszTEXTURE_DEFINES[ 28 ];
#endif
#ifdef ASURA_PS3_USE_32BIT_RENDER_TARGET
	s_axUsedDefines[ s_uNumUsedDefines++ ] = s_aszTEXTURE_DEFINES[ 31 ];
#endif
#ifdef ASURA_USE_PS3_OLD_MATERRIAL_RESPONSE_SHADER_EXPORT
	s_axUsedDefines[ s_uNumUsedDefines++ ] = s_aszTEXTURE_DEFINES[ 27 ];
#endif
}


Asura_Hash_ID Tools_PS3_ShaderExport::CombineHashIDs( Asura_Hash_ID uShaderHash1, Asura_Hash_ID uShaderHash2, bool bUseFog, bool bUseDamageMaps )
{
	//This duplicates the functionality of Asura_PS3_MaterialResponse_System::CreateShaderHashID, and the two functions must be kept in sync.

	u_int uHash = uShaderHash1;

	if ( uShaderHash2 != ASURA_HASH_ID_UNSET )
	{
		// modify flags for material 2's textures
		u_int uTextureFlags = uShaderHash2 & ((1<<Asura_MaterialResponse::NUMBER_OF_TEXTURE_SLOTS) - 1);
		uHash |= uTextureFlags<<1;
		
		// combine flags for other things 
		uHash |= uShaderHash2 & ~((1<<Asura_MaterialResponse::NUMBER_OF_TEXTURE_SLOTS) - 1); 
	}

	if( bUseFog ) uHash += ASURA_PS3_SHADER_FOG;
	if ( bUseDamageMaps ) uHash += ASURA_PS3_SHADER_DAMAGE_MAP;

	return uHash;
}

Asura_Hash_ID Tools_PS3_ShaderExport::CreatePartialHashID( const Tools_MaterialResponse* pxMaterial, const Asura_MaterialResponse* const pxAsuraMaterial )
{
	const Tools_MaterialResponse* pxParent = NULL;
	Asura_Hash_ID uHash	= ASURA_HASH_ID_UNSET;

	if ( pxMaterial ) 
	{
		u_int uBit			= 1;

		// Need to grab some properties from the parent
		pxParent = Tools_MaterialResponse_System::GetMaterialByHashID( pxMaterial->GetParentHash() );


		for( u_int u = 0; u < Asura_MaterialResponse::NUMBER_OF_TEXTURE_SLOTS; ++u )
		{
			if( pxMaterial->GetTexture(u) != ASURA_TEXTURE_NONE )
			{
				uHash |= uBit;
			}
			uBit <<= 2;
		}


		float fColouriseDiffuse;
		float fColouriseSpecular;
		float fMetalicity;
		Asura_RGBA xSpecularColour;
		float fMaxGraphValue;

		fColouriseDiffuse	= ( pxParent ? pxParent->GetColouriseDiffuse()	: pxMaterial->GetColouriseDiffuse() );
		fColouriseSpecular	= ( pxParent ? pxParent->GetColouriseSpecular()	: pxMaterial->GetColouriseSpecular() );
		fMetalicity			= ( pxParent ? pxParent->GetMetalicity()		: pxMaterial->GetMetalicity() );
		xSpecularColour = ( pxParent ? pxParent->GetSpecularColour()	: pxMaterial->GetSpecularColour() );
		fMaxGraphValue = ( pxParent ? pxParent->GetSpecularGraph().GetMaxDiscreteValue() : pxMaterial->GetSpecularGraph().GetMaxDiscreteValue() );

		AddPercentageDefine( fColouriseDiffuse,		uHash, uBit );
		AddPercentageDefine( fColouriseSpecular,	uHash, uBit );
		AddPercentageDefine( fMetalicity,			uHash, uBit );

		// check if we actually want a specular highlight
		const bool bIgnoreSpecular = pxParent 
			? pxParent->GetIgnoreSpecularPrelighting()
			: pxMaterial->GetIgnoreSpecularPrelighting();

		if ( bIgnoreSpecular || fMaxGraphValue == 0
			|| ( fColouriseSpecular >= 99.0f && xSpecularColour.GetLuminance() == 0.0f ) )
		{
			uHash |= uBit;
		}
	}

	if( pxAsuraMaterial )
	{
		if( pxAsuraMaterial->GetSpecialFXMode() == Asura_MaterialResponse::SPECIALFX_MODE_WATER )
		{
			uHash += ASURA_PS3_SHADER_WATER;
		}
		else if( pxAsuraMaterial->IsTransparent() )
		{
			uHash += ASURA_PS3_SHADER_FORWARD;
		}

		Asura_MaterialResponse::BLEND_MODE eBlendMode = pxParent ? pxParent->GetBlendMode() : pxAsuraMaterial->GetBlendMode();
		if( eBlendMode != Asura_MaterialResponse::BLEND_MODE_OPAQUE )
		{
			uHash += ASURA_PS3_SHADER_BLEND_INTO_ALBEDO;
		}
	}

	return uHash;
}

bool Tools_PS3_ShaderExport::AddToTree( Asura_PS3_FPTree& xTree, Asura_Hash_ID uHashID, char* szResourceName )
{
	Asura_PS3_Shader** ppxShader = xTree.GetDataByHash( uHashID );

	if( ppxShader == NULL )
	{
		ppxShader = xTree.AddNode( uHashID );
		*ppxShader = new Asura_PS3_Shader( uHashID );
		if( *ppxShader == NULL )
		{
			Asura_Assertf( false, ("Failed to create a new shader. Possible out of memory. Shader name %s", szResourceName) );
			return false;
		}
		
		SetDefinesFromHash( uHashID );
		bool bCompiled = CompileShader( *ppxShader, szResourceName );

		Asura_Assertf ( bCompiled, ("Failed to compile shader %s", szResourceName) );

		return bCompiled;
	}
	return true;
}

bool Tools_PS3_ShaderExport::CompileShader( Asura_PS3_Shader* pxShader, char* szResourceName )
{
	Asura_String szOutputFileName =  Tools_Level_Export::GetTempDirectory();
	szOutputFileName += "\\TempShader.obj";
	Asura_String szInputFileName = "\"";
	szInputFileName += Tools_Level_Export::GetTempDirectory();
	szInputFileName += "\\";
	szInputFileName += szResourceName;
	szInputFileName += "\"";

	s_szCommandLine = "sce-cgc -p sce_fp_rsx -mcgb -fno-max-psize -po OutColorPrec=fp16 -fuse-nrmh -nobcolor -fastmath -fastprecision -inline all -unroll all -nofloatbindings -ifcvt all ";
	for( u_int i = 0; i < s_uNumUsedDefines; ++i )
	{
		s_szCommandLine += s_axUsedDefines[ i ];
	}

	s_szCommandLine += "-o \"";
	s_szCommandLine += szOutputFileName;
	s_szCommandLine += "\" ";
	s_szCommandLine += szInputFileName;

 	//logfile code copied from the x360 bundler
	SECURITY_ATTRIBUTES xSecurityAttributes;
	xSecurityAttributes.nLength = sizeof( xSecurityAttributes );
	xSecurityAttributes.lpSecurityDescriptor = NULL;
	xSecurityAttributes.bInheritHandle = TRUE;

 	const HANDLE xStdoutHandle = CreateFile( "Logfiles\\Shader_Output.txt", GENERIC_WRITE,
		FILE_SHARE_READ, &xSecurityAttributes, CREATE_ALWAYS, 0, NULL );

	Asura_Assert( xStdoutHandle != INVALID_HANDLE_VALUE,
		"Failed to open Shader logfile for writing." );

	//Compile the shader
	PROCESS_INFORMATION xProcessInfo;
	STARTUPINFO xStartupInfo;
	ZeroMemory( &xStartupInfo, sizeof(STARTUPINFO) );
	xStartupInfo.cb = sizeof( STARTUPINFO );
	xStartupInfo.dwFlags = STARTF_USESTDHANDLES;
	xStartupInfo.hStdInput = INVALID_HANDLE_VALUE;
	xStartupInfo.hStdOutput = xStdoutHandle;
	xStartupInfo.hStdError = xStdoutHandle;

	if( !CreateProcess( NULL, s_szCommandLine.GetString(), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &xStartupInfo, &xProcessInfo ) )
	{
		Asura_Assert( false, "Failed to run the shader compiler!" );
		return false;
	}
	WaitForSingleObject( xProcessInfo.hProcess, INFINITE ); //Wait to finish compilation

	DWORD ulShaderCompilerExitCode = 0;
	if( !GetExitCodeProcess( xProcessInfo.hProcess, &ulShaderCompilerExitCode  ) )
	{
		Asura_Assert( false, "Unable to query exit code for shader compiler process." );
	}

	CloseHandle( xProcessInfo.hThread );
	CloseHandle( xProcessInfo.hProcess );
	CloseHandle( xStdoutHandle );

	if( ulShaderCompilerExitCode != 0 )
	{
	#if defined ASURA_MAIN
		Tools_Level_Export::AddMessage( Tools_Level_Export::MT_ERROR, "Shader compiler tool reported an error. Exit code %u.", ulShaderCompilerExitCode );
		Asura_Assertf( false, ("Shader compiler tool reported an error. Exit code %u. command line:\n  %s", ulShaderCompilerExitCode, s_szCommandLine.GetString() ) );
	#else
		Asura_Assertf( false, ("Shader compiler tool reported an error. Exit code %u.", ulShaderCompilerExitCode) );
	#endif
		Asura_OutputToDebugger( (s_szCommandLine.GetString()) );
		return false;
	}

	//Read shader data
	Asura_Handle uHandle = Asura_FileAccess::Open( szOutputFileName.GetString() );
	if( uHandle == ASURA_INVALID_HANDLE )
	{
		Asura_Assert( false, "Error compiling shader" );
		return 0;
	}
	u_int uiShaderSize = Asura_FileAccess::GetFileSize( uHandle );
	pxShader->SetShaderSize( uiShaderSize );
	if( pxShader->AllocateShaderData() == false ) //Allocate memory for the shader
	{
		return false;
	}
	Asura_FileAccess::ReadData( uHandle, pxShader->GetShaderData(), uiShaderSize );
	Asura_FileAccess::Close( uHandle );

	return true;
}

void Tools_PS3_ShaderExport::Cleanup( void )
{
	s_xBotanicalsShaderTree.Traverse( TreeReset );
	s_xBotanicalsShaderTree.Reset();

	s_xEnvironmentShaderTree.Traverse( TreeReset );
	s_xEnvironmentShaderTree.Reset();

	s_xObjectShaderTree.Traverse( TreeReset );
	s_xObjectShaderTree.Reset();

	s_xSmoothSkinShaderTree.Traverse( TreeReset );
	s_xSmoothSkinShaderTree.Reset();

	s_xOthersShadersTree.Traverse( TreeReset );
	s_xOthersShadersTree.Reset();
}

void Tools_PS3_ShaderExport::ExportShaders( const HANDLE hOutput )
{
	Asura_Chunk_Memory_Stream xStream;

    // Don't write out empty chunks. This also prevents shader chunks
    // appearing in irrelevant files, eg. GUI menu exports.
    if( !s_xBotanicalsShaderTree.GetNumberOfNodes()
        && !s_xEnvironmentShaderTree.GetNumberOfNodes()
        && !s_xObjectShaderTree.GetNumberOfNodes()
        && !s_xSmoothSkinShaderTree.GetNumberOfNodes()
        && !s_xOthersShadersTree.GetNumberOfNodes() )
    {
        return;
    }

	if( Tools_Level_Export::IsLevelExport() )
	{
		Tools_PS3_ShaderExport::AddSkyboxShader();
	}

    xStream.WriteResourceChunk_Start( ASURA_RESOURCEFILE_TYPE_PLATFORMSPECIFIC, ASURA_RESOURCEFILE_TYPE_PS3_SHADERS, ASURA_PS3_SHADERS_CHUNK_VERSION, 0, "PS3_EXPORT_TIME_SHADERS" );

	ExportShaderTree( s_xBotanicalsShaderTree,	xStream );
	ExportShaderTree( s_xEnvironmentShaderTree,	xStream );
	ExportShaderTree( s_xObjectShaderTree,		xStream );
	ExportShaderTree( s_xSmoothSkinShaderTree,	xStream );
	ExportShaderTree( s_xOthersShadersTree,		xStream );

	xStream.WriteResourceChunk_End();

	const void* pData = xStream.GetStartAddress();
	u_int uChunkSize = xStream.GetPosition();
	unsigned long uBytesWritten = 0;

	WriteFile( hOutput, pData, uChunkSize, &uBytesWritten, NULL );

}

void Tools_PS3_ShaderExport::ExportShaderTree( Asura_PS3_FPTree& xTree, Asura_Chunk_Memory_Stream& xStream )
{
	int iNumberOfNodes	= xTree.GetNumberOfNodes();
	int iNodeCount		= 0;

	xStream << iNumberOfNodes;

	for ( Asura_TemplateHashTree_Iterator<Asura_PS3_Shader*> xIt(xTree); !xIt.Done(); xIt.Next(), ++iNodeCount )
	{
		Asura_PS3_Shader* pxShader = (*xIt.GetCurrent());

		Asura_Hash_ID	uHashID		= pxShader->GetShaderID();
		u_int			uShaderSize	= pxShader->GetShaderSize();

		xStream << uShaderSize;
		if( uShaderSize > 0 )
		{
			xStream << uHashID;
			xStream.Write( uShaderSize, pxShader->GetShaderData() );
		}
		else
		{
			Asura_Assert( false, "Invalid shader data. Possible wrong compilation or out of memory." );
		}
	}
	if( iNodeCount != iNumberOfNodes )
	{
		Asura_Assert( false, "Invalid number of shaders to output" );
	}
}

#endif //#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
