//*******************************************************************
//
// File: Asura_PlatformFSFX_DepthOfField.cpp 
//
// Created: 22 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 "FSFX/Asura_Direct3D_FSFX.h"

#include "Rendering/Asura_ViewPort.h"
#include "Rendering/Asura_Render.h"

// other libraries
#include "Rendering/Asura_Direct3D_BackBufferCapture.h"

// internal dependencies
#include "Rendering/Asura_Direct3D_Caps.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"

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


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



//*******************************************************************
// Static data
//*******************************************************************
// DLH - 18 July 2006 11:22:42 - I would have made this a member of the class so in-game it is set to the required size once (in-game at least) and left
// DLH - 18 July 2006 11:24:58 - But the person that designed the FSFX system did not allow for platform data or platform initalisations. 
// DLH - 18 July 2006 11:25:21 - So the array is resized if ever more coeffs are needed
// MGH 22/1/2008: The code is now clearing these arrays between levels to avoid memory fragmentation.
static Asura_HeapArray< Asura_Direct3D_FSFX_BlurKernalCoeff> g_xArrayKernalCoeffs;

static Asura_HeapArray< Asura_Direct3D_FSFX_Blur_Vertex> g_xArrayPolygonVertices;
static Asura_HeapArray< uint16> g_xArrayPolygonIndices;


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

