//*******************************************************************
//
// File: Asura_PlatformFSFX_RadialBlur.cpp   
//
// Created: 28 Feb 2006
//
// Description: 
//
//*******************************************************************

// system libraries
#include "Core/Asura.h"

#if defined( ASURA_USE_FSFX)

// main header
#include "FSFX/Asura_FSFX_System.h"
#include "FSFX/Asura_FSFX_Template.h"
#include "FSFX/Asura_FSFX_Component.h"

#include "Rendering/Asura_Direct3D_Caps.h"
#include "Rendering/Asura_Render.h"
#include "Rendering/Asura_Direct3D_RenderTargetManager.h"
#include "Rendering/Asura_Direct3D_BackBufferCapture.h"
#include "Rendering/Asura_Direct3D_Shaders.h"
#include "Rendering/Asura_Direct3D_UserOptions.h"

// other libraries

// internal dependencies

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


//*******************************************************************
// Local Classes
//*******************************************************************


//*******************************************************************
// Function Definitions
//*******************************************************************

static Direct3D_VertexElement auVSD[] =
{
	{ 0,	0,	D3DDECLTYPE_FLOAT3,	D3DDECLMETHOD_DEFAULT,	D3DDECLUSAGE_POSITION,	0 },
	{ 0,   12,	D3DDECLTYPE_FLOAT2,	D3DDECLMETHOD_DEFAULT,	D3DDECLUSAGE_TEXCOORD,	0 },
	D3DDECL_END()
};

//Apply Blur shader
//expected constants:
// c0-c3	Texcoord offsets
const char* const g_szVS_APPLY_RADIAL_BLUR_CODE =

"#line 1 \" "__FILE__"\" \n"
"vs.1.1 \n"
"dcl_position0 v0 \n"
"dcl_texcoord0 v1 \n"

"mov oPos, v0 \n"

"dp4 oT0.x, v0, c0 \n"
"dp4 oT0.y, v0, c1 \n"

"dp4 oT1.x, v0, c2 \n"
"dp4 oT1.y, v0, c3 \n"

"dp4 oT2.x, v0, c4 \n"
"dp4 oT2.y, v0, c5 \n"

"dp4 oT3.x, v0, c6 \n"
"dp4 oT3.y, v0, c7 \n"

;

//register it with Asura
const Asura_Direct3D_VertexShader g_xVS_APPLY_RADIAL_BLUR_SHADER(ASURA_D3D_VS_FSFX_RADIALBLUR, g_szVS_APPLY_RADIAL_BLUR_CODE, auVSD);

static const float afHBLUR_TEXCOORD_OFFSETS[4][4] =
{
	{ -3.0f / 4.0f, 0.5f, 0.0f, 0.0f },
	{ -1.0f / 4.0f, 0.5f, 0.0f, 0.0f },
	{  1.0f / 4.0f, 0.5f, 0.0f, 0.0f },
	{  3.0f / 4.0f, 0.5f, 0.0f, 0.0f },
};

static const float afVBLUR_TEXCOORD_OFFSETS[4][4] =
{
	{ 0.5f, -3.0f / 4.0f, 0.0f, 0.0f },
	{ 0.5f, -1.0f / 4.0f, 0.0f, 0.0f },
	{ 0.5f,  1.0f / 4.0f, 0.0f, 0.0f },
	{ 0.5f,  3.0f / 4.0f, 0.0f, 0.0f },
};

// Filter weights
static const float afFILTER_WEIGHTS[4][4] =
{
	{ 0.25f, 0.25f, 0.25f, 0.25f },
	{ 0.25f, 0.25f, 0.25f, 0.25f },
	{ 0.25f, 0.25f, 0.25f, 0.25f },
	{ 0.25f, 0.25f, 0.25f, 0.25f },
};

struct SimpleVertex
{
	float m_fX, m_fY, m_fZ;
	float m_fU, m_fV;
};

