//
// Asura_GX_DOF.cpp
//
// Created by KOR - 12 November 2008
//
// Copyright (C) 2008 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

// This file's header.
#include "SpecialFX/Asura_GX_DOF.h"

// Asura
#include "Rendering/Asura_Render.h"
// #include "SpecialFX/Asura_DepthofField_System.h"

// Asura Wii
#include "Rendering/Asura_GX.h"
#include "Rendering/Asura_GX_TextureManagement.h"

// Lib
#include <revolution/gx.h>

// Asura_GX_DOF static members ////////////////////////////////////////////////////////////////////

u_short*	Asura_GX_DOF::s_pusZBuffer = NULL;
u_int*		Asura_GX_DOF::s_puBlurBuffer = NULL;
u_char*		Asura_GX_DOF::s_pucGradientBuffer = NULL;
GXTexObj	Asura_GX_DOF::s_xZTexObj;
GXTexObj	Asura_GX_DOF::s_xBlurTexObj;
GXTexObj	Asura_GX_DOF::s_xGradientTexObj;

// Asura_GX_DOF implementation ////////////////////////////////////////////////////////////////////

DEBUGVAR float dbg_fNearFocusProportion = 0.25f;
DEBUGVAR float dbg_fFarFocusProportion = 0.25f;
void Asura_GX_DOF::Initialise()
{
	if ( !s_pusZBuffer && !s_puBlurBuffer && !s_pucGradientBuffer )
	{
		// Allocate and assign textures

		// Allocate buffers
		const int iScreenWidth = Asura_GX::GetScreenWidth();
		const int iScreenHeight = Asura_GX::GetScreenHeight(); 
		const int iHalfScreenWidth = iScreenWidth / 2;
		const int iHalfScreenHeight = iScreenHeight / 2;

		s_pusZBuffer		= static_cast<u_short*>	(Asura_AllocateAlign(iScreenWidth * OSRoundUp32B(iScreenHeight) * sizeof(u_short),		ASURA_ALIGN_32_BYTE));
		s_puBlurBuffer		= static_cast<u_int*>	(Asura_AllocateAlign(iHalfScreenWidth * OSRoundUp32B(iHalfScreenHeight) * sizeof(u_int),ASURA_ALIGN_32_BYTE));
		// 4 texels high even though it's a 1D texture; makes swizzling a lot easier.
		s_pucGradientBuffer = static_cast<u_char*>	(Asura_AllocateAlign(uASURA_GX_DOF_GRADIENT_SIZE * 4 * sizeof(u_char),					ASURA_ALIGN_32_BYTE));

		Asura_Assert( s_pusZBuffer && s_puBlurBuffer && s_pucGradientBuffer, "Failed to allocate depth of field buffer");
		GXInitTexObj( &s_xZTexObj,			s_pusZBuffer,			iScreenWidth,					iScreenHeight,		GX_TF_IA8,		GX_CLAMP, GX_CLAMP, GX_DISABLE );
		GXInitTexObj( &s_xBlurTexObj,		s_puBlurBuffer,			iHalfScreenWidth,				iHalfScreenHeight,	GX_TF_RGBA8,	GX_CLAMP, GX_CLAMP, GX_DISABLE);
		GXInitTexObj( &s_xGradientTexObj,	s_pucGradientBuffer,	uASURA_GX_DOF_GRADIENT_SIZE,	4,					GX_TF_I8,		GX_CLAMP, GX_CLAMP, GX_DISABLE );
		GXInitTexObjLOD( &s_xZTexObj,		GX_NEAR,	GX_NEAR,	0.f, 0.f, 0.f, GX_DISABLE, GX_DISABLE, GX_ANISO_1 );
		GXInitTexObjLOD( &s_xBlurTexObj,	GX_LINEAR,	GX_LINEAR,	0.f, 0.f, 0.f, GX_DISABLE, GX_DISABLE, GX_ANISO_1 );
		GXInitTexObjLOD( &s_xGradientTexObj,GX_LINEAR,	GX_LINEAR,	0.f, 0.f, 0.f, GX_DISABLE, GX_DISABLE, GX_ANISO_1 );

		// Generate gradient texture
		u_char* pucTempBuffer = static_cast<u_char*>(Asura_AllocateAlign(uASURA_GX_DOF_GRADIENT_SIZE * 4 * sizeof(u_char),ASURA_ALIGN_32_BYTE));
		for(u_int uX = 0; uX < uASURA_GX_DOF_GRADIENT_SIZE; ++uX)
		{
			const float fProportion = static_cast<float>(uX) / uASURA_GX_DOF_GRADIENT_SIZE;
			float fValue = 0.f;
			if(fProportion < dbg_fNearFocusProportion)
			{
				fValue = 1.f - (fProportion / dbg_fNearFocusProportion);
			}
			else if(fProportion > 1.f - dbg_fFarFocusProportion)
			{
				fValue = (fProportion + dbg_fFarFocusProportion - 1.f) / dbg_fFarFocusProportion;
			}
			Asura_Assert(0.f <= fValue && fValue <= 1.f, "Generated gradient value out of range");
			const u_char ucValue = Asura_Maths::ClampToRange(Asura_Maths::FloatToInt_Near(255.f * fValue), 0i, 255i);;
			for(u_int uY = 0; uY < 4; ++uY)
			{
				pucTempBuffer[uY * uASURA_GX_DOF_GRADIENT_SIZE + uX] = ucValue;
			}
		}

		// Swizzle
		Asura_GX_Texture_Management::SwizzleTexture(pucTempBuffer, s_pucGradientBuffer, uASURA_GX_DOF_GRADIENT_SIZE, 4);
		// Done with the temporary buffer now.
		Asura_DeallocateAlign(pucTempBuffer);
	} 
	else
	{
		Asura_Assert( false , "Depth of field already initialised?");
	}
}

