
//
// Asura_PS3_DeferredShading.cpp
//
// Created by DMR 13/02/07
//
// Copyright (C) 2007 Rebellion, All Rights Reserved.
//
// Handles deferred lighting passes
//

#include "Core/Asura.h"

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

// Engine independent.
#include "Camera/Asura_Camera.h"
#include "Light/Asura_Photon.h"
#include "Rendering/Asura_DeferredShading.h"
#include "Rendering/Asura_Module.h"
#include "Rendering/Asura_Viewport.h"
#include "Rendering/Asura_Font_System.h"

// Engine platform-specific.
#include "Rendering/Shaders/Asura_PS3_GCM_Shader.h"
#include "Rendering/Asura_PS3_FrameBuffer.h"
#include "Rendering/Asura_PS3_Graphics.h"
#include "Rendering/Asura_PS3_MaterialResponse_System.h"
#include "Rendering/Asura_PS3_QuadOverlayRenderer.h"
#include "Rendering/Asura_PS3_RenderingStates.h"
#include "Rendering/Asura_PS3_RSXProfiling.h"

//Shaders definition
#include "Rendering/ShadersDefinition/VD/DeferredPhoton_VD.h"
#include "Rendering/ShadersDefinition/VP/DeferredPhoton_VP.h"
#include "Rendering/ShadersDefinition/FP/DeferredPhoton_InfiniteLight_FP.h"
#include "Rendering/ShadersDefinition/FP/DeferredPhoton_LimitedLight_FP.h"
#include "Rendering/ShadersDefinition/FP/DeferredPhoton_Photon_FP.h"
#include "Rendering/ShadersDefinition/FP/ClearAlpha_FP.h"
#include "Rendering/ShadersDefinition/FP/OverlayQuad_FP.h"

#define PS3_DEFFERD_SHADING_MAXIMUM_LIGHTS		500

// Redirect the Platform_ functions to our platform-specific class - as we can't add platform-specific
// members to Asura_DeferredShading, and we could end up with a load of globals again...

void Asura_DeferredShading::Platform_Initialise(void)			{Asura_PS3_DeferredShading::Initialise();}
void Asura_DeferredShading::Platform_Shutdown(void)				{Asura_PS3_DeferredShading::Shutdown();}
void Asura_DeferredShading::Platform_Reset(void)				{Asura_PS3_DeferredShading::Reset();}
void Asura_DeferredShading::Platform_RenderPhotons(bool bWithDarkeningShadows, bool bWithRealShadows, bool bWithNoShadows)				{Asura_PS3_DeferredShading::RenderPhotons(bWithDarkeningShadows, bWithRealShadows, bWithNoShadows);}
void Asura_DeferredShading::Platform_RenderDebugOutput(void)	{Asura_PS3_DeferredShading::RenderDebugOutput();}
void Asura_DeferredShading::Platform_BeginRender(void)			{}
void Asura_DeferredShading::Platform_EndRender(void)			{}

Asura_PS3_GCM_IndexBuffer	Asura_PS3_DeferredShading::s_xIndexBuffer;
Asura_PS3_GCM_VertexBuffer	Asura_PS3_DeferredShading::s_xVertexBuffer[ ASURA_DEFERRED_BUFFERS ];

Asura_PS3_GCM_VertexBuffer*	Asura_PS3_DeferredShading::s_apxSphereVB[4];
int							Asura_PS3_DeferredShading::s_aiSphereNumVerts[4]					= {0,0,0,0};

static int	g_iCurrentLight				= 0;
static int	g_iCurrentRenderBuffer		= 0;


enum
{
	// Effect Pass Indices
	DeferredLight_Pass_ClearDestAlpha	= 0,
	DeferredLight_Pass_Photon			= 1,			// point light
	DeferredLight_Pass_Infinite			= 2,			// parallel light
	DeferredLight_Pass_Limited			= 3,			// limited light
};

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

static Asura_PS3_GCM_VertexShader*		g_pxVertexShader			= NULL;
static Asura_PS3_FragmentShader*		g_pxFragmentShaderP0		= NULL;

static Asura_PS3_FragmentShaderCopy*	g_pxFragmentShaderP1		= NULL;
static Asura_PS3_FragmentShaderCopy*	g_pxFragmentShaderP2		= NULL;
static Asura_PS3_FragmentShaderCopy*	g_pxFragmentShaderP3		= NULL;

#ifdef ASURA_DEBUG_VARIABLE_SYSTEM
//#define PHOTON_TESTBED_ENABLED
#endif

#ifdef PHOTON_TESTBED_ENABLED
//for testing - rig stencils as mutex on a switch, so I can compare perf and get upper bound for SPU geom overlap opts
static bool g_bTestStencilMutex = false;
static bool g_bTest2LightShader = false;

extern u_int _binary_PS3_DeferredPhoton_Testbed_FP_bin_start;
extern u_int _binary_PS3_DeferredPhoton_Testbed_FP_bin_end;
#endif

static int g_iSelectedBuffer = 0;

enum FRAGMENT_SHADER_UNIFORM_DEFERRED
{
	FP_InvViewMatrix,
	FP_TwoOverScreen,
	FP_CameraTanHalfFOV,
	FP_Falloff,
	FP_LightPosScale,
	FP_LightColour,
	FP_CameraPosition,
	FP_LightDirection,
	FP_LightOrientation,
	FP_ProjCubeLerpDist,
	FP_DepthAdjustment,

	FP_ELEMENTS
};

enum TEXTURE_UNITS_DEFERRED
{
	FP_MaterialsTexture,
	FP_NormalMaterialBufferSampler,
	FP_AlbedoSpecularBufferSampler,
	FP_DepthBufferSampler,
	FP_ProjCubeMapSampler,
	FP_ProjCubeMapBlurredSampler,

	TU_ELEMENTS
};

class Asura_PS3_DeferredSystem_Handles : public Asura_PS3_FP_Handles
{
public:
	Asura_PS3_DeferredSystem_Handles() : Asura_PS3_FP_Handles( FP_ELEMENTS, TU_ELEMENTS ) {}


	void InitialiseFromShader( Asura_PS3_FragmentShaderBase* pxShader )
	{
		//Uniform parameters
		pxShader->InitUniformElement( FP_InvViewMatrix,		"g_xInvViewMatrix",		4 );
		pxShader->InitUniformElement( FP_TwoOverScreen,		"g_xTwoOverScreen",		1 );
		pxShader->InitUniformElement( FP_CameraTanHalfFOV,	"g_xCameraTanHalfFOV",	1 );
		pxShader->InitUniformElement( FP_Falloff,			"g_xFalloff",			1 );
		pxShader->InitUniformElement( FP_LightPosScale,		"g_xLightPosScale",		1 );
		pxShader->InitUniformElement( FP_LightColour,		"g_xLightColour",		1 );
		pxShader->InitUniformElement( FP_CameraPosition,	"g_xCameraPosition",	1 );
		pxShader->InitUniformElement( FP_LightDirection,	"g_xLightDirection",	1 );
		pxShader->InitUniformElement( FP_LightOrientation,	"g_xLightOrientation",	3 );
		pxShader->InitUniformElement( FP_ProjCubeLerpDist,	"g_fProjCubeLerpDist",	1 );
		pxShader->InitUniformElement( FP_DepthAdjustment,	"g_xDepthAdjustment",	1 );

		//Texture units
		pxShader->InitTextureUnit( FP_MaterialsTexture, "g_xMaterialsTextureSampler", CELL_GCM_TEXTURE_CLAMP, CELL_GCM_TEXTURE_CLAMP, CELL_GCM_TEXTURE_LINEAR, CELL_GCM_TEXTURE_LINEAR );
		pxShader->InitTextureUnit( FP_NormalMaterialBufferSampler, "g_xNormalMaterialBufferSampler", CELL_GCM_TEXTURE_CLAMP, CELL_GCM_TEXTURE_CLAMP, CELL_GCM_TEXTURE_NEAREST, CELL_GCM_TEXTURE_NEAREST );
		pxShader->InitTextureUnit( FP_AlbedoSpecularBufferSampler, "g_xAlbedoSpecularBufferSampler", CELL_GCM_TEXTURE_CLAMP, CELL_GCM_TEXTURE_CLAMP, CELL_GCM_TEXTURE_NEAREST, CELL_GCM_TEXTURE_NEAREST );
		pxShader->InitTextureUnit( FP_DepthBufferSampler, "g_xDepthBufferSampler", CELL_GCM_TEXTURE_CLAMP, CELL_GCM_TEXTURE_CLAMP, CELL_GCM_TEXTURE_NEAREST, CELL_GCM_TEXTURE_NEAREST );
		pxShader->InitTextureUnit( FP_ProjCubeMapSampler, "g_xProjCubeMapSampler", CELL_GCM_TEXTURE_CLAMP, CELL_GCM_TEXTURE_CLAMP, CELL_GCM_TEXTURE_LINEAR, CELL_GCM_TEXTURE_LINEAR
			, true
			);
		pxShader->InitTextureUnit( FP_ProjCubeMapBlurredSampler, "g_xProjCubeMapBlurredSampler", CELL_GCM_TEXTURE_CLAMP, CELL_GCM_TEXTURE_CLAMP, CELL_GCM_TEXTURE_LINEAR, CELL_GCM_TEXTURE_LINEAR
			, true
			);
	}
};