// MGH 21/1/2008: Clear arrays between levels to prevent memory fragmentation.
void Asura_FSFX_Component_DepthOfField_Platform_Reset()
{
	g_xArrayKernalCoeffs.Clear();
	g_xArrayPolygonVertices.Clear();
	g_xArrayPolygonIndices.Clear();
}

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

	Asura_Direct3D_RenderTarget* pxBlurBuffer = Asura_Direct3D_RenderTargetManager::FindUnusedRenderTarget( 1.0f, 1.0f, Asura_Direct3D_UserOptions::GetRenderTargetBufferFormat(), D3DFMT_UNKNOWN, true);
	if( NULL == pxBlurBuffer)
	{
		return;
	}

	SCOPEDPIXEVENT((0xff00ff00, L"FSFX DOF"));

	do
	{
		const u_int uViewWidth = Asura_Direct3D::GetPresentParameters().BackBufferWidth;
		const u_int uViewHeight = Asura_Direct3D::GetPresentParameters().BackBufferHeight;
		const u_int uBufferWidth = pxBlurBuffer->GetWidth();
		const u_int uBufferHeight = pxBlurBuffer->GetHeight();
		const float fViewWidth = static_cast< float> ( uViewWidth);
		const float fViewHeight = static_cast< float> ( uViewHeight);
		const float fBufferWidth = static_cast< float> ( uBufferWidth);
		const float fBufferHeight = static_cast< float> ( uBufferHeight);
		
		// DLH - 18 July 2006 11:07:55 - Calculate the Scale to go from PS2's 512 wide screen to the present PC resolution 
		const float fResolutionScale = fBufferWidth / static_cast< float>( ASURA_FSFX_BLUR_PS2_REFERENCE_SCREEN_WIDTH);
		const float fBlurRadius = fResolutionScale * (0.5f * this->m_fSize) * (this->m_fStrength / 100.0f) * fIntensity;
		
		u_int uUsedKernalWidth = 0;
		
		const u_int uKernalCoeffsPerPolygon = 4;
		if( false == Asura_Direct3D_FSFX::Component_GaussianBlur_CalcCoeffs( fBlurRadius, g_xArrayKernalCoeffs, uKernalCoeffsPerPolygon, uUsedKernalWidth))
		{
			break;
		}
		
		if( uUsedKernalWidth <= 1)
		{
			break;
		}
				
		Asura_Direct3D_FSFX_Blur_Info xBlurInfo;
		Asura_Direct3D_FSFX::Component_Setup( uUsedKernalWidth, uKernalCoeffsPerPolygon, xBlurInfo, g_xArrayPolygonVertices, g_xArrayPolygonIndices);
		
		Asura_Direct3D_FSFX::Component_Setup_Horizontal( fViewWidth, fViewHeight, xBlurInfo, g_xArrayKernalCoeffs, g_xArrayPolygonVertices);
		
		const u_int uDIPPrimitives = 2 * xBlurInfo.uKernalPolygons;
		
		HRESULT xResult;
		
		// set up some states that persist across the effect
		Asura_Render::CurrentStates.RequestTextureOpMode(ASURA_TEXTUREOP_OPAQUE);
		if( 1 < xBlurInfo.uKernalPolygons)
		{
			Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_ADD);
		}
		else
		{
			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->SetSamplerState(u, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
			ASURA_D3D_OK( xResult);
			xResult = Asura_Direct3D::D3DDevice->SetSamplerState(u, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
			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* pxOldRenderTargetBuffer = NULL;
//		Direct3D_Surface* pxOldRenderTargetDepth = NULL;
		xResult = Asura_Direct3D::D3DDevice->GetRenderTarget(0, &pxOldRenderTargetBuffer);
		ASURA_D3D_OK( xResult);
//		xResult = Asura_Direct3D::D3DDevice->GetDepthStencilSurface(&pxOldRenderTargetDepth);
//		ASURA_D3D_OK( xResult);

		#if defined ASURA_TARGET_SM3
			// Might as well ignore sRGB for this
			Asura_Direct3D::D3DDevice->SetRenderState( D3DRS_SRGBWRITEENABLE, FALSE );
		#endif

		// set up the blur shaders
		Asura_Direct3D_Shaders::RequestVertexShader(ASURA_D3D_VS_FSFX_BLUR);
		Asura_Direct3D_Shaders::RequestPixelShader(ASURA_D3D_PS_FSFX_BLUR);

	////// Horizontal blur pass

		// set our blur buffer as the target
		xResult = Asura_Direct3D::D3DDevice->SetRenderTarget(0, pxBlurBuffer->GetSurface());
		ASURA_D3D_OK( xResult);
		Asura_Direct3D::ClearRenderTargets( 1);
		
//		xResult = Asura_Direct3D::D3DDevice->SetDepthStencilSurface( NULL);
//		ASURA_D3D_OK( xResult);

		// DLH - 17 July 2006 17:04:20 - Only use the area we want
		{
			D3DVIEWPORT9 xViewport;
			
			xViewport.Width = uViewWidth;
			xViewport.Height = uViewHeight;
			xViewport.X = 0;
			xViewport.Y = 0;
			xViewport.MinZ = 0.0f;
			xViewport.MaxZ = 1.0f;
			xResult = Asura_Direct3D::D3DDevice->SetViewport( &xViewport);
			ASURA_D3D_OK( xResult);
		}
		
		{
			const u_int uClearColour = 0x00000000;
			xResult = Asura_Direct3D::D3DDevice->Clear( 0, NULL, D3DCLEAR_TARGET, uClearColour, 0.5, 0x7F);
			ASURA_D3D_OK( xResult);
		}

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

		{
			xResult = Asura_Direct3D::D3DDevice->DrawIndexedPrimitiveUP( D3DPT_TRIANGLELIST, 0, xBlurInfo.uKernalPolygonVertices, uDIPPrimitives, &(g_xArrayPolygonIndices[0]), D3DFMT_INDEX16, &(g_xArrayPolygonVertices[0]), sizeof( g_xArrayPolygonVertices[0]));
			ASURA_D3D_OK( xResult);
		}

	////// Vertical blur passes

		// Restore the render target
		xResult = Asura_Direct3D::D3DDevice->SetRenderTarget(0, pxOldRenderTargetBuffer);
		pxOldRenderTargetBuffer->Release();
		ASURA_D3D_OK( xResult);
//		if( NULL != pxOldRenderTargetDepth)
//		{
//			xResult = Asura_Direct3D::D3DDevice->SetDepthStencilSurface( pxOldRenderTargetDepth);
//			pxOldRenderTargetDepth->Release();
//			ASURA_D3D_OK( xResult);
//		}

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

		Asura_Direct3D_FSFX::Component_Setup_Vertical( fViewWidth, fViewHeight, fBufferWidth, fBufferHeight, xBlurInfo, g_xArrayKernalCoeffs, g_xArrayPolygonVertices);
		
		Asura_Render::CurrentStates.RequestZBufferEnabled(true);
		Asura_Render::CurrentStates.RequestZBufferWriteEnabled(false);

		{
			D3DXMATRIX xMatrix;
			Asura_Direct3D::D3DDevice->GetTransform(D3DTS_PROJECTION, &xMatrix);

			if( 0.0f < this->m_fNearBlurZ)
			{
				Asura_Render::CurrentStates.RequestZBufferCompareMode(ASURA_ZBUFFERCMP_GREATER);

				D3DXVECTOR4 xVec(0, 0, this->m_fNearBlurZ, 1);
				D3DXVec4Transform(&xVec, &xVec, &xMatrix);
				const float fZ = xVec.z / xVec.w;
				
				for( u_int uIndexVertex = 0; uIndexVertex < xBlurInfo.uKernalPolygonVertices; uIndexVertex += 1)
				{
					Asura_Direct3D_FSFX_Blur_Vertex& xVertex = g_xArrayPolygonVertices[ uIndexVertex];
					
					xVertex.m_fPositionZ = fZ;
				}
				
				Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_OFF);
				xResult = Asura_Direct3D::D3DDevice->DrawIndexedPrimitiveUP( D3DPT_TRIANGLELIST, 0, xBlurInfo.uKernalPolygonVertices, 2, &(g_xArrayPolygonIndices[0]), D3DFMT_INDEX16, &(g_xArrayPolygonVertices[0]), sizeof( g_xArrayPolygonVertices[0]));
				ASURA_D3D_OK( xResult);
				
				if( 1 < xBlurInfo.uKernalPolygons)
				{
					Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_ADD);
					xResult = Asura_Direct3D::D3DDevice->DrawIndexedPrimitiveUP( D3DPT_TRIANGLELIST, 0, xBlurInfo.uKernalPolygonVertices, uDIPPrimitives - 2, &(g_xArrayPolygonIndices[0]) + 6, D3DFMT_INDEX16, &(g_xArrayPolygonVertices[0]), sizeof( g_xArrayPolygonVertices[0]));
					ASURA_D3D_OK( xResult);
				}
			}

			if( 0.0f < this->m_fFarBlurZ)
			{
				Asura_Render::CurrentStates.RequestZBufferCompareMode(ASURA_ZBUFFERCMP_LESSEQUAL);

				D3DXVECTOR4 xVec(0, 0, this->m_fFarBlurZ, 1);
				D3DXVec4Transform(&xVec, &xVec, &xMatrix);
				const float fZ = xVec.z / xVec.w;
				
				for( u_int uIndexVertex = 0; uIndexVertex < xBlurInfo.uKernalPolygonVertices; uIndexVertex += 1)
				{
					Asura_Direct3D_FSFX_Blur_Vertex& xVertex = g_xArrayPolygonVertices[ uIndexVertex];
					
					xVertex.m_fPositionZ = fZ;
				}
				
				Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_OFF);
				xResult = Asura_Direct3D::D3DDevice->DrawIndexedPrimitiveUP( D3DPT_TRIANGLELIST, 0, xBlurInfo.uKernalPolygonVertices, 2, &(g_xArrayPolygonIndices[0]), D3DFMT_INDEX16, &(g_xArrayPolygonVertices[0]), sizeof( g_xArrayPolygonVertices[0]));
				ASURA_D3D_OK( xResult);
				
				if( 1 < xBlurInfo.uKernalPolygons)
				{
					Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_ADD);
					xResult = Asura_Direct3D::D3DDevice->DrawIndexedPrimitiveUP( D3DPT_TRIANGLELIST, 0, xBlurInfo.uKernalPolygonVertices, uDIPPrimitives - 2, &(g_xArrayPolygonIndices[0]) + 6, D3DFMT_INDEX16, &(g_xArrayPolygonVertices[0]), sizeof( g_xArrayPolygonVertices[0]));
					ASURA_D3D_OK( xResult);
				}
			}
		}

	////// Finished

		// clean up
		Asura_Render::CurrentStates.RequestZBufferEnabled(true);
		Asura_Render::CurrentStates.RequestZBufferWriteEnabled(true);

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

			Asura_Direct3D::D3DDevice->SetSamplerState(u, D3DSAMP_ADDRESSU, uOldAddressU[u]);
			Asura_Direct3D::D3DDevice->SetSamplerState(u, D3DSAMP_ADDRESSV, uOldAddressV[u]);
		}
		Asura_Direct3D_Shaders::RequestPixelShader(ASURA_D3D_PS_NONE);
		Asura_Render::CurrentStates.RequestZBufferCompareMode(ASURA_ZBUFFERCMP_LESSEQUAL);
		
		Asura_Direct3D_BackBufferCapture::AdviseNextFrame();
	}
	while( false);
		
	if( NULL != pxBlurBuffer)
	{
		pxBlurBuffer->MarkAsUnused();
	}
	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)