void Asura_GX_DOF::Shutdown()
{
	// Deallocate textures
	Asura_DeallocateAlign(s_pusZBuffer);
	Asura_DeallocateAlign(s_puBlurBuffer);
	Asura_DeallocateAlign(s_pucGradientBuffer);
}

static void Render2DQuad(u_int uX, u_int uY, u_int uWidth, u_int uHeight)
{
	// vertex descriptor settings
	GXClearVtxDesc( );
	GXSetVtxDesc( GX_VA_POS, GX_DIRECT );
	GXSetVtxDesc( GX_VA_TEX0, GX_DIRECT );

	// Vertices
	GXBegin(GX_QUADS, GX_VTXFMT2, 4);
		GXPosition2f32(uX, uY);
		GXTexCoord2f32(0.0F, 0.0F);
		GXPosition2f32(uX + uWidth, uY);
		GXTexCoord2f32(1.0F, 0.0F);
		GXPosition2f32(uX + uWidth, uY + uHeight);
		GXTexCoord2f32(1.0F, 1.0F);
		GXPosition2f32(uX, uY + uHeight);
		GXTexCoord2f32(0.0F, 1.0F);
	GXEnd();
}

static float WorldToScreenZ(float fWorldZ)
{
	// Avoid div by zero
	if ( fWorldZ <= 0.001f)
	{
		return 0.0f;
	}
	const float fScreenZ = (((fASURA_GX_FAR_CLIP * fASURA_GX_NEAR_CLIP) / fWorldZ) - fASURA_GX_NEAR_CLIP) / (fASURA_GX_FAR_CLIP - fASURA_GX_NEAR_CLIP);
	Asura_Assert(0.f <= fScreenZ && fScreenZ <= 1.f, "Screen Z out of range" );
	return (1.f - fScreenZ);
}

