//
// Asura_PS3_GCM_ShadersLibrary.cpp
//
// Created by KJFL - 22 August 2005
// Modified by Catalin Arsenescu - 29 March 2007
//
// Copyright (C) 2005 Rebellion, All Rights Reserved.
//

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

#include "Core/Asura.h"

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

// Engine independent.
#include "Debugging/Asura_Assert.h"
#include "Core/Asura_MaterialResponse_System.h"
#include "Rendering/Asura_PS3_MaterialResponse_System.h"

#if defined(ASURA_PS3_ALLOW_SHADER_RELOAD)
#include "Cg/cgc.h"
#include "ChunkSystem/Asura_Chunk_ResourceFile_PS3.h"
#include "FileAccess/Asura_PS3_FileSystem.h"
#include "Interface/Asura_CommandConsole.h"
#include "Rendering/ShadersDefinition/fp/Water_FP.h"
#endif

// C L A S S   D E C L A R A T I O N S ///////////////////////////////////////

FPListTree			Asura_PS3_GCM_ShadersLibrary::m_xFPTreesPermanent;
FPListTree			Asura_PS3_GCM_ShadersLibrary::m_xFPTreesResettableOriginal;
FPListTree*			Asura_PS3_GCM_ShadersLibrary::m_pxFPTreesResettable = &Asura_PS3_GCM_ShadersLibrary::m_xFPTreesResettableOriginal;

static Asura_Mutex	g_xMutex;

static FPListTree*	g_pxFPDevShaderTree = NULL;
static bool g_bShowDevShaders = false;

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

#if defined(ASURA_PS3_ALLOW_SHADER_RELOAD)

//settings for loading dev shaders
//note this assumes PS3's home dir is set to your Asura_Root

#define ASURA_DEV_SHADER_BASE_PATH "~/Asura/PS3/Shaders/"
#define ASURA_DEV_SHADER_PATH ASURA_DEV_SHADER_BASE_PATH"FragmentPrograms/ExportTime/"

#define ASURA_DEV_SHADER_INCLUDE_PATH ASURA_DEV_SHADER_BASE_PATH "Headers/"

static u_int const g_uNumShaderTypes = 8;
static char const * const g_aszShaderSourceFilenames[g_uNumShaderTypes] = {
	ASURA_DEV_SHADER_PATH"Botanicals.ps3FX",
	ASURA_DEV_SHADER_PATH"Env_Forward_FP.ps3FX",
	ASURA_DEV_SHADER_PATH"Env_FP.ps3FX",
	ASURA_DEV_SHADER_PATH"Env_Water_FP.ps3FX",
	ASURA_DEV_SHADER_PATH"Object_Forward_FP.ps3FX",
	ASURA_DEV_SHADER_PATH"Object_FP.ps3FX",
	ASURA_DEV_SHADER_PATH"SmoothSkin_Forward_FP.ps3FX",
	ASURA_DEV_SHADER_PATH"SmoothSkin_FP.ps3FX"
};
static u_int const g_auShaderTreeHash[g_uNumShaderTypes] = {
	ASURA_SHADER_HASH_BOTANICALS,
	ASURA_SHADER_HASH_ENVIRONMENT,
	ASURA_SHADER_HASH_ENVIRONMENT,
	ASURA_SHADER_HASH_ENVIRONMENT,
	ASURA_SHADER_HASH_OBJECTS,
	ASURA_SHADER_HASH_OBJECTS,
	ASURA_SHADER_HASH_CHARACTER,
	ASURA_SHADER_HASH_CHARACTER
};
static u_int const g_auShaderAdditionalHash[g_uNumShaderTypes] = {
	ASURA_HASH_ID_UNSET,
	ASURA_PS3_SHADER_FORWARD,
	ASURA_HASH_ID_UNSET,
	ASURA_PS3_SHADER_WATER,
	ASURA_PS3_SHADER_FORWARD,
	ASURA_HASH_ID_UNSET,
	ASURA_PS3_SHADER_FORWARD,
	ASURA_HASH_ID_UNSET
};