static SimpleVertex axQUAD_VERTEX_DATA[9] =
{
	{ -1.0f,  1.0f,  1.0f,  0.0f,  0.0f },
	{  0.0f,  1.0f,  1.0f,  0.5f,  0.0f },
	{  1.0f,  1.0f,  1.0f,  1.0f,  0.0f },
	
	{ -1.0f,  0.0f,  1.0f,  0.0f,  0.5f },
	{  0.0f,  0.0f,  1.0f,  0.5f,  0.5f },
	{  1.0f,  0.0f,  1.0f,  1.0f,  0.5f },
	
	{ -1.0f, -1.0f,  1.0f,  0.0f,  1.0f },
	{  0.0f, -1.0f,  1.0f,  0.5f,  1.0f },
	{  1.0f, -1.0f,  1.0f,  1.0f,  1.0f }
};

static uint16 auQUAD_INDEDX_DATA[ 3 * 2 * 8] =
{
	0, 1, 3,
	1, 3, 4,

	1, 2, 4,
	2, 4, 5,	
	
	3, 4, 6,
	4, 6, 7,
	
	4, 5, 7,
	5, 7, 8
};


static void Asura_FSFX_Component_RadialBlur_UploadVSConstant( const uint32 uMatrixIndex, const D3DMATRIX& xD3DMatrix)
{
	HRESULT xResult;
	
	const uint32 uFirstConstant = 2 * uMatrixIndex;
	
	const float afConstants[2][4] = 
	{
		{ xD3DMatrix.m[0][0], xD3DMatrix.m[1][0], xD3DMatrix.m[2][0], xD3DMatrix.m[3][0]},
		{ xD3DMatrix.m[0][1], xD3DMatrix.m[1][1], xD3DMatrix.m[2][1], xD3DMatrix.m[3][1]},
	};
	xResult = Asura_Direct3D::D3DDevice->SetVertexShaderConstantF( uFirstConstant, &afConstants[0][0], 3);
	ASURA_D3D_OK( xResult);
}

static Asura_Direct3D_RenderTarget* g_pxRadialBlurRT = NULL;

void Asura_FSFX_Component_RadialBlur_PlatformInitialise()
{
	g_pxRadialBlurRT = Asura_Direct3D_RenderTargetManager::CreateRenderTarget( 0.5f, 0.5f,
		Asura_Direct3D_UserOptions::GetRenderTargetBufferFormat(), D3DFMT_UNKNOWN, true );
}

void Asura_FSFX_Component_RadialBlur_PlatformShutdown()
{
	Asura_Direct3D_RenderTargetManager::DestroyRenderTarget( g_pxRadialBlurRT );
	g_pxRadialBlurRT = NULL;
}