DEBUGVAR bool dbg_bDebugTextureRender = false;
DEBUGVAR u_int dbg_uDebugTexture = 0;
DEBUGVAR bool dbg_bRenderFocusMask = false;
DEBUGVAR bool dbg_bRenderFullyBlurred = false;
DEBUGVAR bool dbg_bDiagonalBlurMask = false;
DEBUGVAR float dbg_fBlurPixelDistance = 2.f;
DEBUGVAR bool dbg_bFullPrecisionBlur = false;
void Asura_GX_DOF::Render( const float fNearZ, const float fFarZ, const float fBlurSize, const float fBlurStrength )
{
	// For the moment, we will only be applying DoF to a chosen viewport.
	// If we want greater control over which viewports get the effect,
	// we will need to enable this appropriately.
	//Asura_Render::AllowFullScreenRendering();

	Asura_GX::Set3DProjection(false);
	Asura_Render::CurrentStates.RequestAlphaTest(false);
	Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_OFF);
	Asura_Render::CurrentStates.RequestZBufferEnabled(false);
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(false);
	Asura_Render::CurrentStates.RequestCullMode(ASURA_CULLMODE_NONE);

	// First, framebuffer copy to textures
	const GXRenderModeObj& xRenderMode = Asura_GX::GetRenderModeObject();
	const int iScreenWidth = Asura_GX::GetScreenWidth();
	const int iScreenHeight = Asura_GX::GetScreenHeight();
	const int iHalfScreenWidth = iScreenWidth / 2;
	const int iHalfScreenHeight = iScreenHeight / 2;
	const float fScreenWidth = static_cast<float>(iScreenWidth);
	const float fScreenHeight = static_cast<float>(iScreenHeight);

	// Disable the filter as it's doing nasty things to the exact pixel values I'd like.
	GXSetCopyFilter(GX_FALSE, xRenderMode.sample_pattern, GX_FALSE, xRenderMode.vfilter);

	// Copy out eFB z plane as I8 texture format
	GXSetTexCopySrc( 0, 0, iScreenWidth, iScreenHeight );
	GXSetTexCopyDst(iScreenWidth, iScreenHeight, GX_TF_Z16, GX_DISABLE );
	GXCopyTex( s_pusZBuffer, GX_DISABLE );

	// Copy out box filtered half size colour framebuffer
	GXSetTexCopyDst(iHalfScreenWidth, iHalfScreenHeight, GX_TF_RGBA8, GX_ENABLE );
	GXCopyTex( s_puBlurBuffer, GX_DISABLE );

	// Be sure texture is completely copied out
	GXPixModeSync();

	// flush texture cache
	GXInvalidateTexAll();

	if(dbg_bDebugTextureRender)
	{
		GXSetNumTexGens(1);
		GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4,  GX_TG_TEX0, GX_IDENTITY);

		GXSetNumTevStages(1);
		GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0,  GX_COLOR_NULL);
		GXSetTevOp(GX_TEVSTAGE0, GX_REPLACE);

		if(dbg_uDebugTexture == 0)
		{
			// Render IA z texture as red/cyan
			GXSetTevSwapModeTable(GX_TEV_SWAP0, GX_CH_ALPHA, GX_CH_GREEN, GX_CH_BLUE, GX_CH_ALPHA);
		}
		else
		{
			GXSetTevSwapModeTable(GX_TEV_SWAP0, GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_ALPHA);
		}
		GXSetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0);

		switch(dbg_uDebugTexture)
		{
			default:
			case 0: GXLoadTexObj(&s_xZTexObj, GX_TEXMAP0); break; // 0 = z texture
			case 1: GXLoadTexObj(&s_xBlurTexObj, GX_TEXMAP0); break; // 1 = blur
			case 2: GXLoadTexObj(&s_xGradientTexObj, GX_TEXMAP0); break; // 2 = gradient
		}

		Render2DQuad(0.f, 0.f, fScreenWidth, fScreenHeight);
		
		GXSetTevSwapModeTable(GX_TEV_SWAP0, GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_ALPHA);
		return;
	}

	// Set up indirect matrix.
	const float fNearScreenZ = WorldToScreenZ( fNearZ );
	const float fFarScreenZ = WorldToScreenZ( fFarZ );
	const float fScreenZRange = fFarScreenZ - fNearScreenZ;
	if(fScreenZRange <= 0.f)
	{
		Asura_Assert(0, "Invalid depth of field settings");
		// Could simply blur the whole screen?
		return;
	}
	float fScale = (1.f / fScreenZRange);

	// Calculate offset before rescaling into optimal range
	const float fOffset = -fNearScreenZ * fScale * 255.f / uASURA_GX_DOF_GRADIENT_SIZE;

	// Rescale to make best use of limited precision indirect matrix.
	int iExponent = 0;
	while(fScale < .5f || 1.f <= fScale )
	{
		if(fScale < .5f)
		{
			fScale *= 2.f;
			--iExponent;
		}
		else
		{
			fScale *= .5f;
			++iExponent;
		}
	}

	Asura_Assert(-17 <= iExponent && iExponent <= 46, "Indirect texture matrix exponent out of range");

	const float afIndTexMtx[2][3] = { { fScale/256.f, fScale, 0.f}, {0.f, 0.f, 0.f} };
	GXSetIndTexMtx(GX_ITM_0, afIndTexMtx, iExponent);

	// Set up TEV
	GXSetNumIndStages(1);
	GXSetIndTexOrder(GX_INDTEXSTAGE0, GX_TEXCOORD1, GX_TEXMAP0);
	GXSetTevIndirect(GX_TEVSTAGE0, GX_INDTEXSTAGE0, GX_ITF_8, GX_ITB_NONE, GX_ITM_0, GX_ITW_OFF, GX_ITW_OFF, GX_FALSE, GX_FALSE, GX_ITBA_OFF);

	if(dbg_bRenderFocusMask)
	{
		GXSetNumTexGens(2);
		GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4,  GX_TG_TEX0, GX_IDENTITY);
		GXSetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4,  GX_TG_TEX1, GX_IDENTITY);

		GXSetNumTevStages(1);
		GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP2,  GX_COLOR_NULL);
		GXSetTevOp(GX_TEVSTAGE0, GX_REPLACE);
	}
	else
	{
		GXSetNumTexGens(6);
		// Offset for focus mask
		GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4,  GX_TG_TEX0, GX_IDENTITY);
		// Screen co-ordinates
		GXSetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4,  GX_TG_TEX1, GX_IDENTITY);
		// Blur offsets
		GXSetTexCoordGen(GX_TEXCOORD2, GX_TG_MTX2x4,  GX_TG_TEX1, GX_TEXMTX0);
		GXSetTexCoordGen(GX_TEXCOORD3, GX_TG_MTX2x4,  GX_TG_TEX1, GX_TEXMTX1);
		GXSetTexCoordGen(GX_TEXCOORD4, GX_TG_MTX2x4,  GX_TG_TEX1, GX_TEXMTX2);
		GXSetTexCoordGen(GX_TEXCOORD5, GX_TG_MTX2x4,  GX_TG_TEX1, GX_TEXMTX3);

		float afTextureMatrix[3][4] = { {1.f, 0.f, 0.f, 0.f}, {0.f, 1.f, 0.f, 0.f} };
		const float fXOffset = fBlurSize / fScreenWidth;
		const float fYOffset = fBlurSize / fScreenHeight;

		if(dbg_bDiagonalBlurMask)
		{
			afTextureMatrix[0][3] = fXOffset; afTextureMatrix[1][3] = fYOffset;
			GXLoadTexMtxImm(&afTextureMatrix[0], GX_TEXMTX0, GX_MTX2x4);
			afTextureMatrix[0][3] =-fXOffset; afTextureMatrix[1][3] = fYOffset;
			GXLoadTexMtxImm(&afTextureMatrix[0], GX_TEXMTX1, GX_MTX2x4);
			afTextureMatrix[0][3] =-fXOffset; afTextureMatrix[1][3] =-fYOffset;
			GXLoadTexMtxImm(&afTextureMatrix[0], GX_TEXMTX2, GX_MTX2x4);
			afTextureMatrix[0][3] = fXOffset; afTextureMatrix[1][3] =-fYOffset;
			GXLoadTexMtxImm(&afTextureMatrix[0], GX_TEXMTX3, GX_MTX2x4);
		}
		else
		{
			// X offsets
			afTextureMatrix[0][3] = fXOffset;
			GXLoadTexMtxImm(&afTextureMatrix[0], GX_TEXMTX0, GX_MTX2x4);
			afTextureMatrix[0][3] = -fXOffset;
			GXLoadTexMtxImm(&afTextureMatrix[0], GX_TEXMTX1, GX_MTX2x4);

			// Y offsets
			afTextureMatrix[0][3] = 0.0f;
			afTextureMatrix[1][3] = fYOffset;
			GXLoadTexMtxImm(&afTextureMatrix[0], GX_TEXMTX2, GX_MTX2x4);
			afTextureMatrix[1][3] = -fYOffset;
			GXLoadTexMtxImm(&afTextureMatrix[0], GX_TEXMTX3, GX_MTX2x4);
		}

		if(dbg_bRenderFullyBlurred)
		{
			Asura_Render::CurrentStates.RequestAlphaTest(false);
			Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_OFF);
		}
		else
		{
			Asura_Render::CurrentStates.RequestAlphaTest(true);
			Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_NORMAL);
		}
		
		GXSetNumTevStages(dbg_bFullPrecisionBlur ? 6 : 5);

		GXSetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0);
		GXSetTevSwapMode(GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP0);
		GXSetTevSwapMode(GX_TEVSTAGE2, GX_TEV_SWAP0, GX_TEV_SWAP0);
		GXSetTevSwapMode(GX_TEVSTAGE3, GX_TEV_SWAP0, GX_TEV_SWAP0);
		GXSetTevSwapMode(GX_TEVSTAGE4, GX_TEV_SWAP0, GX_TEV_SWAP0);
		GXSetTevSwapMode(GX_TEVSTAGE5, GX_TEV_SWAP0, GX_TEV_SWAP0);

		// First calculate focus mask and store in alpha
		const u_char ucStrengthAlpha = Asura_Maths::ClampToRange(Asura_Maths::FloatToInt_Near(fBlurStrength * 255.f), 0i, 255i);
		GXColor xStrengthAlpha = { 0xff, 0xff, 0xff, ucStrengthAlpha };
		GXSetTevKColor(GX_KCOLOR0, xStrengthAlpha);
		GXSetTevKAlphaSel(GX_TEVSTAGE0, GX_TEV_KASEL_K0_A);

		GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP2,  GX_COLOR_NULL);
		GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO);
		GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_KONST, GX_CA_TEXA, GX_CA_ZERO);
		GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
		GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);

		// Accumulate blur
		// +X offset
		if(!dbg_bFullPrecisionBlur)
		{
			GXSetTevKColorSel(GX_TEVSTAGE1, GX_TEV_KCSEL_1_4);
			GXSetTevKColorSel(GX_TEVSTAGE2, GX_TEV_KCSEL_1_4);
			GXSetTevKColorSel(GX_TEVSTAGE3, GX_TEV_KCSEL_1_4);
			GXSetTevKColorSel(GX_TEVSTAGE4, GX_TEV_KCSEL_1_4);
		}
		const GXTevColorArg eBlurConstant = dbg_bFullPrecisionBlur ? GX_CC_ONE : GX_CC_KONST;
		const bool bClampColour = !dbg_bFullPrecisionBlur;
		GXSetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD2, GX_TEXMAP1,  GX_COLOR_NULL);
		GXSetTevColorIn(GX_TEVSTAGE1, GX_CC_ZERO, eBlurConstant, GX_CC_TEXC, GX_CC_ZERO);
		GXSetTevAlphaIn(GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV);
		GXSetTevColorOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, bClampColour, GX_TEVPREV);
		GXSetTevAlphaOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);

		// -X offset
		GXSetTevOrder(GX_TEVSTAGE2, GX_TEXCOORD3, GX_TEXMAP1,  GX_COLOR_NULL);
		GXSetTevColorIn(GX_TEVSTAGE2, GX_CC_ZERO, eBlurConstant, GX_CC_TEXC, GX_CC_CPREV);
		GXSetTevAlphaIn(GX_TEVSTAGE2, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV);
		GXSetTevColorOp(GX_TEVSTAGE2, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, bClampColour, GX_TEVPREV);
		GXSetTevAlphaOp(GX_TEVSTAGE2, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);

		// +Y offset
		GXSetTevOrder(GX_TEVSTAGE3, GX_TEXCOORD4, GX_TEXMAP1,  GX_COLOR_NULL);
		GXSetTevColorIn(GX_TEVSTAGE3, GX_CC_ZERO, eBlurConstant, GX_CC_TEXC, GX_CC_CPREV);
		GXSetTevAlphaIn(GX_TEVSTAGE3, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV);
		GXSetTevColorOp(GX_TEVSTAGE3, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, bClampColour, GX_TEVPREV);
		GXSetTevAlphaOp(GX_TEVSTAGE3, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);

		// -Y offset
		GXSetTevOrder(GX_TEVSTAGE4, GX_TEXCOORD5, GX_TEXMAP1,  GX_COLOR_NULL);
		GXSetTevColorIn(GX_TEVSTAGE4, GX_CC_ZERO, eBlurConstant, GX_CC_TEXC, GX_CC_CPREV);
		GXSetTevAlphaIn(GX_TEVSTAGE4, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV);
		GXSetTevColorOp(GX_TEVSTAGE4, GX_TEV_ADD, GX_TB_ZERO, dbg_bFullPrecisionBlur ? GX_CS_DIVIDE_2 : GX_CS_SCALE_1, bClampColour, GX_TEVPREV);
		GXSetTevAlphaOp(GX_TEVSTAGE4, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);

		if(dbg_bFullPrecisionBlur)
		{
			// Divide by 2 again
			GXSetTevOrder(GX_TEVSTAGE5, GX_TEXCOORD_NULL, GX_TEXMAP_NULL,  GX_COLOR_NULL);
			GXSetTevColorIn(GX_TEVSTAGE5, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_CPREV);
			GXSetTevAlphaIn(GX_TEVSTAGE5, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV);
			GXSetTevColorOp(GX_TEVSTAGE5, GX_TEV_ADD, GX_TB_ZERO, GX_CS_DIVIDE_2, GX_ENABLE, GX_TEVPREV);
			GXSetTevAlphaOp(GX_TEVSTAGE5, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV);
		}
	}

	GXLoadTexObj(&s_xZTexObj, GX_TEXMAP0);
	GXLoadTexObj(&s_xBlurTexObj, GX_TEXMAP1);
	GXLoadTexObj(&s_xGradientTexObj, GX_TEXMAP2);

	// Render fullscreen quad
	GXClearVtxDesc( );
	GXSetVtxDesc( GX_VA_POS, GX_DIRECT );
	GXSetVtxDesc( GX_VA_TEX0, GX_DIRECT );
	GXSetVtxDesc( GX_VA_TEX1, GX_DIRECT );

	GXBegin(GX_QUADS, GX_VTXFMT2, 4);
		GXPosition2f32(0.f, 0.f);					GXTexCoord2f32(fOffset, 0.f);	GXTexCoord2f32(0.f, 0.f);
		GXPosition2f32(fScreenWidth, 0.f);			GXTexCoord2f32(fOffset, 0.f);	GXTexCoord2f32(1.f, 0.f);
		GXPosition2f32(fScreenWidth, fScreenHeight);GXTexCoord2f32(fOffset, 0.f);	GXTexCoord2f32(1.f, 1.f);
		GXPosition2f32(0.f, fScreenHeight);			GXTexCoord2f32(fOffset, 0.f);	GXTexCoord2f32(0.f, 1.f);
	GXEnd();

	// Reset any specialist render states
	GXSetTevDirect(GX_TEVSTAGE0);
	Asura_Render::CurrentStates.RequestTextureIndex(0, ASURA_TEXTURE_NONE);
	Asura_Render::CurrentStates.RequestTextureIndex(1, ASURA_TEXTURE_NONE);
	Asura_Render::CurrentStates.RequestTextureIndex(2, ASURA_TEXTURE_NONE);
}