static u_int const g_uNumDefines = 33;
static char const * const g_aszDefineArgs[g_uNumDefines] = {
	//these first few are in the same order as the lower bits in the hash (see Tools_PS3_ShaderExport::CreateHashID)
	"-DUSE_ALBEDO_1=true",
	"-DUSE_ALBEDO_2=true",
	"-DUSE_NORMAL_1=true",
	"-DUSE_NORMAL_2=true",
	"-DUSE_SPECULAR_1=true",
	"-DUSE_SPECULAR_2=true",
	"-DUSE_TRANSPARENCY_1=true",
	"-DUSE_TRANSPARENCY_2=true",
	"-DUSE_SELFILLUMINATION_1=true",
	"-DUSE_SELFILLUMINATION_2=true",
	"-DUSE_ENVIRONMENT_1=true",
	"-DUSE_ENVIRONMENT_2=true",
	"-DUSE_REFLECTIVITY_1=true",
	"-DUSE_REFLECTIVITY_2=true",
	"-DUSE_PARALLAX_1=true",
	"-DUSE_PARALLAX_2=true",
	"-DUSE_ALBEDO_DETAIL_MAP_1=true",
	"-DUSE_ALBEDO_DETAIL_MAP_2=true",
	"-DUSE_NORMAL_DETAIL_MAP_1=true",
	"-DUSE_NORMAL_DETAIL_MAP_2=true",

	//not presently using these ones, since they're just for shader optimisations, but include them so indices are the same as the tools export (makes it easier to maintain the code)
	"-DCOLOURISE_DIFFUSE=0",
	"-DCOLOURISE_DIFFUSE=1",
	"-DCOLOURISE_SPECULAR=0",
	"-DCOLOURISE_SPECULAR=1",
	"-DMETALICITY=0",
	"-DMETALICITY=1",

	//these ones are used
	"-DUSE_BLEND_INTO_ALBEDO=true",
	"-DUSE_RESPONSE=false",
	"-DASURA_USE_HDR=1",
	"-DUSE_FOG=true",
	"-DUSE_DAMAGE_MAPS=true",
	"-DASURA_PS3_USE_32BIT_RENDER_TARGET=1",
	
	"-DUSE_SPECULAR=false"
};

//Compile args
//from Tools_PS3_Shader_Export.cpp:
//	s_szCommandLine = "sce-cgc -p sce_fp_rsx -mcgb -fno-max-psize -po OutColorPrec=fp16 -texformat d RGBA8 -texformat 15 FLOAT_RGBA16 -fuse-nrmh -nobcolor -fastmath -fastprecision -inline all -unroll all -nofloatbindings -ifcvt all ";
static u_int const g_uNumSharedCompileArgs = 13;
static char const * g_aszCompileArgs[g_uNumSharedCompileArgs+g_uNumDefines+1] = {
	//the array points to these static const strings, with space at the end of the array to dynamically point at the defines for each shader
	//i.e. it's not a static const array, but implicitly these strings are static consts. enjoy. -BW
	"-mcgb",
	"-fno-max-psize",
	"-po OutColorPrec=fp16",
	"-texformat d RGBA8",
	"-texformat 15 FLOAT_RGBA16",
	"-fuse-nrmh",
	"-nobcolor",
	"-fastmath",
	"-fastprecision",
	"-inline all",
	"-unroll all",
	"-nofloatbindings",
	"-ifcvt all"
	//the rest of the array is available for the defines
};

#endif //#if defined(ASURA_PS3_ALLOW_SHADER_RELOAD)

// global shaders
extern u_int _binary_PS3_Blank_FP_bin_start;
extern u_int _binary_PS3_Blank_FP_bin_end;

extern u_int _binary_PS3_Blank_MRT_FP_bin_start;
extern u_int _binary_PS3_Blank_MRT_FP_bin_end;

extern u_int _binary_PS3_Simple_AlphaTest_FP_bin_start;
extern u_int _binary_PS3_Simple_AlphaTest_FP_bin_end;

class BlankMrtHandles : public Asura_PS3_FP_Handles
{
public:
	BlankMrtHandles() : Asura_PS3_FP_Handles( 1, 0 )
	{}

	void InitialiseFromShader( Asura_PS3_FragmentShaderBase* pxShader )
	{
		pxShader->InitUniformElement( 0, "g_axClearValue", 3 );
	}
};



void TraverseShutdown( Asura_Hash_ID uHash, Asura_PS3_FP_List_Tree** pxFP_List )
{
	(*pxFP_List)->Shutdown();
	delete (*pxFP_List);
	(*pxFP_List) = NULL;
}

void TraverseEndFrame( Asura_Hash_ID uHash, Asura_PS3_FP_List_Tree** pxFP_List )
{
	(*pxFP_List)->EndFrame();
}