void Asura_FSFX_Component_RadialBlur::Platform_Render( const float fIntensity )
{
	if ( fIntensity == 0.0f )
	{
		return;
	}

	HRESULT xResult;
	
	Asura_Direct3D_RenderTarget* pxBlurBuffer = g_pxRadialBlurRT;

	SCOPEDPIXEVENT(( 0xFFE0D0C0, L"FSFX: Radial Blur" ));

	// set up some states that persist across the effect

	Asura_Render::CurrentStates.RequestTextureOpMode(ASURA_TEXTUREOP_OPAQUE);
	Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_OFF);
	Asura_Render::CurrentStates.RequestAlphaTest(false);
	Asura_Render::CurrentStates.RequestZBufferEnabled(false);
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(false);

	// Use clamping of tex co-ords

	DWORD uOldAddressU[4], uOldAddressV[4];
	Direct3D_BaseTexture* pxOldTexture[4];
	for (u_int u = 0; u < 4; ++u)
	{
		xResult = Asura_Direct3D::D3DDevice->GetSamplerState(u, D3DSAMP_ADDRESSU, &uOldAddressU[u]);
		ASURA_D3D_OK( xResult);
		xResult = Asura_Direct3D::D3DDevice->GetSamplerState(u, D3DSAMP_ADDRESSV, &uOldAddressV[u]);
		ASURA_D3D_OK( xResult);

		xResult = Asura_Direct3D::D3DDevice->SetSamplerState(u, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
		ASURA_D3D_OK( xResult);
		xResult = Asura_Direct3D::D3DDevice->SetSamplerState(u, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
		ASURA_D3D_OK( xResult);

		xResult = Asura_Direct3D::D3DDevice->GetTexture(u, &pxOldTexture[u]);
		ASURA_D3D_OK( xResult);
	}

	// ensure that we have a backbuffer capture knocking about
	if(!Asura_Direct3D_BackBufferCapture::HasCapturedThisFrame())
	{
		Asura_Direct3D_BackBufferCapture::CaptureCurrentBackBuffer();
	}
	Asura_Render::AllowFullScreenRendering();

	// get the current render target
	Direct3D_Surface* pxOldRenderTarget = NULL;
	xResult = Asura_Direct3D::D3DDevice->GetRenderTarget(0, &pxOldRenderTarget);
	ASURA_D3D_OK( xResult);

	#if defined ASURA_TARGET_SM3
		Asura_Direct3D::D3DDevice->SetRenderState( D3DRS_SRGBWRITEENABLE, FALSE );
	#endif

	// scale the blur offsets by the texture size so that they remain in terms of pixels
	float afScaledHBlurOffsets[4][4], afScaledVBlurOffsets[4][4];
	float afScaledFilterWeights[4][4];
	const float fSampleSize = this->m_fSize;
	const float fSampleStrength3 = 1.0f - (0.75f * (fIntensity * this->m_fStrength / 100.0f));
	const float fSampleStrength0 = 0.25f * (1.0f - fSampleStrength3);
	const float fSampleStrength2 = 0.5f * (1.0f - fSampleStrength3 - fSampleStrength0);
	const float fSampleStrength1 = fSampleStrength2;
	
	// DLH - 22 February 2006 12:52:16 - I have 2* the blur buffer to get the size back to what the original buffer is. Stops the vertical bluring twice as fast as the horizontal.
	const float fBlurBufferWidth = 1.0f * static_cast<float>(pxBlurBuffer->GetWidth());
	const float fBlurBufferHeight = 1.0f * static_cast<float>(pxBlurBuffer->GetHeight());
	for ( u_int u = 0; u < 4; ++u )
	{
		afScaledHBlurOffsets[u][0] = ((fSampleSize * afHBLUR_TEXCOORD_OFFSETS[u][0]) + 0.5f) / static_cast<float>(Asura_Direct3D::GetPresentParameters().BackBufferWidth);
		afScaledHBlurOffsets[u][1] = (afHBLUR_TEXCOORD_OFFSETS[u][1] + 0.5f) / static_cast<float>(Asura_Direct3D::GetPresentParameters().BackBufferHeight);
		afScaledVBlurOffsets[u][0] = (afVBLUR_TEXCOORD_OFFSETS[u][0] + 0.5f) / fBlurBufferWidth;
		afScaledVBlurOffsets[u][1] = ((fSampleSize * afVBLUR_TEXCOORD_OFFSETS[u][1]) + 0.5f) / fBlurBufferHeight;
		afScaledHBlurOffsets[u][2] = afScaledHBlurOffsets[u][3] = 0.0f;
		afScaledVBlurOffsets[u][2] = afScaledVBlurOffsets[u][3] = 0.0f;
		
		afScaledFilterWeights[ 0][ u] = fSampleStrength3;
		afScaledFilterWeights[ 1][ u] = fSampleStrength2;
		afScaledFilterWeights[ 2][ u] = fSampleStrength1;
		afScaledFilterWeights[ 3][ u] = fSampleStrength0;
	}

	// this is now the fixed alpha to write out (we've got an alpha in the capture buffer now, and sampling/blurring that was causing problems)
	afScaledFilterWeights[0][3] = 1.0f;

	// set up the blur shaders
	Asura_Direct3D_Shaders::RequestVertexShader(ASURA_D3D_VS_FSFX_RADIALBLUR);
	Asura_Direct3D_Shaders::RequestPixelShader(ASURA_D3D_PS_APPLYBLUR);
	
	//Asura_Direct3D::D3DDevice->SetPixelShaderConstantF(0, &afScaledFilterWeights[0][0], 4);
	xResult = Asura_Direct3D::D3DDevice->SetPixelShaderConstantF(0, &afScaledFilterWeights[0][0], 4);
	ASURA_D3D_OK( xResult);
	
////// Horizontal blur pass

	// set our blur buffer as the target
	xResult =Asura_Direct3D::D3DDevice->SetRenderTarget(0, pxBlurBuffer->GetSurface());
	ASURA_D3D_OK( xResult);
	{
		unsigned int u;
		for (u = 1; u < Asura_Direct3D_Caps::GetMaxNumberOfRenderTargets(); ++u)
		{
			Asura_Direct3D::D3DDevice->SetRenderTarget(u, NULL);
		}
	}

	// (AAM - 26.10.2007) Preserve the depth-stencil surface
	Direct3D_Surface* pxOldDepthStencilSurface = NULL;
	xResult = Asura_Direct3D::D3DDevice->GetDepthStencilSurface(&pxOldDepthStencilSurface);
	ASURA_D3D_OK( xResult);

 	xResult = Asura_Direct3D::D3DDevice->SetDepthStencilSurface(NULL);
 	ASURA_D3D_OK( xResult);

	// set the backbuffer capture as the textures
	for(u_int u = 0; u < 4; ++u)
	{
		xResult = Asura_Direct3D::D3DDevice->SetTexture(u, Asura_Direct3D_BackBufferCapture::GetCaptureTexture());
		ASURA_D3D_OK( xResult);
	}


	{
		D3DXMATRIX xMatrix0;
		D3DXMATRIX xMatrixTemp;
		
		D3DXMatrixIdentity( &xMatrix0);
		
		D3DXMatrixScaling( &xMatrixTemp, 0.5f, 0.5f, 1.0f);
		D3DXMatrixMultiply( &xMatrix0, &xMatrix0, &xMatrixTemp);
		
		D3DXMatrixTranslation( &xMatrixTemp, +0.5f, +0.5f, 0.0f);
		D3DXMatrixMultiply( &xMatrix0, &xMatrix0, &xMatrixTemp);
		
		D3DXMatrixTranslation( &xMatrixTemp, 0.5f / static_cast<float>(Asura_Direct3D::GetPresentParameters().BackBufferWidth), 0.5f / static_cast<float>(Asura_Direct3D::GetPresentParameters().BackBufferHeight), 0.0f);
		D3DXMatrixMultiply( &xMatrix0, &xMatrix0, &xMatrixTemp);
		
		D3DXMATRIX xMatrix1;
		
		Asura_FSFX_Component_RadialBlur_UploadVSConstant( 0, xMatrix0);
		
		const float fSize = this->m_fSize / 4.0f;
		const float fCenter = this->m_fCentreX / 100.0f;
		
		{
			const float fScaleX = 1.0f - ((1.0f * fSize) / fBlurBufferHeight);
			const float fTranslateX = (1.0f - fScaleX) * fCenter;
			D3DXMatrixTranslation( &xMatrixTemp, fTranslateX, 0.0f, 1.0f);
			D3DXMatrixMultiply( &xMatrix1, &xMatrixTemp, &xMatrix0);
			
			D3DXMatrixScaling( &xMatrixTemp, fScaleX, 1.0f, 1.0f);
			D3DXMatrixMultiply( &xMatrix1, &xMatrixTemp, &xMatrix1);
			
			Asura_FSFX_Component_RadialBlur_UploadVSConstant( 1, xMatrix1);
		}
		
		{
			const float fScaleX = 1.0f - ((3.0f * fSize) / fBlurBufferHeight);
			const float fTranslateX = (1.0f - fScaleX) * fCenter;
			D3DXMatrixTranslation( &xMatrixTemp, fTranslateX, 0.0f, 1.0f);
			D3DXMatrixMultiply( &xMatrix1, &xMatrixTemp, &xMatrix0);
			
			D3DXMatrixScaling( &xMatrixTemp, fScaleX, 1.0f, 1.0f);
			D3DXMatrixMultiply( &xMatrix1, &xMatrixTemp, &xMatrix1);
			
			Asura_FSFX_Component_RadialBlur_UploadVSConstant( 2, xMatrix1);
		}
		
		{
			const float fScaleX = 1.0f - ((5.0f * fSize) / fBlurBufferHeight);
			const float fTranslateX = (1.0f - fScaleX) * fCenter;
			D3DXMatrixTranslation( &xMatrixTemp, fTranslateX, 0.0f, 1.0f);
			D3DXMatrixMultiply( &xMatrix1, &xMatrixTemp, &xMatrix0);
			
			D3DXMatrixScaling( &xMatrixTemp, fScaleX, 1.0f, 1.0f);
			D3DXMatrixMultiply( &xMatrix1, &xMatrixTemp, &xMatrix1);
			
			Asura_FSFX_Component_RadialBlur_UploadVSConstant( 3, xMatrix1);
		}
	}
//	Asura_Direct3D::D3DDevice->SetVertexShaderConstantF(0, &afScaledHBlurOffsets[0][0], 4);

	const uint32 uDIPVertexCount = 9;
	const uint32 uDIPPrimitiveCount = 8;
	
//	axQUAD_VERTEX_DATA[0].m_fZ = axQUAD_VERTEX_DATA[1].m_fZ = axQUAD_VERTEX_DATA[2].m_fZ = axQUAD_VERTEX_DATA[3].m_fZ = 0.0f;

	xResult = Asura_Direct3D::D3DDevice->DrawIndexedPrimitiveUP( D3DPT_TRIANGLELIST, 0, uDIPVertexCount, uDIPPrimitiveCount, auQUAD_INDEDX_DATA, D3DFMT_INDEX16, axQUAD_VERTEX_DATA, sizeof(SimpleVertex));
	ASURA_D3D_OK( xResult);

////// Vertical blur passes

	// Restore the render target
	xResult = Asura_Direct3D::D3DDevice->SetRenderTarget(0, pxOldRenderTarget);
	ASURA_D3D_OK( xResult);
	pxOldRenderTarget->Release();
	xResult = Asura_Direct3D::D3DDevice->SetDepthStencilSurface(pxOldDepthStencilSurface);
	ASURA_D3D_OK( xResult);

	// set our blur buffer as the textures
	for(u_int u = 0; u < 4; ++u)
	{
		xResult = Asura_Direct3D::D3DDevice->SetTexture(u, pxBlurBuffer->GetTexture());
		ASURA_D3D_OK( xResult);
	}

	{
		D3DXMATRIX xMatrix0;
		D3DXMATRIX xMatrixTemp;
		
		D3DXMatrixIdentity( &xMatrix0);
		
		//xMatrix0.m[1][2] = 1.0f;
		//xMatrix0.m[2][2] = 0.0f;
		
		D3DXMatrixScaling( &xMatrixTemp, 0.5f, 0.5f, 1.0f);
		D3DXMatrixMultiply( &xMatrix0, &xMatrix0, &xMatrixTemp);
		
		D3DXMatrixTranslation( &xMatrixTemp, +0.5f, +0.5f, 0.0f);
		D3DXMatrixMultiply( &xMatrix0, &xMatrix0, &xMatrixTemp);
		
		D3DXMatrixTranslation( &xMatrixTemp, 0.5f / fBlurBufferWidth, 0.5f / fBlurBufferHeight, 0.0f);
		D3DXMatrixMultiply( &xMatrix0, &xMatrix0, &xMatrixTemp);
		
		D3DXMATRIX xMatrix1;
		
		Asura_FSFX_Component_RadialBlur_UploadVSConstant( 0, xMatrix0);
		
		const float fSize = this->m_fSize / 4.0f;
		const float fCenter = this->m_fCentreY / 100.0f;
		
		{
			const float fScaleY = 1.0f - ((1.0f * fSize) / fBlurBufferHeight);
			const float fTranslateY = (1.0f - fScaleY) * fCenter;
			D3DXMatrixTranslation( &xMatrixTemp, 0.0f, fTranslateY, 1.0f);
			D3DXMatrixMultiply( &xMatrix1, &xMatrixTemp, &xMatrix0);
			
			D3DXMatrixScaling( &xMatrixTemp, 1.0f, fScaleY, 1.0f);
			D3DXMatrixMultiply( &xMatrix1, &xMatrixTemp, &xMatrix1);
			
			Asura_FSFX_Component_RadialBlur_UploadVSConstant( 1, xMatrix1);
		}
		
		{
			const float fScaleY = 1.0f - ((3.0f * fSize) / fBlurBufferHeight);
			const float fTranslateY = (1.0f - fScaleY) * fCenter;
			D3DXMatrixTranslation( &xMatrixTemp, 0.0f, fTranslateY, 1.0f);
			D3DXMatrixMultiply( &xMatrix1, &xMatrixTemp, &xMatrix0);
			
			D3DXMatrixScaling( &xMatrixTemp, 1.0f, fScaleY, 1.0f);
			D3DXMatrixMultiply( &xMatrix1, &xMatrixTemp, &xMatrix1);
			
			Asura_FSFX_Component_RadialBlur_UploadVSConstant( 2, xMatrix1);
		}
		
		{
			const float fScaleY = 1.0f - ((5.0f * fSize) / fBlurBufferHeight);
			const float fTranslateY = (1.0f - fScaleY) * fCenter;
			D3DXMatrixTranslation( &xMatrixTemp, 0.0f, fTranslateY, 1.0f);
			D3DXMatrixMultiply( &xMatrix1, &xMatrixTemp, &xMatrix0);
			
			D3DXMatrixScaling( &xMatrixTemp, 1.0f, fScaleY, 1.0f);
			D3DXMatrixMultiply( &xMatrix1, &xMatrixTemp, &xMatrix1);
			
			Asura_FSFX_Component_RadialBlur_UploadVSConstant( 3, xMatrix1);
		}
	}
	
	//Asura_Direct3D::D3DDevice->SetVertexShaderConstantF(0, &afScaledVBlurOffsets[0][0], 4);

//	Asura_Direct3D::D3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, axQUAD_VERTEX_DATA, sizeof(SimpleVertex));
	xResult = Asura_Direct3D::D3DDevice->DrawIndexedPrimitiveUP( D3DPT_TRIANGLELIST, 0, uDIPVertexCount, uDIPPrimitiveCount, auQUAD_INDEDX_DATA, D3DFMT_INDEX16, axQUAD_VERTEX_DATA, sizeof(SimpleVertex));
	ASURA_D3D_OK( xResult);

////// Finished

	// clean up
	for(u_int u = 0; u < 4; ++u)
	{
		xResult = Asura_Direct3D::D3DDevice->SetTexture(u, pxOldTexture[u]);
		ASURA_D3D_OK( xResult);
		if(pxOldTexture[u]) pxOldTexture[u]->Release();

		xResult = Asura_Direct3D::D3DDevice->SetSamplerState(u, D3DSAMP_ADDRESSU, uOldAddressU[u]);
		ASURA_D3D_OK( xResult);
		xResult = Asura_Direct3D::D3DDevice->SetSamplerState(u, D3DSAMP_ADDRESSV, uOldAddressV[u]);
		ASURA_D3D_OK( xResult);
	}
	
	Asura_Direct3D_Shaders::RequestPixelShader(ASURA_D3D_PS_NONE);
	Asura_Render::CurrentStates.RequestZBufferCompareMode(ASURA_ZBUFFERCMP_LESSEQUAL);
	
	Asura_Render::CurrentStates.RequestZBufferEnabled(true);
	//Asura_Render::CurrentStates.RequestZBufferWriteEnabled(false);

	SAFE_RELEASE( pxOldDepthStencilSurface);

	Asura_Direct3D_BackBufferCapture::AdviseNextFrame();
	Asura_Render::ApplyCameraSettings();

	#if defined ASURA_TARGET_SM3
		const bool bSRGB = Asura_Direct3D::UseGammaCorrection();
		Asura_Direct3D::D3DDevice->SetRenderState( D3DRS_SRGBWRITEENABLE, bSRGB );
	#endif
}


//*******************************************************************
//
// (c) 2006 Rebellion (Derby)
//
// Warning: This computer program is protected by copyright law and 
// international treaties. Unauthorized reproduction or distribution
// of this program, or any portion of it, may result in severe civil and
// criminal penalties, and will be prosecuted to the maximum extent 
// possible under the law.
//
//*******************************************************************

#endif //defined( ASURA_USE_FSFX)