static Asura_PS3_DeferredSystem_Handles g_xHandlesP1;
static Asura_PS3_DeferredSystem_Handles g_xHandlesP2;
static Asura_PS3_DeferredSystem_Handles g_xHandlesP3;

void Asura_PS3_DeferredShading::Initialise(void)
{
	g_pxVertexShader = new Asura_PS3_VP_Deferred();

	g_pxFragmentShaderP0 = new Asura_PS3_FragmentShader( (unsigned char *)&_binary_PS3_DeferredPhoton_P0_FP_bin_start, (unsigned char *)&_binary_PS3_DeferredPhoton_P0_FP_bin_end, NULL );
	Asura_PS3_GCM_ShadersLibrary::AddFP( static_cast<Asura_Hash_ID> ( ASURA_SHADER_HASH_DEFERRED ), 1, (unsigned char *)&_binary_PS3_DeferredPhoton_P1_FP_bin_start, ( (unsigned char *)&_binary_PS3_DeferredPhoton_P1_FP_bin_end - (unsigned char *)&_binary_PS3_DeferredPhoton_P1_FP_bin_start ), new Asura_PS3_DeferredSystem_Handles(), 1, true );
	Asura_PS3_GCM_ShadersLibrary::AddFP( static_cast<Asura_Hash_ID> ( ASURA_SHADER_HASH_DEFERRED ), 2, (unsigned char *)&_binary_PS3_DeferredPhoton_P2_FP_bin_start, ( (unsigned char *)&_binary_PS3_DeferredPhoton_P2_FP_bin_end - (unsigned char *)&_binary_PS3_DeferredPhoton_P2_FP_bin_start ), new Asura_PS3_DeferredSystem_Handles(), 1, true );

#ifdef PHOTON_TESTBED_ENABLED
	Asura_PS3_GCM_ShadersLibrary::AddFP( static_cast<Asura_Hash_ID> ( ASURA_SHADER_HASH_DEFERRED ), 666, (unsigned char *)&_binary_PS3_DeferredPhoton_Testbed_FP_bin_start, ( (unsigned char *)&_binary_PS3_DeferredPhoton_Testbed_FP_bin_end - (unsigned char *)&_binary_PS3_DeferredPhoton_Testbed_FP_bin_start ), new Asura_PS3_DeferredSystem_Handles(), 1, true );
#endif

	Asura_PS3_GCM_ShadersLibrary::AddFP( static_cast<Asura_Hash_ID> ( ASURA_SHADER_HASH_DEFERRED ), 3000, (unsigned char *)&_binary_PS3_DeferredPhoton_P3_000_FP_bin_start, ( (unsigned char *)&_binary_PS3_DeferredPhoton_P3_000_FP_bin_end - (unsigned char *)&_binary_PS3_DeferredPhoton_P3_000_FP_bin_start ), new Asura_PS3_DeferredSystem_Handles(), 1, true );
	Asura_PS3_GCM_ShadersLibrary::AddFP( static_cast<Asura_Hash_ID> ( ASURA_SHADER_HASH_DEFERRED ), 3010, (unsigned char *)&_binary_PS3_DeferredPhoton_P3_010_FP_bin_start, ( (unsigned char *)&_binary_PS3_DeferredPhoton_P3_010_FP_bin_end - (unsigned char *)&_binary_PS3_DeferredPhoton_P3_010_FP_bin_start ), new Asura_PS3_DeferredSystem_Handles(), 1, true );
	Asura_PS3_GCM_ShadersLibrary::AddFP( static_cast<Asura_Hash_ID> ( ASURA_SHADER_HASH_DEFERRED ), 3011, (unsigned char *)&_binary_PS3_DeferredPhoton_P3_011_FP_bin_start, ( (unsigned char *)&_binary_PS3_DeferredPhoton_P3_011_FP_bin_end - (unsigned char *)&_binary_PS3_DeferredPhoton_P3_011_FP_bin_start ), new Asura_PS3_DeferredSystem_Handles(), 1, true );

	Asura_PS3_GCM_ShadersLibrary::AddFP( static_cast<Asura_Hash_ID> ( ASURA_SHADER_HASH_DEFERRED ), 3100, (unsigned char *)&_binary_PS3_DeferredPhoton_P3_100_FP_bin_start, ( (unsigned char *)&_binary_PS3_DeferredPhoton_P3_100_FP_bin_end - (unsigned char *)&_binary_PS3_DeferredPhoton_P3_100_FP_bin_start ), new Asura_PS3_DeferredSystem_Handles(), 1, true );
	Asura_PS3_GCM_ShadersLibrary::AddFP( static_cast<Asura_Hash_ID> ( ASURA_SHADER_HASH_DEFERRED ), 3110, (unsigned char *)&_binary_PS3_DeferredPhoton_P3_110_FP_bin_start, ( (unsigned char *)&_binary_PS3_DeferredPhoton_P3_110_FP_bin_end - (unsigned char *)&_binary_PS3_DeferredPhoton_P3_110_FP_bin_start ), new Asura_PS3_DeferredSystem_Handles(), 1, true );
	Asura_PS3_GCM_ShadersLibrary::AddFP( static_cast<Asura_Hash_ID> ( ASURA_SHADER_HASH_DEFERRED ), 3111, (unsigned char *)&_binary_PS3_DeferredPhoton_P3_111_FP_bin_start, ( (unsigned char *)&_binary_PS3_DeferredPhoton_P3_111_FP_bin_end - (unsigned char *)&_binary_PS3_DeferredPhoton_P3_111_FP_bin_start ), new Asura_PS3_DeferredSystem_Handles(), 1, true );

	g_pxVertexShader->InitVertexBufferDeclaration( g_axVertexDeclaration, VD_ELEMENTS );

	const uint16 au16Indices[] =
	{
		0,1,2 ,2,3,0,
		1,5,6 ,6,2,1,
		7,6,5 ,5,4,7,
		0,3,7 ,7,4,0,
		4,5,1 ,1,0,4,
		7,3,2 ,2,6,7
	};

	s_xIndexBuffer.Initialise( 36 );
	void* pBuffer = s_xIndexBuffer.LockForWriting();
	Asura_Mem::Copy( pBuffer, au16Indices, sizeof( au16Indices ) );
	s_xIndexBuffer.Unlock();

    const u_char uVtxSize = sizeof( Asura_Vector_3 );
	for( int i = 0; i < ASURA_DEFERRED_BUFFERS; ++i )
	{
		s_xVertexBuffer[ i ].Initialise( uVtxSize, PS3_DEFFERD_SHADING_MAXIMUM_LIGHTS * 8, CELL_GCM_LOCATION_MAIN );
	}

	// Create VBs for a sphere, used to render the deferred passes, at 4 different LOD levels
	for( int iLod = 0; iLod < 4; ++iLod )
	{
		int   iSphereVerticalSegments = 10;
		int   iSphereRadialSegments   = 20;
		float fSphereRadiusScale      = 1.0f;

		switch(iLod)
		{
		case 3:
			fSphereRadiusScale		= 1.1f;
			iSphereVerticalSegments = 6;
			iSphereRadialSegments	= 12;				
			break;
		case 2:
			fSphereRadiusScale		= 1.075f;
			iSphereVerticalSegments = 8;
			iSphereRadialSegments	= 16;
			break;
		case 1:
			fSphereRadiusScale		= 1.05f;
			iSphereVerticalSegments = 10;
			iSphereRadialSegments   = 20;
			break;
		case 0:
			fSphereRadiusScale		= 1.025f;
			iSphereVerticalSegments = 16;
			iSphereRadialSegments   = 32;
			break;
		}

		int iNumSphereVerts = ( ( iSphereRadialSegments * 4 ) + 2 ) * iSphereVerticalSegments;

		s_apxSphereVB[ iLod ] = new Asura_PS3_GCM_VertexBuffer;
		s_apxSphereVB[ iLod ]->Initialise( uVtxSize, iNumSphereVerts, CELL_GCM_LOCATION_MAIN );
		Asura_Vector_3 *pxVerts = static_cast< Asura_Vector_3* >( s_apxSphereVB[ iLod ]->LockForWriting() );		
		Asura_DeferredShading::CreateSphere( pxVerts, iNumSphereVerts, iSphereRadialSegments, iSphereVerticalSegments, fSphereRadiusScale );
		s_aiSphereNumVerts[ iLod ] = iNumSphereVerts;
	}

#ifdef PHOTON_TESTBED_ENABLED
	Asura_CommandConsole::AddVar( L"Render.Deferred.TestStencilMutex", &g_bTestStencilMutex, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
	Asura_CommandConsole::AddVar( L"Render.Deferred.Test2LightShader", &g_bTest2LightShader, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
#endif
#ifdef ASURA_SUPPORT_COMMAND_CONSOLE
	Asura_CommandConsole::AddVar( L"Render.Deferred.ShownBuffer", &g_iSelectedBuffer, 0, 7, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
#endif
}

void Asura_PS3_DeferredShading::Shutdown(void)
{
	s_xIndexBuffer.Shutdown();
	for( int i = 0; i < ASURA_DEFERRED_BUFFERS; ++i )
	{
		s_xVertexBuffer[ i ].Shutdown();
	}

	for (u_int u = 0; u < 4; ++u)
	{
		delete s_apxSphereVB[u];
		s_apxSphereVB[u] = NULL;
	}
	delete g_pxVertexShader;
	g_pxVertexShader = NULL;
	delete g_pxFragmentShaderP0;
	g_pxFragmentShaderP0 = NULL;
}

void Asura_PS3_DeferredShading::Reset(void)
{
}

void Asura_PS3_DeferredShading::RenderPhotonOutlines_Callback( Asura_Photon* pxPhoton, void* pData )
{
	// Photons now support a distance fade-out, like the projectors
	float fDistanceFade = 1.0f;
	if (pxPhoton->GetDistanceFadeEnable())
	{
		Asura_Vector_3 xCameraPos = *Asura_Camera::GetPosition();
		Asura_Vector_3 xOfs = pxPhoton->GetPosition()-xCameraPos;
		float fDist = xOfs.Magnitude();
		if (fDist> pxPhoton->GetDistanceFadeStart())
		{
			fDistanceFade = 1.0f - ((fDist - pxPhoton->GetDistanceFadeStart())/pxPhoton->GetDistanceFadeDist());
		}

		if (fDistanceFade<=0.0f)
		{
			return;
		}
	}
#if defined (ASURA_USE_NEW_VISIBILITY_SYSTEM)
	if ( !pxPhoton->IsVisible() )
	{
		return;
	}
	
	const Asura_Vector_3& xPos = pxPhoton->GetPosition();		

#else// !defined(ASURA_USE_NEW_VISIBILITY_SYSTEM )
	Asura_Vector_3 xPos = pxPhoton->GetPosition();
	float fRadius = pxPhoton->GetRange();

	const bool bIsPhotonInFrustrum = Asura_Camera::IsSphereInFrustum( xPos, fRadius);
	if( false == bIsPhotonInFrustrum)
	{
		return;
	}

	Asura_Bounding_Box xBB;
	xBB.Reset();
	xBB.Set(xPos + Asura_Vector_3(fRadius,fRadius,fRadius));
	xBB.Set(xPos - Asura_Vector_3(fRadius,fRadius,fRadius));
	if (!Asura_Module_System::DoesBBIntersectVisibleModule(xBB, &pxPhoton->m_iLastKnownModule))
	{
		return;
	}
#endif// !defined(ASURA_USE_NEW_VISIBILITY_SYSTEM)

	pxPhoton->RenderLightVolume();
}

bool g_bUseCorrectZTestForPhotons = true;

void SetZBufferStates(const Asura_Vector_3 &xPos, bool bInside)
{
    if( g_bUseCorrectZTestForPhotons )
    {
        if( bInside )
        {
            Asura_PS3_RenderingStates::RequestCullMode( ASURA_CULLMODE_CW );
            Asura_PS3_RenderingStates::RequestZBufferCompareMode( ASURA_ZBUFFERCMP_GREATER );     
        }
        else
        {   
            Asura_PS3_RenderingStates::RequestCullMode( ASURA_CULLMODE_CCW );       
            Asura_PS3_RenderingStates::RequestZBufferCompareMode( ASURA_ZBUFFERCMP_LESSEQUAL );       
        }
    }
    else
    {
        // This version was in use for a while, due to some artifacts in
        // edge cases when the camera was at the outer radius of the photons.
        // However, we couldn't reproduce any such problems with latest code
        // so possibly that was some other issue. The variable for toggling
        // this is still here just in case.
        Asura_PS3_RenderingStates::RequestCullMode( ASURA_CULLMODE_CW );
        Asura_PS3_RenderingStates::RequestZBufferCompareMode(ASURA_ZBUFFERCMP_GREATER);		
    }
}

class Asura_PS3_DeferredShading_RenderPhotons_Callback_Data
{
public:
	Asura_Vector_3     m_xCameraPos;
#if !defined (ASURA_USE_NEW_VISIBILITY_SYSTEM)
	Asura_Bounding_Box m_xBoundingBox;
#endif
	bool			   m_bWithDarkeningShadows;
	bool			   m_bWithRealShadows;
	bool			   m_bWithNoShadows;
};

void Asura_PS3_DeferredShading::RenderSphereLOD( int iLod )
{
	s_apxSphereVB[ iLod ]->Bind( g_axVertexDeclaration, VD_ELEMENTS );
	s_apxSphereVB[ iLod ]->RenderTriangleStrip( s_aiSphereNumVerts[ iLod ] );
}

void Asura_PS3_DeferredShading::RenderPhotons_Callback( Asura_Photon* pxPhoton, void* pData )
{
	Asura_PS3_DeferredShading_RenderPhotons_Callback_Data* pxData = static_cast< Asura_PS3_DeferredShading_RenderPhotons_Callback_Data* >(pData);
	bool bInside = false;
	bool bRenderAsCuboid = false;
	int  iSphereLOD = 0;

	if (!pxPhoton->IsActive())
	{
		return;
	}


	float fRadius = pxPhoton->GetRange();
	if( fRadius <= 0.0f)
	{
		return;
	}


	// Check this light has one of the types of shadows that we're rendering this pass
	bool bAccept = false;
	uint32 uFlags =  pxPhoton->GetShadowParameters().m_uFlags;

	if (pxData->m_bWithRealShadows)
	{
		if( ( uFlags & ( ASURA_SHADOWFLAG_CASTS_OWN_SHADOWS | ASURA_SHADOWFLAG_CASTS_ENVIRONMENT_SHADOWS ) ) && ((uFlags & ASURA_SHADOWFLAG_IS_DARKENING_PASS) == 0))
		{
			if( uFlags & ASURA_SHADOWFLAG_CASTS_ENVIRONMENT_SHADOWS )
			{
				if( ( uFlags & ASURA_SHADOWFLAG_CONTRIBUTES_TO_AVERAGE_SHADOW ) == 0 )
				{
					bAccept = true;
				}
			}
			else
			{
				bAccept = true;
			}
		}
	}

	if (pxData->m_bWithDarkeningShadows)
	{
		if (((uFlags & ( ASURA_SHADOWFLAG_CASTS_OWN_SHADOWS | ASURA_SHADOWFLAG_CASTS_ENVIRONMENT_SHADOWS ) ) && (uFlags & ASURA_SHADOWFLAG_IS_DARKENING_PASS)) || (uFlags & ASURA_SHADOWFLAG_CONTRIBUTES_TO_AVERAGE_SHADOW))
		{
			bAccept = true;
		}			
	}

	if (pxData->m_bWithNoShadows)
	{
		if (0 == (uFlags & (( ASURA_SHADOWFLAG_CASTS_OWN_SHADOWS | ASURA_SHADOWFLAG_CASTS_ENVIRONMENT_SHADOWS ) | ASURA_SHADOWFLAG_CONTRIBUTES_TO_AVERAGE_SHADOW)))
		{
			bAccept = true;
		}			
	}

	if (!bAccept)
	{		
		return;
	}

	// Photons now support a distance fade-out, like the projectors
	float fDistanceFade = 1.0f;
	if (pxPhoton->GetDistanceFadeEnable())
	{	
		Asura_Vector_3 xOfs = pxPhoton->GetPosition()-pxData->m_xCameraPos;
		float fDist = xOfs.Magnitude();
		if (fDist> pxPhoton->GetDistanceFadeStart())
		{
			fDistanceFade = 1.0f - ((fDist - pxPhoton->GetDistanceFadeStart())/pxPhoton->GetDistanceFadeDist());
		}

		if (fDistanceFade<=0.0f)
		{
			return;
		}
	}
#if defined (ASURA_USE_NEW_VISIBILITY_SYSTEM)
	if ( !pxPhoton->IsVisible() )
	{
		return;
	}
	
	const Asura_Vector_3& xPos = pxPhoton->GetPosition();		

#else// !defined(ASURA_USE_NEW_VISIBILITY_SYSTEM )
	Asura_Vector_3 xPos = pxPhoton->GetPosition();
	const bool bIsPhotonInBoundingBox = pxData->m_xBoundingBox.IntersectsSphere( xPos, fRadius);
	if( false == bIsPhotonInBoundingBox)
	{
		return;
	}

	const bool bIsPhotonInFrustrum = Asura_Camera::IsSphereInFrustum( xPos, fRadius);
	if( false == bIsPhotonInFrustrum)
	{
		return;
	}

	Asura_Bounding_Box xBB;
	xBB.Reset();
	xBB.Set(xPos + Asura_Vector_3(fRadius,fRadius,fRadius));
	xBB.Set(xPos - Asura_Vector_3(fRadius,fRadius,fRadius));
	if (!Asura_Module_System::DoesBBIntersectVisibleModule(xBB, &pxPhoton->m_iLastKnownModule))
	{
		return;
	}
#endif// !defined(ASURA_USE_NEW_VISIBILITY_SYSTEM)

	Asura_Vector_3 xUp, xRight;
	Asura_Camera::GetUpVector(xUp);
	Asura_Camera::GetRightVector(xRight);
	Asura_Vector_3* xVerts = ( Asura_Vector_3* )s_xVertexBuffer[ g_iCurrentRenderBuffer ].LockForWriting();
	xVerts += g_iCurrentLight * 8;

	float		   fMeshScale;
	Asura_Vector_3 xMeshPos;

	if (pxPhoton->GetCustomLightVolumeEnable())
	{
		// Rendering a cuboid in world-space
		bRenderAsCuboid = true;
		xMeshPos.x = 0;
		xMeshPos.y = 0;
		xMeshPos.z = 0;
		fMeshScale = 1;

		// Custom bounding box
		const Asura_Bounding_Box& xBB = pxPhoton->GetCustomLightVolume();
		const Asura_Quat& xOrient = pxPhoton->GetCustomLightVolumeOrient();

		xVerts[0].x = xBB.MinX;		xVerts[0].y = xBB.MinY;		xVerts[0].z = xBB.MinZ;
		xVerts[1].x = xBB.MaxX;		xVerts[1].y = xBB.MinY;		xVerts[1].z = xBB.MinZ;
		xVerts[2].x = xBB.MaxX;		xVerts[2].y = xBB.MaxY;		xVerts[2].z = xBB.MinZ;
		xVerts[3].x = xBB.MinX;		xVerts[3].y = xBB.MaxY;		xVerts[3].z = xBB.MinZ;
		xVerts[4].x = xBB.MinX;		xVerts[4].y = xBB.MinY;		xVerts[4].z = xBB.MaxZ;
		xVerts[5].x = xBB.MaxX;		xVerts[5].y = xBB.MinY;		xVerts[5].z = xBB.MaxZ;
		xVerts[6].x = xBB.MaxX;		xVerts[6].y = xBB.MaxY;		xVerts[6].z = xBB.MaxZ;
		xVerts[7].x = xBB.MinX;		xVerts[7].y = xBB.MaxY;		xVerts[7].z = xBB.MaxZ;

		// It's an oriented box, so rotate the verts	
		Asura_Vector_3 xCentre = xBB.CalculateCentre();
		for(int i = 0; i < 8; ++i)
		{
			xVerts[i] -= xCentre;
			xVerts[i] = xOrient.RotateVector(xVerts[i]);
			xVerts[i] += xCentre;
		}

		// Are we inside the volume?

		//need to get position relative to volume
		Asura_Vector_3 xRelative = pxData->m_xCameraPos - xCentre;

		//and rotate into the space of the volume
		Asura_Quat xOrientInverse = xOrient;
		xOrientInverse.Invert();
		xRelative = xOrientInverse.RotateVector(xRelative);
		Asura_Vector_3 xTestPos = xRelative+xCentre;

		float fAdjust = 0.1f;
		bInside = ((xTestPos.x>=xBB.MinX-fAdjust && xTestPos.x<=xBB.MaxX+fAdjust) &&
			(xTestPos.y>=xBB.MinY-fAdjust && xTestPos.y<=xBB.MaxY+fAdjust) &&
			(xTestPos.z>=xBB.MinZ-fAdjust && xTestPos.z<=xBB.MaxZ+fAdjust));
	}
	else
	{
		if (Asura_DeferredShading::GetUseCubesForPhotons())
		{
			// Rendering a cube in world-space
			bRenderAsCuboid = true;
			xMeshPos.x = 0;
			xMeshPos.y = 0;
			xMeshPos.z = 0;
			fMeshScale = 1;
			// Standard cube
			for(int i = 0; i < 8; ++i)
			{
				static const float afUp[8]    = {-1.0f, -1.0f,  1.0f,  1.0f, -1.0f, -1.0f, 1.0f,  1.0f};
				static const float afRight[8] = {-1.0f,  1.0f,  1.0f, -1.0f, -1.0f,  1.0f, 1.0f, -1.0f};
				static const float afFwd[8]   = {-1.0f, -1.0f, -1.0f, -1.0f,  1.0f,  1.0f, 1.0f,  1.0f};

				xVerts[i].x = xPos.x + (afRight[i]*fRadius);
				xVerts[i].y = xPos.y + (afUp[i]*fRadius);
				xVerts[i].z = xPos.z + (afFwd[i]*fRadius);	
			}

			// Are we inside the volume?
			const Asura_Vector_3 &xCameraPos = pxData->m_xCameraPos;
			float fRadiusAdjusted = fRadius+0.1f;
			bInside = ((xCameraPos.x>=xPos.x-fRadiusAdjusted && xCameraPos.x<=xPos.x+fRadiusAdjusted) &&
					   (xCameraPos.y>=xPos.y-fRadiusAdjusted && xCameraPos.y<=xPos.y+fRadiusAdjusted) &&
					   (xCameraPos.z>=xPos.z-fRadiusAdjusted && xCameraPos.z<=xPos.z+fRadiusAdjusted));
		}
		else
		{
			// Rendering a sphere - use the offset+scale
			bRenderAsCuboid = false;
			xMeshPos.x = xPos.x;
			xMeshPos.y = xPos.y;
			xMeshPos.z = xPos.z;
			fMeshScale = fRadius;

			// Are we inside the volume?
			const Asura_Vector_3 &xCameraPos = pxData->m_xCameraPos;
			float fRadiusAdjusted = ( fRadius * 1.025f ) + 0.1f;
			Asura_Vector_3 xOfs = xCameraPos - xPos;
			float fDist = xOfs.Magnitude();
			bInside = ( fDist<fRadiusAdjusted );			

			// Pick a sphere LOD. Switch down quickly to the low LOD, as we only need a more round
			// sphere when the camera is about to move inside it, for a more accurate inside-outside test
			iSphereLOD = ( int )( fDist * 0.5f / fRadius );
			if( iSphereLOD > 3 )
			{
				iSphereLOD = 3;
			}		
			else if( iSphereLOD < 0 )
			{
				iSphereLOD = 0;
			}
		}
	}

	int iPass = DeferredLight_Pass_Photon;

	if (pxPhoton->GetIsDirectional())
	{
		iPass = DeferredLight_Pass_Infinite;		
	}
	else
	{
		if( pxPhoton->GetLimitAnglesEnabled() || pxPhoton->GetProjCubeTextureIndex() != ASURA_TEXTURE_NONE )
		{
			iPass = DeferredLight_Pass_Limited;
		}
	}

	//BW comment from the xbox360 version:
	// -----------------------------------------------------------------------------------------------------------------------
	// DMR - 06/08/07 - Stencil buffer optimization
	// The stencil mask is created before rendering shadows or the light itself, as it should help with both.
	// We only care about the Hi-Stencil, as the per-pixel stencil test is executed after the shader - so not an optimization
	//
	// It takes 2 passes to fill the Hi-Stencil - the first one draws back faces, setting the stencil to 1 on z-pass and 0 on
	// z-fail. The 2nd pass draws front faces, setting the hi-stencil if the stencil was previously set to 1 (i.e. if both
	// the front and back are visible => nothing to light)
	//
	// (The camera-inside-the-volume case needs a special-case in the 2nd pass - draws the back faces again, with z-testing off)
	//
	// -----------------------------------------------------------------------------------------------------------------------	
	bool bUseStencil = Asura_DeferredShading::GetUseStencilBuffer();
	
 	Asura_PS3_FragmentShaderCopy *pxBlankFP = Asura_PS3_GCM_ShadersLibrary::GetPermanentFP( ASURA_SHADER_HASH_GLOBAL, ASURA_GLOBAL_SHADER_BLANK );

	if (bUseStencil)
	{		
		Asura_PS3_RenderingStates::RequestCullMode( ASURA_CULLMODE_NONE );
		Asura_PS3_RenderingStates::RequestZBufferCompareMode(ASURA_ZBUFFERCMP_ALWAYS);		

//BW still takes a while drawing this. mostly ROP utilization => vert shader? I think it should be quicker

		Asura_PS3_RenderingStates::RequestTranslucencyMode(ASURA_TRANSLUCENCY_OFF);

		Asura_PS3_RenderingStates::SetColorMaskMrtState( ASURA_PS3_MRT_NONE );
		//Asura_PS3_RenderingStates::RequestTranslucencyMode(ASURA_TRANSLUCENCY_OFF);

		SetShaderParameters( NULL, 0, xMeshPos, fMeshScale, fDistanceFade );

		pxBlankFP->Bind();

		for (int iStencilPass=0; iStencilPass<2; iStencilPass++)
		{
#ifdef ASURA_USE_GCM_REPLAY
			char szProfileName[256];
			sprintf( szProfileName, "Render Photon - stencil pass %d", iStencilPass );
			ASURA_PS3_RSX_PROFILE__BEGIN( ASURA_PS3_RSX_PROFILE__MISC, szProfileName );
#endif

			switch(iStencilPass)
			{		
				//BW I do these in the opposite order to xbox, because it's a bit simpler - write 1 for front faces then overwrite 0 for backfaces.
				case 1:		
					// Back faces - the bits that should be excluded
					Asura_PS3_RenderingStates::SetStencilTestEnabled(true);
					Asura_PS3_RenderingStates::SetStencilMask( ASURA_STENCIL_MASK_PHOTON );
					Asura_PS3_RenderingStates::SetStencilFunction( ASURA_PS3_STENCIL_ALWAYS, ASURA_STENCIL_MASK_PHOTON, ASURA_STENCIL_MASK_PHOTON );
					Asura_PS3_RenderingStates::SetStencilOperation( ASURA_PS3_STENCIL_OPERATION_KEEP, ASURA_PS3_STENCIL_OPERATION_KEEP, ASURA_PS3_STENCIL_OPERATION_ZERO );

#ifdef PHOTON_TESTBED_ENABLED
					if ( g_bTestStencilMutex )
					{
						Asura_PS3_RenderingStates::SetStencilFunction( ASURA_PS3_STENCIL_EQUAL, ASURA_STENCIL_MASK_PHOTON, ASURA_STENCIL_MASK_PHOTON );
						Asura_PS3_RenderingStates::SetStencilOperation( ASURA_PS3_STENCIL_OPERATION_KEEP, ASURA_PS3_STENCIL_OPERATION_KEEP, ASURA_PS3_STENCIL_OPERATION_ZERO );
					}
#endif

					Asura_PS3_RenderingStates::RequestCullMode( ASURA_CULLMODE_CW );
					Asura_PS3_RenderingStates::RequestZBufferCompareMode(ASURA_ZBUFFERCMP_LESSEQUAL);		
					break;

				case 0:
					// Front faces   - difference between this and backfaces is the area we need to mask.
 					Asura_PS3_RenderingStates::SetStencilTestEnabled(true);
					Asura_PS3_RenderingStates::SetStencilMask( ASURA_STENCIL_MASK_PHOTON );
					Asura_PS3_RenderingStates::SetStencilFunction( ASURA_PS3_STENCIL_ALWAYS, ASURA_STENCIL_MASK_PHOTON, ASURA_STENCIL_MASK_PHOTON );
					Asura_PS3_RenderingStates::SetStencilOperation( ASURA_PS3_STENCIL_OPERATION_ZERO, ASURA_PS3_STENCIL_OPERATION_ZERO, ASURA_PS3_STENCIL_OPERATION_REPLACE );

#ifdef PHOTON_TESTBED_ENABLED
					if ( g_bTestStencilMutex )
					{
						Asura_PS3_RenderingStates::SetStencilFunction( ASURA_PS3_STENCIL_NOTEQUAL, ASURA_STENCIL_MASK_PHOTON, ASURA_STENCIL_MASK_PHOTON );
						Asura_PS3_RenderingStates::SetStencilOperation( ASURA_PS3_STENCIL_OPERATION_ZERO, ASURA_PS3_STENCIL_OPERATION_ZERO, ASURA_PS3_STENCIL_OPERATION_REPLACE );
					}
#endif
					
					if (bInside)
					{
						//shouldn't we just draw a quad?
						Asura_PS3_RenderingStates::RequestCullMode( ASURA_CULLMODE_CW );
						Asura_PS3_RenderingStates::RequestZBufferCompareMode(ASURA_ZBUFFERCMP_ALWAYS);
					}
					else
					{
						Asura_PS3_RenderingStates::RequestCullMode( ASURA_CULLMODE_CCW );
						Asura_PS3_RenderingStates::RequestZBufferCompareMode(ASURA_ZBUFFERCMP_LESSEQUAL);		
					}

					break;
			}			
			
			if (bRenderAsCuboid)
			{
				s_xVertexBuffer[ g_iCurrentRenderBuffer ].BindFromVertex( g_axVertexDeclaration, VD_ELEMENTS, g_iCurrentLight * 8 );
				s_xIndexBuffer.RenderTriangles( 36, 0 );
			}
			else
			{		
				RenderSphereLOD( iSphereLOD );
			}

#ifdef ASURA_USE_GCM_REPLAY
			ASURA_PS3_RSX_PROFILE__END( ASURA_PS3_RSX_PROFILE__MISC );
#endif

		}

		Asura_PS3_RenderingStates::RequestZBufferCompareMode(ASURA_ZBUFFERCMP_LESSEQUAL);		
		Asura_PS3_RenderingStates::RequestCullMode( ASURA_CULLMODE_CCW );
		Asura_PS3_RenderingStates::SetColorMaskMrtState( ASURA_PS3_MRT_MRT0_ALL );

		Asura_PS3_RenderingStates::SetStencilTestEnabled(false);

	}

	if (bUseStencil)
	{		
		// read the stencil
		Asura_PS3_RenderingStates::SetStencilTestEnabled(true);
		Asura_PS3_RenderingStates::SetStencilFunction( ASURA_PS3_STENCIL_EQUAL, ASURA_STENCIL_MASK_PHOTON, ASURA_STENCIL_MASK_PHOTON );
		Asura_PS3_RenderingStates::SetStencilMask( ASURA_STENCIL_MASK_PHOTON );
		Asura_PS3_RenderingStates::SetStencilOperation( ASURA_PS3_STENCIL_OPERATION_KEEP, ASURA_PS3_STENCIL_OPERATION_KEEP, ASURA_PS3_STENCIL_OPERATION_KEEP );
	}


//	u_int uiValue = Asura_PS3_RenderingStates::BlockRSX();
	// -----------------------------------------------------------------------------------------------------------------------
	// DMR - 03/04/07 - Shadow support.
	// If this light is a shadow caster, we need to build the shadow mask now (in the destination alpha channel), and set the
	// appropriate blending mode.
	//
	// Before projecting the shadows, we need to clear the area of the shadow mask that's used by this light - we do that by
	// rendering the light volume, with colour writes only enabled to the alpha channel
	// -----------------------------------------------------------------------------------------------------------------------
	bool bShadows = false;
	if( pxPhoton->GetShadowParameters().m_uFlags & ( ASURA_SHADOWFLAG_CASTS_OWN_SHADOWS | ASURA_SHADOWFLAG_CASTS_ENVIRONMENT_SHADOWS ) )
	{	

		// Are there any active shadows for this light?
		if( Asura_Shadow_System::LightHasActiveShadows( ASURA_SHADOW_LIGHTSOURCE_PHOTON, pxPhoton->GetHandle() ) )
		{	
			// Set this flag, so we know to restore the renderstates
			bShadows = true;

			// First, clear the destination alpha to 0 for the area affected by our shadows		
			Asura_PS3_RenderingStates::SetColorMaskMrtState( ASURA_PS3_MRT_MRT0_ALPHA );
			Asura_PS3_RenderingStates::RequestTranslucencyMode(ASURA_TRANSLUCENCY_OFF); 			
			SetZBufferStates(xPos, bInside);
			SetShaderParameters( NULL, 0, xMeshPos, fMeshScale, fDistanceFade );

			if( bRenderAsCuboid )
			{
				s_xVertexBuffer[ g_iCurrentRenderBuffer ].BindFromVertex( g_axVertexDeclaration, VD_ELEMENTS, g_iCurrentLight * 8 );
				s_xIndexBuffer.RenderTriangles( 36, 0 );
			}
			else
			{
				RenderSphereLOD( iSphereLOD );
			}

			// Then render the shadow mask into the destination alpha
			Asura_Shadow_System::BuildShadowMask(ASURA_SHADOW_LIGHTSOURCE_PHOTON, pxPhoton->GetHandle());

			// Re-enable RGB writes
			Asura_PS3_RenderingStates::SetColorMaskMrtState( ASURA_PS3_MRT_MRT0_ALL );
			Asura_PS3_RenderingStates::RequestTranslucencyMode(ASURA_TRANSLUCENCY_GLOWING); 			
			// Asura doesn't have a mode for this, so I'm changing it manually here, then restoring it when we're done
			cellGcmSetBlendFuncUnsafeInline( Asura_PS3_Graphics::GetGcmCurrentContext(), CELL_GCM_ONE_MINUS_DST_ALPHA, CELL_GCM_ONE, CELL_GCM_ONE_MINUS_DST_ALPHA, CELL_GCM_ONE );
		}
	}

	ASURA_PS3_RSX_PROFILE__BEGIN( ASURA_PS3_RSX_PROFILE__PHOTONS, "Render Photons" );


	SetZBufferStates(xPos, bInside);
	SetShaderParameters( pxPhoton, iPass, xMeshPos, fMeshScale, fDistanceFade );

	Asura_PS3_RenderingStates::RequestTranslucencyMode(ASURA_TRANSLUCENCY_GLOWING);

	if ( Asura_DeferredShading::GetShowStencils() )
	{
		// highlight the stencilled area,
		// - draw same shape used when render lights
		// - draw translucent so can see each light (possibly colour code lights too)
		// ...so actually just draw light as a flat colour
		pxBlankFP->Bind();
		
		Asura_PS3_RenderingStates::RequestCullMode( ASURA_CULLMODE_CW ); //draw back faces, makes no difference unless inside where it ensures we can see them
		Asura_PS3_RenderingStates::RequestTranslucencyMode(ASURA_TRANSLUCENCY_GLOWING);
		cellGcmSetBlendFuncUnsafeInline( Asura_PS3_Graphics::GetGcmCurrentContext(), CELL_GCM_ONE, CELL_GCM_ONE, CELL_GCM_ONE, CELL_GCM_ONE );
		Asura_PS3_RenderingStates::RequestZBufferCompareMode(ASURA_ZBUFFERCMP_ALWAYS); //actually show the stencil, regardless of depth
		Asura_PS3_RenderingStates::InvalidateTranslucencyMode();
	}

	if (bRenderAsCuboid)
	{
		s_xVertexBuffer[ g_iCurrentRenderBuffer ].BindFromVertex( g_axVertexDeclaration, VD_ELEMENTS, g_iCurrentLight * 8 );
		s_xIndexBuffer.RenderTriangles( 36, 0 );
	}
	else
	{
		RenderSphereLOD( iSphereLOD );
	}

	Asura_PS3_RenderingStates::SetStencilTestEnabled(false);
	Asura_PS3_RenderingStates::SetStencilMask( 0 );


	if (bShadows)
	{
		// If we've been rendering with shadows, restore states, so we aren't doing the multiply by the dest alpha	
		cellGcmSetBlendFuncUnsafeInline( Asura_PS3_Graphics::GetGcmCurrentContext(), CELL_GCM_SRC_ALPHA, CELL_GCM_ONE, CELL_GCM_SRC_ALPHA, CELL_GCM_ONE );
		Asura_PS3_RenderingStates::InvalidateTranslucencyMode();
	}

//	Asura_PS3_RenderingStates::UnblockRSX( uiValue );
	//Move to the next light
	++g_iCurrentLight;
	if( g_iCurrentLight >= PS3_DEFFERD_SHADING_MAXIMUM_LIGHTS )
	{
		++g_iCurrentRenderBuffer;
		g_iCurrentRenderBuffer = g_iCurrentRenderBuffer % ASURA_DEFERRED_BUFFERS;
		g_iCurrentLight = 0;
	}

	ASURA_PS3_RSX_PROFILE__END( ASURA_PS3_RSX_PROFILE__PHOTONS );
}

void Asura_PS3_DeferredShading::SetShaderParameters( Asura_Photon *pxPhoton, int iPass, Asura_Vector_3 &xMeshPos, float fMeshScale, float fDistFade )
{
	const Asura_Vector_3* const pxCamPos = Asura_Camera::GetPosition();
	float afCamPos[ 4 ]				= { pxCamPos->x, pxCamPos->y, pxCamPos->z, 1.0f };
	float afMeshScaleAndPos[ 4 ]	= { xMeshPos.x, xMeshPos.y, xMeshPos.z, fMeshScale };

	g_pxVertexShader->Bind();

	const Asura_AMatrix4x4& xViewProjectionMatrix = Asura_PS3_Graphics::GetViewProjectionMatrix();
	g_pxVertexShader->SetUniformParameter( VS_ViewProjectionMatrix, xViewProjectionMatrix.GetElements() );
	g_pxVertexShader->SetUniformParameter( VS_MeshScaleAndPos, afMeshScaleAndPos );
	if( iPass == 0 )
	{
		g_pxFragmentShaderP0->Bind();
		return;
	}

	Asura_AMatrix4x4 xViewMatrixInv;
	xViewMatrixInv = Asura_PS3_Graphics::GetViewMatrix();
	xViewMatrixInv.Invert();

	float afTwoOverScreenDimensions[2];
	float afDir[3];
	float afPosition[4];
	float afColour[4];
	float afFallOff[4];
	afTwoOverScreenDimensions[0] = 2.0f / Asura_Viewport::GetWidth();
	afTwoOverScreenDimensions[1] = 2.0f / Asura_Viewport::GetHeight();	

	float afCameraTanHalfFOV[2];
	float fCameraTanHalfVertFOV	= Asura_Maths::Tangent( Asura_Camera::GetVerticalFieldOfView() * 0.5f );
	float fCameraTanHalfHorizFOV= fCameraTanHalfVertFOV * Asura_PS3_Graphics::GetScreenAspectRatio();// Asura_Camera::GetXOverYAspectRatio();
	afCameraTanHalfFOV[0] = fCameraTanHalfHorizFOV;
	afCameraTanHalfFOV[1] = fCameraTanHalfVertFOV;
	afFallOff[ 0 ] = pxPhoton->GetFalloffScale();
	afFallOff[ 1 ] = pxPhoton->GetFalloffPower();

	const Asura_Vector_3& xDir = pxPhoton->GetLightDirection();
	afDir[0] = xDir.x;
	afDir[1] = xDir.y;
	afDir[2] = xDir.z;

	const Asura_Vector_3& xPosition = pxPhoton->GetPosition();
	afPosition[0] = xPosition.GetX();
	afPosition[1] = xPosition.GetY();
	afPosition[2] = xPosition.GetZ();
	afPosition[3] = 1.0f / pxPhoton->GetRange();

	const float fR = fDistFade * pxPhoton->GetRedComponent();
	const float fG = fDistFade * pxPhoton->GetGreenComponent();
	const float fB = fDistFade * pxPhoton->GetBlueComponent();
	afColour[0] = fR;
	afColour[1] = fG;
	afColour[2] = fB;
	afColour[3] = (fR + fG + fB) / 3.0f;

	Asura_PS3_FP_List_Tree* fpTree = Asura_PS3_GCM_ShadersLibrary::GetPermanentFPTree( ASURA_SHADER_HASH_DEFERRED );
	switch( iPass )
	{
		case DeferredLight_Pass_Photon:
		{
			g_pxFragmentShaderP1 = fpTree->GetShader( 1 );

#ifdef PHOTON_TESTBED_ENABLED
			if ( g_bTest2LightShader )
			{
				g_pxFragmentShaderP1 = fpTree->GetShader( 666 );
			}
#endif

			if( !g_pxFragmentShaderP1 )
			{
				Asura_Assert( false, "Invalid shader (Deferred 1)" );
				return;
			}
			g_pxFragmentShaderP1->SetArray(  FP_InvViewMatrix,	4,	4, xViewMatrixInv.GetElements() );
			g_pxFragmentShaderP1->SetFloats( FP_CameraTanHalfFOV,	4, &afCameraTanHalfFOV[0] );
			g_pxFragmentShaderP1->SetFloats( FP_TwoOverScreen,		4, &afTwoOverScreenDimensions[0] );
			g_pxFragmentShaderP1->SetFloats( FP_Falloff,			4, &afFallOff[0] );
			g_pxFragmentShaderP1->SetFloats( FP_LightColour,		4, &afColour[0] );
			g_pxFragmentShaderP1->SetFloats( FP_LightPosScale,		4, &afPosition[0] );
			g_pxFragmentShaderP1->SetFloats( FP_CameraPosition,		4, afCamPos );

			Asura_PS3_MaterialResponse_System::SetDepthParameters( g_pxFragmentShaderP1, FP_DepthAdjustment );

			g_pxFragmentShaderP1->SetTexture( FP_MaterialsTexture, Asura_PS3_MaterialResponse_System::GetDataTexture(), false );
			g_pxFragmentShaderP1->SetTexture( FP_NormalMaterialBufferSampler, Asura_PS3_DeferredRendering::GetDeferredFrameBuffer()->GetRTAsTexture( ASURA_DEFERRED_RENDER_TARGET_NORMAL_MATERIAL ), false );
			g_pxFragmentShaderP1->SetTexture( FP_AlbedoSpecularBufferSampler, Asura_PS3_DeferredRendering::GetDeferredFrameBuffer()->GetRTAsTexture( ASURA_DEFERRED_RENDER_TARGET_ALBEDO_SPECULAR ), false );
			g_pxFragmentShaderP1->SetTexture( FP_DepthBufferSampler, Asura_PS3_DeferredRendering::GetDeferredFrameBuffer()->GetZBufferAsTexture(), false );
			g_pxFragmentShaderP1->Bind();
		}
		break;
		case DeferredLight_Pass_Infinite:
		{
			g_pxFragmentShaderP2 = fpTree->GetShader( 2 );
			if( !g_pxFragmentShaderP2 )
			{
				Asura_Assert( false, "Invalid shader (Deferred 2)" );
				return;
			}
			g_pxFragmentShaderP2->SetArray(  FP_InvViewMatrix,	4,	4, xViewMatrixInv.GetElements() );
			g_pxFragmentShaderP2->SetFloats( FP_Falloff,			4, &afFallOff[0] );
			g_pxFragmentShaderP2->SetFloats( FP_CameraTanHalfFOV,	4, &afCameraTanHalfFOV[0] );
			g_pxFragmentShaderP2->SetFloats( FP_TwoOverScreen,		4, &afTwoOverScreenDimensions[0] );
			g_pxFragmentShaderP2->SetFloats( FP_LightColour,		4, &afColour[0] );
			g_pxFragmentShaderP2->SetFloats( FP_LightPosScale,		4, &afPosition[0] );
			g_pxFragmentShaderP2->SetFloats( FP_LightDirection,		4, &afDir[0] );
			g_pxFragmentShaderP2->SetFloats( FP_CameraPosition,		4, afCamPos );

			Asura_PS3_MaterialResponse_System::SetDepthParameters( g_pxFragmentShaderP2, FP_DepthAdjustment );

			g_pxFragmentShaderP2->SetTexture( FP_MaterialsTexture, Asura_PS3_MaterialResponse_System::GetDataTexture(), false );
			g_pxFragmentShaderP2->SetTexture( FP_NormalMaterialBufferSampler, Asura_PS3_DeferredRendering::GetDeferredFrameBuffer()->GetRTAsTexture( ASURA_DEFERRED_RENDER_TARGET_NORMAL_MATERIAL ), false );
			g_pxFragmentShaderP2->SetTexture( FP_AlbedoSpecularBufferSampler, Asura_PS3_DeferredRendering::GetDeferredFrameBuffer()->GetRTAsTexture( ASURA_DEFERRED_RENDER_TARGET_ALBEDO_SPECULAR ), false );
			g_pxFragmentShaderP2->SetTexture( FP_DepthBufferSampler, Asura_PS3_DeferredRendering::GetDeferredFrameBuffer()->GetZBufferAsTexture(), false );
			g_pxFragmentShaderP2->Bind();
		}
		break;
		case DeferredLight_Pass_Limited:
		{
			bool bLimitAnglesEnabled = pxPhoton->GetLimitAnglesEnabled();
			if (bLimitAnglesEnabled)
			{	
				// Handle angular fade - same way as spotlights
				afFallOff[2] = -pxPhoton->GetLimitAngleOuter();
				afFallOff[3] = -(pxPhoton->GetLimitAngleOuter() - pxPhoton->GetLimitAngleInner());
				if (afFallOff[3] != 0.0f)
				{
					afFallOff[3] = 1.0f / afFallOff[3];
				}
				else
				{
					// Nasty case where Inner angle == Outer angle
					afFallOff[3] = 1e15f;
				}
			}
			bool bUseProjCube = (ASURA_TEXTURE_NONE != pxPhoton->GetProjCubeTextureIndex());
			bool bUseDualProjCube = (ASURA_TEXTURE_NONE != pxPhoton->GetProjCubeBlurredTextureIndex());
			float fLerpDist = pxPhoton->GetProjCubeLerpDist();
			const Asura_Matrix_3x3& xPhotonOrient = pxPhoton->GetLightOrientation();
			float faOrient[3][4];

			for( int i = 0; i < 3; ++i )
			{
				for( int j = 0; j < 3; ++j )
				{
					faOrient[ i ][ j ] = xPhotonOrient.Element[i][j];
				}
			}
			if( bLimitAnglesEnabled )
			{
				if( bUseProjCube )
				{
					if( bUseDualProjCube )
					{
						g_pxFragmentShaderP3 = fpTree->GetShader( 3111 );
					}
					else
					{
						g_pxFragmentShaderP3 = fpTree->GetShader( 3110 );
					}
				}
				else
				{
					g_pxFragmentShaderP3 = fpTree->GetShader( 3100 );
				}
			}
			else
			{
				if( bUseProjCube )
				{
					if( bUseDualProjCube )
					{
						g_pxFragmentShaderP3 = fpTree->GetShader( 3011 );
					}
					else
					{
						g_pxFragmentShaderP3 = fpTree->GetShader( 3010 );
					}
				}
				else
				{
					g_pxFragmentShaderP3 = fpTree->GetShader( 3000 );
				}
			}
			if( !g_pxFragmentShaderP3 )
			{
				Asura_Assert( false, "Invalid shader (Deferred 3)" );
				return;
			}
			g_pxFragmentShaderP3->SetArray ( FP_InvViewMatrix,		4, 4, xViewMatrixInv.GetElements() );
			g_pxFragmentShaderP3->SetArray ( FP_LightOrientation,	3, 4, faOrient[ 0 ] );
			g_pxFragmentShaderP3->SetFloats( FP_CameraTanHalfFOV,	2, &afCameraTanHalfFOV[0] );
			g_pxFragmentShaderP3->SetFloats( FP_TwoOverScreen,		2, &afTwoOverScreenDimensions[0] );
			g_pxFragmentShaderP3->SetFloats( FP_LightColour,		4, &afColour[0] );
			g_pxFragmentShaderP3->SetFloats( FP_LightPosScale,		4, &afPosition[0] );
			g_pxFragmentShaderP3->SetFloats( FP_LightDirection,		4, &afDir[0] );
			g_pxFragmentShaderP3->SetFloats( FP_CameraPosition,		4, afCamPos );
			g_pxFragmentShaderP3->SetFloats( FP_Falloff,			4, &afFallOff[0] );
			g_pxFragmentShaderP3->SetFloats( FP_ProjCubeLerpDist,	4, &fLerpDist );

			Asura_PS3_MaterialResponse_System::SetDepthParameters( g_pxFragmentShaderP3, FP_DepthAdjustment );

			g_pxFragmentShaderP3->SetTexture( FP_MaterialsTexture, Asura_PS3_MaterialResponse_System::GetDataTexture(), false );
			g_pxFragmentShaderP3->SetTexture( FP_NormalMaterialBufferSampler, Asura_PS3_DeferredRendering::GetDeferredFrameBuffer()->GetRTAsTexture( ASURA_DEFERRED_RENDER_TARGET_NORMAL_MATERIAL ), false );
			g_pxFragmentShaderP3->SetTexture( FP_AlbedoSpecularBufferSampler, Asura_PS3_DeferredRendering::GetDeferredFrameBuffer()->GetRTAsTexture( ASURA_DEFERRED_RENDER_TARGET_ALBEDO_SPECULAR ), false );
			g_pxFragmentShaderP3->SetTexture( FP_DepthBufferSampler, Asura_PS3_DeferredRendering::GetDeferredFrameBuffer()->GetZBufferAsTexture(), false );

			g_pxFragmentShaderP3->SetTexture( FP_ProjCubeMapSampler, pxPhoton->GetProjCubeTextureIndex() );
			g_pxFragmentShaderP3->SetTexture( FP_ProjCubeMapBlurredSampler, pxPhoton->GetProjCubeBlurredTextureIndex() );
			g_pxFragmentShaderP3->Bind();
		}
	}
}

void Asura_PS3_DeferredShading::BeginFrame(void)
{
}

void Asura_PS3_DeferredShading::EndFrame(void)
{
	++g_iCurrentRenderBuffer;
	g_iCurrentRenderBuffer = g_iCurrentRenderBuffer % ASURA_DEFERRED_BUFFERS;
	g_iCurrentLight = 0;
}

void Asura_PS3_DeferredShading::RenderPhotons(bool bWithDarkeningShadows, bool bWithRealShadows, bool bWithNoShadows)
{
#if !defined (ASURA_USE_NEW_VISIBILITY_SYSTEM)
	Asura_Bounding_Box xBB;

	// Make a bounding box around all visible modules
	xBB.MinX=xBB.MinY=xBB.MinZ = 1e12f;
	xBB.MaxX=xBB.MaxY=xBB.MaxZ = -1e12f;
	{
		const u_int uModuleCount = Asura_Module_System::GetNumberOfModules();
		for( u_int uModuleIndex = 0; uModuleIndex < uModuleCount; uModuleIndex += 1)
		{
			if( false == Asura_Module_System::IsModuleVisible( uModuleIndex))
			{
				continue;
			}
			Asura_Bounding_Box xModuleBoundingBox = Asura_Module_System::GetBoundingBoxOfModule( uModuleIndex);
			xModuleBoundingBox.Displace( Asura_Module_System::GetPositionOfModule(uModuleIndex) );

			xBB.Set( xModuleBoundingBox);
		}
	}
#endif// !defined (ASURA_USE_NEW_VISIBILITY_SYSTEM)


	Asura_PS3_DeferredRendering::BindAsSingleRT();

	Asura_PS3_DeferredShading_RenderPhotons_Callback_Data xCallbackData;
#if !defined (ASURA_USE_NEW_VISIBILITY_SYSTEM)
	xCallbackData.m_xBoundingBox = xBB;
#endif
	xCallbackData.m_bWithDarkeningShadows = bWithDarkeningShadows;
	xCallbackData.m_bWithRealShadows      = bWithRealShadows;
	xCallbackData.m_bWithNoShadows		  = bWithNoShadows;

	Asura_PS3_RenderingStates::RequestAlphaTest( false );
	// I've had some problems with Asura's cached renderstates being out-of-sync with D3D, so we change the
	// state twice to force the change...
	Asura_PS3_RenderingStates::RequestZBufferEnabled( Asura_DeferredShading::IsUsingZBufferOptimization() );

	Asura_PS3_RenderingStates::RequestZBufferWriteEnabled( false );
	Asura_PS3_RenderingStates::RequestTranslucencyMode(ASURA_TRANSLUCENCY_GLOWING); 

	// optimize stencil culling for the tests we're about to perform
	Asura_PS3_Graphics::SwitchStencilCullMode( ASURA_PS3_STENCIL_EQUAL, ASURA_STENCIL_MASK_PHOTON, ASURA_STENCIL_MASK_PHOTON );


	// Traverse all photons, rendering the visible ones
	xCallbackData.m_xCameraPos = *Asura_Camera::GetPosition();
	Asura_Photon_System::TraverseAll( Asura_PS3_DeferredShading::RenderPhotons_Callback, static_cast< void*>( &xCallbackData), false, true);


	Asura_PS3_RenderingStates::RequestZBufferEnabled( true );		
	Asura_PS3_RenderingStates::RequestCullMode( ASURA_CULLMODE_CCW );
	Asura_PS3_RenderingStates::RequestTranslucencyMode(ASURA_TRANSLUCENCY_OFF); 
	Asura_PS3_RenderingStates::RequestZBufferCompareMode(ASURA_ZBUFFERCMP_LESSEQUAL);		

	Asura_PS3_Graphics::Flush();
}

void Asura_PS3_DeferredShading::RenderDebugOutput(void)
{
	// Debug rendering
	if (Asura_DeferredShading::IsRenderingPhotonOutlines())
	{	
		Asura_Photon_System::TraverseAll( Asura_PS3_DeferredShading::RenderPhotonOutlines_Callback, NULL, false, true);
	}

	if (Asura_DeferredShading::IsShowingDeferredBuffers())
	{
		DebugRenderDeferredBuffers();
	}
}

enum DEBUG_RENDER_BUFFER_TYPE {
	DEBUG_RENDER_BUFFER_COLOUR,
	DEBUG_RENDER_BUFFER_NORMAL,
	DEBUG_RENDER_BUFFER_DEPTH,
	DEBUG_RENDER_BUFFER_ALPHA,
	DEBUG_RENDER_BUFFER_MATERIAL,
	DEBUG_RENDER_BUFFER_STENCIL
};

struct DebugRenderBufferInfo
{
	Asura_UnicodeChar*			m_szName;
	Asura_PS3_Texture*			m_pxTexture;
	DEBUG_RENDER_BUFFER_TYPE	m_eType;
};

void DebugRenderBuffer( DebugRenderBufferInfo& xBuffer, float fX, float fY, float fW, float fH, bool bActualPixels=false )
{
	Asura_Overlay_Quad xQuad;

	if ( bActualPixels )
	{
		//1:1 size with pixels, based on centre of source image (i.e. ignore x,y just adjust to match w,h
		float fU = fW / (float)Asura_Viewport::GetWidth();
		float fV = fH / (float)Asura_Viewport::GetHeight();
		xQuad.SetRectUV( 0.5f-fU*0.5f, 0.5f-fV*0.5f, fU, fV );
	}
	else
	{
		xQuad.SetRectUV(0,0,1,1);
	}

	xQuad.SetRectXY(fX, fY, fW, fH);			

	Asura_PS3_RenderingStates::RequestAlphaTest( false );

	Asura_PS3_RenderingStates::RequestTranslucencyMode( ASURA_TRANSLUCENCY_OFF );
	xQuad.TranslucencyMode = ASURA_TRANSLUCENCY_OFF;  //cheat! we use some wierd blend modes here so do it without telling the quad system or render states system.
	Asura_PS3_RenderingStates::SetAlphaBlending( false );


	switch ( xBuffer.m_eType )
	{
		case DEBUG_RENDER_BUFFER_DEPTH: // WRONG! this is arg
			//drop through
		case DEBUG_RENDER_BUFFER_COLOUR:
			xQuad.Colour = 0xffffffff;
			Asura_PS3_QuadOverlayRenderer::RenderEx(xQuad, xBuffer.m_pxTexture );
			break;

		case DEBUG_RENDER_BUFFER_NORMAL:
			//render flat grey
			xQuad.Colour = 0xff808080;
			Asura_PS3_QuadOverlayRenderer::RenderEx(xQuad, Asura_PS3_TextureManagement::GetTexture(ASURA_TEXTURE_NONE) );
			//render buffer over this with blending, so we can see the negative values
			Asura_PS3_RenderingStates::SetAlphaBlending( true );
			cellGcmSetBlendFuncUnsafeInline( Asura_PS3_Graphics::GetGcmCurrentContext(), CELL_GCM_ONE, CELL_GCM_ONE, CELL_GCM_ZERO, CELL_GCM_ONE );
			Asura_PS3_QuadOverlayRenderer::RenderEx(xQuad, xBuffer.m_pxTexture );
			break;

		//alpha channels - render a white quad then draw over it with 
		case DEBUG_RENDER_BUFFER_STENCIL: // WRONG! this is in b
			//drop through
		case DEBUG_RENDER_BUFFER_ALPHA:
			xQuad.Colour = 0xffffffff;
			Asura_PS3_QuadOverlayRenderer::RenderEx(xQuad, Asura_PS3_TextureManagement::GetTexture(ASURA_TEXTURE_NONE) );

			Asura_PS3_RenderingStates::SetAlphaBlending( true );
			cellGcmSetBlendFuncUnsafeInline( Asura_PS3_Graphics::GetGcmCurrentContext(), CELL_GCM_ZERO, CELL_GCM_SRC_ALPHA, CELL_GCM_ZERO, CELL_GCM_ONE );
			xQuad.Colour = 0xff000000;
			Asura_PS3_QuadOverlayRenderer::RenderEx(xQuad, xBuffer.m_pxTexture );
			break;

		case DEBUG_RENDER_BUFFER_MATERIAL:
			//integer values stored in this alpha channel, so render much darker or they'll all saturate out to white.
			xQuad.Colour = 0xff080808; //0xff100440; //saturate out 1 component at a time, for clearer distinctions between different materials
			Asura_PS3_QuadOverlayRenderer::RenderEx(xQuad, Asura_PS3_TextureManagement::GetTexture(ASURA_TEXTURE_NONE) );

			Asura_PS3_RenderingStates::SetAlphaBlending( true );
			cellGcmSetBlendFuncUnsafeInline( Asura_PS3_Graphics::GetGcmCurrentContext(), CELL_GCM_ZERO, CELL_GCM_SRC_ALPHA, CELL_GCM_ZERO, CELL_GCM_ONE );
			xQuad.Colour = 0xff000000;
			Asura_PS3_QuadOverlayRenderer::RenderEx(xQuad, xBuffer.m_pxTexture );
			break;
	}

	Asura_PS3_RenderingStates::InvalidateTranslucencyMode();
	Asura_PS3_RenderingStates::RequestTranslucencyMode( ASURA_TRANSLUCENCY_NORMAL );
	
	// write text on screen
	//ASURA_TEXTFORMAT_CENTREJUSTIFIED | ASURA_TEXTFORMAT_FORMATSTRING | ASURA_TEXTFORMAT_DROPSHADOWS
	Asura_Font_System::Print( fX+fW/2.0f, fY+fH, 1.0f, 0xffffff00, ASURA_TEXTFORMAT_CENTREJUSTIFIED | ASURA_TEXTFORMAT_DROPSHADOWS | ASURA_TEXTFORMAT_VERTICALBOTTOMJUSTIFIED, xBuffer.m_szName );
}

void Asura_PS3_DeferredShading::DebugRenderDeferredBuffers(void)
{
	DebugRenderBufferInfo Buffers[] =
	{
		{ L"Front Buffer",			Asura_PS3_DeferredRendering::GetRTAsTexture(ASURA_DEFERRED_RENDER_TARGET_CURRENT_FRONT_BUFFER),			DEBUG_RENDER_BUFFER_COLOUR },
		{ L"Front Buffer Alpha",	Asura_PS3_DeferredRendering::GetRTAsTexture(ASURA_DEFERRED_RENDER_TARGET_CURRENT_FRONT_BUFFER),			DEBUG_RENDER_BUFFER_ALPHA },
		{ L"Albedo",				Asura_PS3_DeferredRendering::GetDeferredFrameBuffer()->GetRTAsTexture(ASURA_DEFERRED_RENDER_TARGET_ALBEDO_SPECULAR),		DEBUG_RENDER_BUFFER_COLOUR },
		{ L"Specular",				Asura_PS3_DeferredRendering::GetDeferredFrameBuffer()->GetRTAsTexture(ASURA_DEFERRED_RENDER_TARGET_ALBEDO_SPECULAR),		DEBUG_RENDER_BUFFER_ALPHA },
		{ L"Normal",				Asura_PS3_DeferredRendering::GetDeferredFrameBuffer()->GetRTAsTexture(ASURA_DEFERRED_RENDER_TARGET_NORMAL_MATERIAL),		DEBUG_RENDER_BUFFER_NORMAL },
		{ L"Material",				Asura_PS3_DeferredRendering::GetDeferredFrameBuffer()->GetRTAsTexture(ASURA_DEFERRED_RENDER_TARGET_NORMAL_MATERIAL),		DEBUG_RENDER_BUFFER_MATERIAL },
		{ L"Depth",					Asura_PS3_DeferredRendering::GetDeferredFrameBuffer()->GetZBufferAsTexture(),												DEBUG_RENDER_BUFFER_DEPTH },
		{ L"Stencil",				Asura_PS3_DeferredRendering::GetDeferredFrameBuffer()->GetZBufferAsTexture(),												DEBUG_RENDER_BUFFER_STENCIL },
	};

	float fW = (float)(Asura_Viewport::GetWidth()/4);
	float fH = (float)(Asura_Viewport::GetHeight()/4);
	
	Asura_PS3_RenderingStates::SetColorMaskMrt0(0xe);

	Asura_Font_System::SetFont(0);

	// Render the main view
	DebugRenderBuffer( Buffers[g_iSelectedBuffer], 0,0, fW*3.0f, fH*3.0f, true );
	
	// Render the other views
	int j=0;
	for ( int i=0; i < 4; i++ )
	{
		if ( j == g_iSelectedBuffer ) j++;
		DebugRenderBuffer( Buffers[j++], i*fW, 3*fH, fW, fH );
	}
	for ( int i=1; i < 4; i++ )
	{
		if ( j == g_iSelectedBuffer ) j++;
		DebugRenderBuffer( Buffers[j++], 3*fW, (3-i)*fH, fW, fH );
	}

	Asura_PS3_RenderingStates::SetColorMaskMrt0(0xf);

	Asura_Render::FlushDisplay();
}