void Asura_PS3_GCM_ShadersLibrary::Initialise( void )
{
#if defined(ASURA_PS3_ALLOW_SHADER_RELOAD)
	Asura_CommandConsole::AddCmd(L"Render.ReloadShaders.AllExported", LoadAllShaders, Asura_CommandConsole::REMOVE_AT_SHUTDOWN);
	Asura_CommandConsole::AddCmd(L"Render.ReloadShaders.Water", LoadWaterShaders, Asura_CommandConsole::REMOVE_AT_SHUTDOWN);
	Asura_CommandConsole::AddCmd(L"Render.ReloadShaders.Environment", LoadEnvShaders, Asura_CommandConsole::REMOVE_AT_SHUTDOWN);
	//Asura_CommandConsole::AddCmd(L"Render.ReloadShaders.TEST", TestShaderLoad, Asura_CommandConsole::REMOVE_AT_SHUTDOWN);
	Asura_CommandConsole::AddCmd(L"Render.ReloadShaders.ToggleShaders", ToggleDevShaders, Asura_CommandConsole::REMOVE_AT_SHUTDOWN);
#endif

	Asura_PS3_GCM_ShadersLibrary::AddFP( ASURA_SHADER_HASH_GLOBAL, ASURA_GLOBAL_SHADER_BLANK, (unsigned char *)&_binary_PS3_Blank_FP_bin_start, (unsigned char *)&_binary_PS3_Blank_FP_bin_end-(unsigned char *)&_binary_PS3_Blank_FP_bin_start, NULL, 1, true );
	Asura_PS3_GCM_ShadersLibrary::AddFP( ASURA_SHADER_HASH_GLOBAL, ASURA_GLOBAL_SHADER_BLANK_MRT, (unsigned char *)&_binary_PS3_Blank_MRT_FP_bin_start, (unsigned char *)&_binary_PS3_Blank_MRT_FP_bin_end-(unsigned char *)&_binary_PS3_Blank_MRT_FP_bin_start, new BlankMrtHandles, 1, true );
	Asura_PS3_GCM_ShadersLibrary::AddFP( ASURA_SHADER_HASH_GLOBAL, ASURA_GLOBAL_SHADER_ALPHA_TEST, (unsigned char *)&_binary_PS3_Simple_AlphaTest_FP_bin_start, (unsigned char *)&_binary_PS3_Simple_AlphaTest_FP_bin_end-(unsigned char *)&_binary_PS3_Simple_AlphaTest_FP_bin_start, new Asura_PS3_MaterialResponse_Handles, 1, true );
}

void Asura_PS3_GCM_ShadersLibrary::Reset( void )
{
	g_xMutex.Lock();

	m_xFPTreesResettableOriginal.Traverse( TraverseShutdown );
	m_xFPTreesResettableOriginal.Reset();

	DestroyDevShaders();

	g_xMutex.Unlock();
}


void Asura_PS3_GCM_ShadersLibrary::Shutdown( void )
{
	g_xMutex.Lock();

	m_xFPTreesResettableOriginal.Traverse( TraverseShutdown );
	m_xFPTreesResettableOriginal.Reset();
	m_xFPTreesPermanent.Traverse( TraverseShutdown );
	m_xFPTreesPermanent.Reset();
	
	DestroyDevShaders();

	g_xMutex.Unlock();
}

void Asura_PS3_GCM_ShadersLibrary::EndFrame( void )
{
	g_xMutex.Lock();

	m_xFPTreesPermanent.Traverse( TraverseEndFrame );
	m_pxFPTreesResettable->Traverse( TraverseEndFrame );

	g_xMutex.Unlock();
}

Asura_PS3_FP_List_Tree* Asura_PS3_GCM_ShadersLibrary::GetPermanentFPTree( Asura_Hash_ID uHash )
{
	Asura_PS3_FP_List_Tree** ppxListTree = m_xFPTreesPermanent.GetDataByHash( uHash );

	if( ppxListTree )
	{
		return ( *ppxListTree );
	}

	Asura_Assertf( false, ( "Shader tree hash %X not found", uHash ) );
	return NULL;
}

Asura_PS3_FP_List_Tree* Asura_PS3_GCM_ShadersLibrary::GetResettableFPTree( Asura_Hash_ID uHash )
{
	Asura_PS3_FP_List_Tree** ppxListTree = m_pxFPTreesResettable->GetDataByHash( uHash );

	if( ppxListTree )
	{
		return ( *ppxListTree );
	}

	Asura_Assertf( false, ( "Shader tree hash %X not found", uHash ) );
	return NULL;
}

void Asura_PS3_GCM_ShadersLibrary::AddFP( Asura_Hash_ID uHashTree, Asura_Hash_ID uHashFP, void* uStartProgram, u_int uProgramSize, Asura_PS3_FP_Handles* pxHandles, u_int uiNumberOfElements, bool bPermanent )
{
	Asura_PS3_FP_List_Tree** ppxListTree = bPermanent ? m_xFPTreesPermanent.GetDataByHash( uHashTree ) : m_pxFPTreesResettable->GetDataByHash( uHashTree );

	if( ppxListTree == NULL )
	{
		g_xMutex.Lock();
		ppxListTree = bPermanent ? m_xFPTreesPermanent.AddNode( uHashTree ) : m_pxFPTreesResettable->AddNode( uHashTree );;
		*ppxListTree = new Asura_PS3_FP_List_Tree();
		g_xMutex.Unlock();
		if( *ppxListTree == NULL )
		{
			Asura_Assert( false, "Failed to create a new shader tree list. Possible out of memory" );
			return;
		}
	}

	FPTree* fpTree = (*ppxListTree)->GetTree();
	Asura_PS3_FP_List** fpList = fpTree->GetDataByHash( uHashFP );

	if( fpList == NULL )
	{
		g_xMutex.Lock();
		fpList = fpTree->AddNode( uHashFP );
		*fpList = new Asura_PS3_FP_List( uStartProgram, uProgramSize, pxHandles, uiNumberOfElements );
		Asura_Assert( *fpList, "Failed to create a new shader list. Possible out of memory" );
		g_xMutex.Unlock();
	}
	else
	{
		if( pxHandles )
		{
			delete pxHandles;
		}
	}
}

Asura_PS3_FragmentShaderCopy* Asura_PS3_GCM_ShadersLibrary::GetPermanentFP( Asura_Hash_ID uHashTree, Asura_Hash_ID uHashFP )
{
	Asura_PS3_FP_List_Tree** ppxListTree = m_xFPTreesPermanent.GetDataByHash( uHashTree );

	if( ppxListTree )
	{
		return ( *ppxListTree )->GetShader( uHashFP );
	}

	Asura_Assertf( false, ( "Shader tree hash %X not found", uHashTree ) );
	return NULL;
}

Asura_PS3_FragmentShaderCopy* Asura_PS3_GCM_ShadersLibrary::GetResettableFP( Asura_Hash_ID uHashTree, Asura_Hash_ID uHashFP )
{
	Asura_PS3_FP_List_Tree** ppxListTree = m_pxFPTreesResettable->GetDataByHash( uHashTree );

	if( ppxListTree )
	{
		return ( *ppxListTree )->GetShader( uHashFP );
	}

	Asura_Assertf( false, ( "Shader tree hash %X not found", uHashTree ) );
	return NULL;
}

void Asura_PS3_GCM_ShadersLibrary::DestroyDevShaders( void )
{
#if defined(ASURA_PS3_ALLOW_SHADER_RELOAD)
	if ( m_pxFPTreesResettable != &m_xFPTreesResettableOriginal )
	{
		m_pxFPTreesResettable->Reset();
		delete m_pxFPTreesResettable;
	}
#endif
}

#if defined(ASURA_PS3_ALLOW_SHADER_RELOAD)

//
// returns pointer to start of code (not at start of buffer), or NULL if load failed.
//
char* Asura_PS3_GCM_ShadersLibrary::LoadSourceCode( const char* szFilename, char* pSourceBuffer, size_t uSourceBufferSize, const char ** szAdditionalIncludePaths )
{
	//-------------------------------------------------------------------
	// Load the source code
	//-------------------------------------------------------------------

	//Asura_OutputToDebugger_Retail(("LoadSourceCode: Attempting to load \"%s\"\n", szFilename));

	Asura_PS3_FileSystem::Handle xFileHandle;
	if ( !Asura_PS3_FileSystem::OpenForReading( szFilename, &xFileHandle ) )
	{
		Asura_Assertf(0,("LoadSourceCode: Can't open shader file \"%s\". Home(~/) directory should be set to wherever ASURA_ROOT is on your local drive.\n", szFilename ));
		return NULL;
	}

	//assume includes come first - *shaders must be written that way*!
	u_int uFileSize = Asura_PS3_FileSystem::GetFileSize( xFileHandle );

	//put it at the end of the buffer, so we can insert include files before it
	char* pSourceStart = pSourceBuffer+uSourceBufferSize-uFileSize-1;
	pSourceStart[uFileSize] = '\0';

	Asura_PS3_FileSystem::ReadData( xFileHandle, uFileSize, pSourceStart );
	Asura_PS3_FileSystem::Close(&xFileHandle);

	//Asura_OutputToDebugger_Retail(("LoadSourceCode: Loaded\n"));
	//Asura_OutputToDebugger_Retail(("LoadSourceCode: Scanning for #includes\n"));


	//-------------------------------------------------------------------
	// Load included files, at the start of the source code
	//-------------------------------------------------------------------

	/*
	>1/ find *last* #include
	>2/ open that file
	>3/ read the size & set write pos (= end of include - size)
	>4/ memmove everything up to the #include back to end at start of included file (and set new curpos)
	>5/ read the file
	>6/ goto 1/
	*/

	while ( true )
	{
		//find the last #include in current source code
		char* pStrPos = pSourceStart;
		char* pIncludeStart = NULL;
		char* pIncludeEnd = NULL;

		while ( NULL != (pStrPos = strstr( pStrPos, "#include" ) ) )
		{
			//BW try to avoid reading commented out includes. this is obviously not robust, I ain't trying to write a preprocessor here.
			if ( pStrPos == pSourceStart || pStrPos[-1] != '/' ) pIncludeStart = pStrPos;
			pStrPos+=1; //make sure we don't keep finding the same one!
		}
		
		//no more includes, end the loop
		if ( !pIncludeStart )
			break;		
		
		//read the filename
		char *pOpenQuote = NULL;
		char *pCloseQuote = NULL;
		char szIncludeFilename[256];

		if ( NULL != (pOpenQuote = strstr( pIncludeStart, "\"" ))
			&& NULL != (pCloseQuote = strstr( pOpenQuote+1, "\"" )) )
		{
			strncpy( szIncludeFilename, pOpenQuote+1, pCloseQuote-pOpenQuote-1 );
			szIncludeFilename[pCloseQuote-pOpenQuote-1] = '\0';

			pIncludeEnd = pCloseQuote+1;
		}				
		else
		{
			//couldn't read the include for some reason
			//truncate the string we're about to dump, so we don't dump the whole shader to TTY
			pSourceStart[64] = '\0';
			Asura_OutputToDebugger_Retail(("LoadSourceCode: failed to read #included file name, expected format \"#include \"%%s\"\".\nError when reading this code:\n%s\n",pSourceStart));
			Asura_Assert(0, "LoadSourceCode: Got confused reading #include file's name, look at TTY");
			return NULL;
		}

		//include the file at that point (move everything before the #include, including possibly earlier #includes, to be before the #included file.
		//Asura_OutputToDebugger_Retail(("LoadSourceCode: Attempting to include \"%s\"\n", szIncludeFilename));

		Asura_PS3_FileSystem::Handle xIncludeFileHandle;

		char szIncludeFilenameWithPath[256];
		strcpy( szIncludeFilenameWithPath, ASURA_DEV_SHADER_INCLUDE_PATH );
		strcat( szIncludeFilenameWithPath, szIncludeFilename );
		
		bool bSkipInclude = false;
		int i=0;
		while ( !Asura_PS3_FileSystem::OpenForReading( szIncludeFilenameWithPath, &xIncludeFileHandle ) )
		{
			//not found, try in the additional include dir
			if ( !szAdditionalIncludePaths || !szAdditionalIncludePaths[i] )
			{
				Asura_Assertf(0,("LoadSourceCode: Can't open include file \"%s\". Check ASURA_DEV_SHADER_INCLUDE_PATH\n", szIncludeFilename ));
				//bSkipInclude = true; // see if it will compile without it
				//break;
				return NULL;
			}
			strcpy( szIncludeFilenameWithPath, szAdditionalIncludePaths[i++] );
			strcat( szIncludeFilenameWithPath, szIncludeFilename );
		}

		if ( bSkipInclude )
		{
			// remove the #include line
			pIncludeStart[0] = '/';
			pIncludeStart[1] = '/';
			continue;
		}
		
		u_int uIncludeFileSize = Asura_PS3_FileSystem::GetFileSize( xIncludeFileHandle );
		
		//move everything before #include to be before the included file
		u_int uCopySize = pIncludeStart - pSourceStart;
		char *pDest = pIncludeEnd - uIncludeFileSize - uCopySize;

		Asura_Assert( pDest >= pSourceBuffer, "LoadSourceCode: Oh bugger we ran out of sourcecode memory. How big are your include files?!" );
		
		Asura_Mem::Move( pDest, pSourceStart, uCopySize );
		pSourceStart = pDest;
		
		pDest += uCopySize;
		Asura_PS3_FileSystem::ReadData( xIncludeFileHandle, uIncludeFileSize, pDest );

		Asura_PS3_FileSystem::Close(&xIncludeFileHandle);

	}

	//sanity check, dump the whole thing to tty.
	/*static bool bOnce=true;
	if ( bOnce )
	{
	bOnce = false;
	Asura_OutputToDebugger(("**********DUMPING SHADER SOURCE CODE*************\n"));
	printf(pSourceStart); //outputtodebugger can't handle it which is no surprise, it is several k
	Asura_OutputToDebugger(("**********DUMPED SHADER SOURCE CODE*************\n"));
	}*/

	return pSourceStart;
}

//---------------------------------------------------------------
// Compile a shader to replace one already in the shader tree
//
// szFilename is the full path (i.e. must start with ~/ or app_home/)
// if uHashTree is ASURA_HASH_ID_UNSET the shader will not be placed in a shader tree (meaning you'll have to hang onto the pointer and use that)
// if szArgs is NULL it will use some locally defined default compiler args
//
// returns a pointer to the shader (either a Asura_PS3_FragmentShader if no uHashTree was given or a Asura_PS3_FragmentShaderCopy if the shaders in the tree)
//---------------------------------------------------------------
void* Asura_PS3_GCM_ShadersLibrary::LoadAndCompileShader( char *szFilename, Asura_Hash_ID uHashTree, Asura_Hash_ID uHashFP, const char ** aszArgs, const char ** aszAdditionalIncludePaths, Asura_PS3_FP_Handles *pHandles )
{
	//default compile args
	//based on AVP settings on 12/5/2008
	static const char* aszDefaultArgs[] = {
		"-mcgb",
		"-ifcvt none",
		"-texformat d FLOAT_RGBA16",
		"-fuse-nrmh",
		"-nobcolor",
		"-fno-max-psize",
		"-po OutColorPrec=fp16",
		"-fastmath",
		"-fastprecision",
		"-inline all",
		"-unroll all",
		"-nofloatbindings",
		NULL
	};

	const u_int uSourceBufferSize = 64*1024; //MaterialResponse_Shared.ps3FXH is 32KB, so make sure there's more room than that.
	char* pSourceBuffer = static_cast<char*>(Asura_Allocate(uSourceBufferSize));

	char* pCodeStart = LoadSourceCode( szFilename, pSourceBuffer, uSourceBufferSize, aszAdditionalIncludePaths );
	
	if ( !pCodeStart )
	{
		Asura_Deallocate(pSourceBuffer);
		return NULL;
	}
	
	if ( aszArgs == NULL )
	{
		//use local compile args
		aszArgs = aszDefaultArgs;
	}

	char *pcTempOutputBuffer = NULL;
	int iErr = compile_program_from_string(
		pCodeStart,
		"sce_fp_rsx",
		NULL, //defaults to "main"
		aszArgs,
		&pcTempOutputBuffer
		);

	Asura_Deallocate(pSourceBuffer);

	if ( iErr != 0 || pcTempOutputBuffer == NULL )
	{
		Asura_Assertf(0,("LoadAndCompileShader: Shader compilation failed - shader %s\n", szFilename ));
		Asura_OutputToDebugger_Retail(("LoadAndCompileShader: Shader compilation failed - shader %s\n", szFilename ));
		return NULL;
	}

	u_int uProgramSize = cellCgbGetSize(pcTempOutputBuffer);
	
	Asura_OutputToDebugger_Retail(( "Shader compiled size %d bytes - %s\n", uProgramSize, szFilename ));

	void *pFP = NULL;
	if ( uHashTree == ASURA_HASH_ID_UNSET )
	{
		pFP = new Asura_PS3_FragmentShader( reinterpret_cast<unsigned char*>(pcTempOutputBuffer), reinterpret_cast<unsigned char*>(pcTempOutputBuffer+uProgramSize), pHandles );
	}
	else
	{
		bool bPermanent = false;
		Asura_PS3_FP_List_Tree** ppxListTree = m_pxFPTreesResettable->GetDataByHash( uHashTree );
		if ( !ppxListTree )
		{
			ppxListTree = m_xFPTreesPermanent.GetDataByHash( uHashTree );
			bPermanent = true;
		}
		Asura_Assertf(ppxListTree, ("CompileShader: Tree hash 0x%08x does not exist", uHashTree));

		//if the previous shader exists, delete it
		(*ppxListTree)->GetTree()->DeleteNodeByHash(uHashFP);

		//put it in the tree
		//N.B. AddFP will copy the shader from it's current temporary location
		AddFP( uHashTree, uHashFP, pcTempOutputBuffer, uProgramSize, pHandles, 2, bPermanent );

		if ( bPermanent )
		{
			pFP = GetPermanentFP( uHashTree, uHashFP );
		}
		else
		{
			pFP = GetResettableFP( uHashTree, uHashFP );
		}
	}

	free_compiled_program(pcTempOutputBuffer);
	
	return pFP;
}


//---------------------------------------------------------------
// Reload the export time shaders
// uHashType - e.g. ASURA_SHADER_HASH_ENVIRONMENT to load environment shaders, 0 to load all shaders
// uAdditionalHash - e.g. ASURA_PS3_SHADER_WATER to load any water shaders, 0 to load all types
//---------------------------------------------------------------
bool Asura_PS3_GCM_ShadersLibrary::LoadDevShaders( u_int uHashType, u_int uAdditionalHash )
{
	if ( !g_bShowDevShaders )
	{
		ToggleDevShaders(false); // make sure dev shaders are showing
		g_bShowDevShaders = true;
	}

	//delete the previous dev shaders (if there are any)
	//hopefully this should mean we keep reusing the same memory, which will be useful for people developing & constantly reloading shaders. 
	DestroyDevShaders();

	//-------------------------------------------------------------------
	// Create a new, blank, tree
	//-------------------------------------------------------------------

	g_pxFPDevShaderTree = new FPListTree;
	m_pxFPTreesResettable = g_pxFPDevShaderTree;
	
	const u_int uSourceBufferSize = 64*1024;
	char* pSourceBuffer = static_cast<char*>(Asura_Allocate(uSourceBufferSize)); //MaterialResponse_Shared.ps3FXH is 32KB, so make sure there's more room than that.
	
	Asura_Assert( pSourceBuffer, "LoadDevShaders: Failed to allocate source-code buffer" );


	//-------------------------------------------------------------------
	// Compile a shader for every hash in the tree
	//-------------------------------------------------------------------

	bool bErrors = false;
	for ( u_int uShaderType=0; uShaderType < g_uNumShaderTypes; uShaderType++ )
	{
		//get the original tree corresponding to this shader type
		Asura_PS3_FP_List_Tree** ppxList = m_xFPTreesResettableOriginal.GetDataByHash(g_auShaderTreeHash[uShaderType]);
		if ( !ppxList )
		{
			Asura_OutputToDebugger_Retail(("LoadDevShaders: Skipping unused shader \"%s\" (no shader tree)\n", g_aszShaderSourceFilenames[uShaderType]));
			continue; //BW yeah it's a goto
		}

		FPTree* pxTree = (*ppxList)->GetTree();


		//-------------------------------------------------------------------
		// Load Source Code
		//-------------------------------------------------------------------
		const char* pCurPos = LoadSourceCode( g_aszShaderSourceFilenames[uShaderType], pSourceBuffer, uSourceBufferSize, NULL );
		if ( !pCurPos ) continue;
		

		//-------------------------------------------------------------------
		// Compile 1 shader for each Hash
		//-------------------------------------------------------------------
		
		int iNumNodes = pxTree->GetNumberOfNodes();
		Asura_Hash_ID* auHashList = pxTree->MakeHashList();
		for ( int iNode=0; iNode<iNumNodes; iNode++ )
		{
			Asura_Hash_ID uHash = auHashList[iNode];
		
			if ( (uHash&g_auShaderAdditionalHash[uShaderType]) == g_auShaderAdditionalHash[uShaderType] ) //skip water/forward shaders unless that's what we just loaded the source for
			{

				// only compile the shaders requested
				if ( ( uHashType == 0 || g_auShaderTreeHash[uShaderType] == uHashType )
					&& ( uAdditionalHash == 0 || g_auShaderAdditionalHash[uShaderType] == uAdditionalHash ) )
				{
					//-------------------------------------------------------------------
					// Set compile options
					//-------------------------------------------------------------------
				
					//get #defines options from hash
					char const **currarg = &g_aszCompileArgs[g_uNumSharedCompileArgs];
					u_int uBit=0;
					for ( ; uBit < 2*Asura_MaterialResponse::NUMBER_OF_TEXTURE_SLOTS; uBit++ )
					{
						if ( uHash & (1<<uBit) )
						{
							*(currarg++) = g_aszDefineArgs[uBit]; //set the char* pointer, no need to copy the string (they're all static consts being pointed to by arrays of pointers)
						}
					}
					
					// colourise diffuse + metalicity + specular
					for ( u_int i=0; i < 3; i++ )
					{
						switch ( uHash&(3<<uBit) )
						{
							case 0:	*(currarg++) = g_aszDefineArgs[uBit]; break;
							case 1:	*(currarg++) = g_aszDefineArgs[uBit+1]; break;
							default: break; //don't define anything, it defaults to defining 2 (this is how the tools work)
						}
						uBit += 2;
					}
					if ( uHash&(1<<uBit) ) *(currarg++) = g_aszDefineArgs[32]; 
					
					//additional #defines
//					u_int uIdx = 2*Asura_MaterialResponse::NUMBER_OF_TEXTURE_SLOTS; //from this point on the index to aszDefineArgs doesn't match hash bit, no good reason why it shouldn't
					if ( uHash & ASURA_PS3_SHADER_BLEND_INTO_ALBEDO )	*(currarg++) = g_aszDefineArgs[26];
					if ( uHash & ASURA_PS3_SHADER_FOG )					*(currarg++) = g_aszDefineArgs[29];
					if ( uHash & ASURA_PS3_SHADER_DAMAGE_MAP )			*(currarg++) = g_aszDefineArgs[30];

#ifdef ASURA_USE_HDR
					*(currarg++) = g_aszDefineArgs[ 28 ];
#endif
#ifdef ASURA_PS3_USE_32BIT_RENDER_TARGET
					*(currarg++) = g_aszDefineArgs[ 31 ];
#endif
#ifdef ASURA_USE_PS3_OLD_MATERRIAL_RESPONSE_SHADER_EXPORT
					*(currarg++) = g_aszDefineArgs[ 27 ];
#endif

					//put a terminator on the arg list
					*currarg = NULL;



					//-------------------------------------------------------------------
					// Compile the shader
					//-------------------------------------------------------------------

					//Asura_OutputToDebugger_Retail(("compiling for hash %08x\n", uHash));
			
					char *pcTempOutputBuffer = NULL;
					int iErr = compile_program_from_string(
							pCurPos,
							"sce_fp_rsx",
							NULL, //defaults to "main"
							g_aszCompileArgs,
							&pcTempOutputBuffer
						);
					
					if ( iErr != 0 || pcTempOutputBuffer == NULL )
					{
						Asura_Assertf(0,("LoadDevShaders: Shader compilation failed - Hash %08x, shader %s\n", uHash, g_aszShaderSourceFilenames[uShaderType] ));
						Asura_OutputToDebugger_Retail(("LoadDevShaders: Shader compilation failed - Hash %08x, shader %s\n", uHash, g_aszShaderSourceFilenames[uShaderType] ));
						bErrors = true;
						break; //stop trying - alternatively we could substitute each failed shader with a duplicate of the one loaded with the level, but that could get confusing.
					}


					//-------------------------------------------------------------------
					// Put it in the tree
					//-------------------------------------------------------------------
					
					//put it in the tree
					u_int uProgramSize = cellCgbGetSize(pcTempOutputBuffer);

					Asura_OutputToDebugger_Retail(( "Shader compiled size %d bytes - %s(%08x)\n", uProgramSize, g_aszShaderSourceFilenames[uShaderType], uHash ));


					//N.B. AddFP will copy the shader from it's current temporary location
					if ( g_auShaderAdditionalHash[uShaderType] == ASURA_PS3_SHADER_WATER )
					{
						AddFP( g_auShaderTreeHash[uShaderType], uHash, pcTempOutputBuffer, uProgramSize, new Asura_PS3_Water_Handles(), 1, false );
					}
					else
					{
						AddFP( g_auShaderTreeHash[uShaderType], uHash, pcTempOutputBuffer, uProgramSize, new Asura_PS3_MaterialResponse_Handles(), 2, false );
					}

					free_compiled_program(pcTempOutputBuffer);


				} // if (this == uHashType)
				else
				{
// 					//refer to the previous one
// 					//*m_pxFPTreesResettable->AddOrFindNode(g_auShaderTreeHash[uShaderType]) = *m_xFPTreesResettableOriginal.GetDataByHash(g_auShaderTreeHash[uShaderType]);
// 					//AddFP( g_auShaderTreeHash[uShaderType], uHash, pcTempOutputBuffer, uProgramSize, new Asura_PS3_MaterialResponse_Handles(), 2, false );
// 					Asura_PS3_FP_List** ppxOldFPList = (*m_xFPTreesResettableOriginal.GetDataByHash(g_auShaderTreeHash[uShaderType]))->GetTree()->GetDataByHash(uHash);
// 					bool bDidExist;
// 					Asura_PS3_FP_List_Tree** ppxFPListTree = m_pxFPTreesResettable->AddOrFindNode(g_auShaderTreeHash[uShaderType], &bDidExist);
// 					if ( bDidExist )
// 					{
// 						(*(*ppxFPListTree)->GetTree()->AddOrFindNode(uHash)) = *ppxOldFPList;
// 					}
// 					else
// 					{
// 						//duh...
// 					}
				}
			} // if ( (uHash&g_auShaderAdditionalHash[uShaderType]) == g_auShaderAdditionalHash[uShaderType] )			
		} //for iNode
	
		//BW memory leaks I know about:
		//  Asura_PS3_MaterialResponse_Handles get new'd but not deleted
		//  auHashList doesn't let me delete it for some reason
		//not worth fixing since recompiling shaders is a dev-only thing, obviously.
		
	} //for ( u_int uShaderType=0; uShaderType < g_uNumShaderTypes; uShaderType++ )

	Asura_Deallocate(pSourceBuffer);

	if ( bErrors )
	{
		Asura_Assert(0,"LoadDevShaders: Errors were encountered, reverting to original shaders.");
	
		//clean up
		DestroyDevShaders();
		
		//return to using the original shaders (loaded with the level)
		m_pxFPTreesResettable = &m_xFPTreesResettableOriginal;
		g_pxFPDevShaderTree = NULL;
	}

	return bErrors;
}


bool Asura_PS3_GCM_ShadersLibrary::ToggleDevShaders( bool bHelp )
{
	if ( bHelp )
	{
		Asura_CommandConsole::Printf("Currently showing: %s", g_bShowDevShaders ?  "compiled shaders" : "original shaders" );
		return true;
	}

	if ( g_bShowDevShaders ) // currently showing dev shaders
	{
		//return to using the original shaders (loaded with the level)
		m_pxFPTreesResettable = &m_xFPTreesResettableOriginal;
		g_bShowDevShaders = false;
	}
	else
	{
		if ( g_pxFPDevShaderTree )
		{
			m_pxFPTreesResettable = g_pxFPDevShaderTree;
			g_bShowDevShaders = true;
		}
	}
	
	return true;
}


bool Asura_PS3_GCM_ShadersLibrary::LoadAllShaders( bool bHelp )
{
	if ( bHelp )
	{
		Asura_CommandConsole::Printf("Recompile & reload all this level's \"ExportTime\" shaders, might be slow.");
		return true;
	}

	return LoadDevShaders( 0, 0 );
}

bool Asura_PS3_GCM_ShadersLibrary::LoadWaterShaders( bool bHelp )
{
	if ( bHelp )
	{
		Asura_CommandConsole::Printf("Recompile & reload all this level's water shaders");
		return true;
	}

	return LoadDevShaders( 0, ASURA_PS3_SHADER_WATER );
}

bool Asura_PS3_GCM_ShadersLibrary::LoadEnvShaders( bool bHelp )
{
	if ( bHelp )
	{
		Asura_CommandConsole::Printf("Recompile & reload all this level's environment shaders, might be slow.");
		return true;
	}

	return LoadDevShaders( ASURA_SHADER_HASH_ENVIRONMENT, 0 );
}

bool Asura_PS3_GCM_ShadersLibrary::TestShaderLoad( bool bHelp )
{
	if ( bHelp )
	{
		Asura_CommandConsole::Printf("Test the shader compiler");
		return true;
	}

	LoadAndCompileShader( "~/Asura/PS3/Shaders/FragmentPrograms/Test/1.fpx", ASURA_HASH_ID_UNSET, ASURA_HASH_ID_UNSET, NULL, NULL, NULL );

	return true;
}

#endif
