//////////////////////////////////////////////////////////////////////////////
//
// Asura_Direct3D_AmbientOcclusion.cpp
//
// Copyright (c) 2009 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

#include "Rendering/Asura_Xbox360_EdgeAntiAliasing.h"


#ifdef ASURA_EDGE_ANTIALIASING

// Independent

#include "Animation/Asura_Animation.h"
#include "Core/Asura_Timers.h"
#include "Debugging/Asura_Platform_GPUProfile.h"
#include "Interface/Asura_CommandConsole.h"
#include "Rendering/Asura_Viewport.h"

// Asura Xbox 360
#include "Rendering/Asura_Xbox360_Effect_System.h"
#include "Rendering/Asura_Xbox360_IndexBuffer.h"
#include "Rendering/Asura_Xbox360_MaterialResponse_System.h"
#include "Rendering/Asura_Xbox360_TextureManagement.h"
#include "Rendering/Asura_Xbox360_VertexBuffer.h"
#include "Rendering/Asura_Xbox360_Fog.h"
#include "Rendering/Asura_Xbox360_Graphics.h"
#include "Rendering/Asura_Xbox360_EnvironmentRenderer.h"
#include "Rendering/Asura_Xbox360_SkinRenderer.h"
#include "Rendering/Asura_Render.h"

// Shader Include Files
#include "EdgeAntiAliasing.h"

// Misc
#include <fxl.h>
#include <xtl.h>


//#define USE_POSTPROCESS_AA_MODE		1

// VARIABLES

Asura_Xbox360_EdgeAntiAliasing::AntiAliasEntity	Asura_Xbox360_EdgeAntiAliasing::s_axAntiAliasEntities[MAX_ANTIALIAS_ENTITIES];
u_int											Asura_Xbox360_EdgeAntiAliasing::s_uNumAntiAliasEntities = 0;
int												Asura_Xbox360_EdgeAntiAliasing::s_iDebugMode = Asura_Xbox360_EdgeAntiAliasing::AA_ENABLED;


static Asura_Xbox360_Effect*		g_pxEffect			= NULL;

static const char* g_aszDebugModeName[] =   //corresponding to AA_DEBUG_MODE
{
	"Disabled",
	"Enabled",
	"Debug",
};

static u_int g_uNumEntitiesDrawn	= 0;
static u_int g_uNumEdgesDrawn		= 0;

static float g_fDepthOffset			= -0.0006;	// good compromise between jaggies on very shallow edges (fixed by bigger numbers) & zipper artefact on overlapping edges on bevelled corners (often can be avoided using smaller numbers).

//doesn't work on 360, will probably never work
//static float g_fLineThickness		= 0.9f; 	// set this to slightly less than 1-pixel => only touch the worst pixels. This improves render performance (less pixels to shade) and reduces artefacts.

#if USE_POSTPROCESS_AA_MODE
static Direct3D_Texture*			g_pxBlurMap				= NULL;
static Direct3D_Surface*			g_pxBlurMapRT			= NULL;
#endif


Asura_Xbox360_EdgeAntiAliasing::AntiAliasSkinEntity	Asura_Xbox360_EdgeAntiAliasing::s_axAntiAliasSkinEntities[MAX_ANTIALIAS_ENTITIES];
u_int Asura_Xbox360_EdgeAntiAliasing::s_uNumAntiAliasSkinEntities	= 0;
bool Asura_Xbox360_EdgeAntiAliasing::s_bCollectingSkinEntities		= false;

static Asura_Xbox360_Effect_System::Handle		g_fDepthOffsetHandle	= NULL;
static Asura_Xbox360_Effect_System::Handle		g_xConstantsHandle		= NULL;
static Asura_Xbox360_Effect_System::Handle		g_xWorldViewProjHandle	= NULL;
static Asura_Xbox360_Effect_System::Handle		g_xInputSamplerHandle	= NULL;

// FUNCTIONS


//////////////////////////////////////////////////////////////////////////
static int g_uReloadShaderTickCount = 0;

static bool ReloadShader( Asura_Xbox360_Effect*& pxEffectRef )
{
	g_uReloadShaderTickCount++;
	if( g_uReloadShaderTickCount % 5 == 0 )
	{
		const char* szFileName = "devkit:\\_ShaderReloader\\EdgeAntiAliasing.bin";

		HANDLE xFile = ::CreateFile( szFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
		if( xFile != INVALID_HANDLE_VALUE )
		{
			Asura_Xbox360_Effect* pxReloadedEff = NULL;
			if( g_uReloadShaderTickCount != 0 )				// ignore first file if it exists
			{
				u_int uSize = ::GetFileSize( xFile, NULL );
				char * pBuffer = new char[uSize];

				u_int uBytesRead = 0;
				::ReadFile( xFile, pBuffer, uSize, (LPDWORD)&uBytesRead, NULL );

				pxReloadedEff = Asura_Xbox360_Effect_System::CreateEffectFromMemory( pBuffer );
				delete[] pBuffer;
			}
			::CloseHandle( xFile );
			::DeleteFile( szFileName );

			if( pxReloadedEff != NULL )
			{
				if( pxEffectRef != NULL )
				{
					Asura_Xbox360_Effect_System::Destroy( pxEffectRef );
				}
				pxEffectRef = pxReloadedEff;
				return true;
			}
			return false;
		}
	}
	return false;
}
//////////////////////////////////////////////////////////////////////////

static void InitialiseEffectVariableHandles()
{
	Asura_Assert( g_pxEffect != NULL, "The SSAO effect is not initialised!" );
	if( g_pxEffect == NULL )
		return;

	g_fDepthOffsetHandle	= g_pxEffect->GetParameterHandle("g_fDepthOffset");
	g_xConstantsHandle		= g_pxEffect->GetParameterHandle("g_xConstants");
	g_xWorldViewProjHandle	= g_pxEffect->GetParameterHandle("g_xWorldViewProj");
	g_xInputSamplerHandle	= g_pxEffect->GetParameterHandle("g_xInputSampler");
}

void Asura_Xbox360_EdgeAntiAliasing::Initialise( void )
{
#ifdef ASURA_SUPPORT_COMMAND_CONSOLE
	Asura_CommandConsole::AddCmd(L"Render.AntiAlias.EdgeAntiAliasing", SwitchAAModes, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
	Asura_CommandConsole::AddVar(L"Render.AntiAlias.DepthOffset", &g_fDepthOffset, -FLT_MAX, FLT_MAX, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
//	Asura_CommandConsole::AddVar(L"Render.AntiAlias.LineThickness", &g_fLineThickness, 0, 63.875f, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
#endif


#ifdef USE_POSTPROCESS_AA_MODE
	Asura_Xbox360_Graphics::GetDevice()->CreateTexture( Asura_Xbox360_Graphics::GetScreenWidth(), Asura_Xbox360_Graphics::GetScreenHeight(),
			1, 0, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &g_pxBlurMap, NULL );

	// Create render target used for downsampling and blurring depths
	const D3DSURFACE_PARAMETERS& xEAASurfaceParams =
		Asura_Xbox360_Graphics::GetSurfaceParams( Asura_Xbox360_Graphics::SP_EAA_BLURMAP );

	Asura_Xbox360_Graphics::GetDevice()->CreateRenderTarget( Asura_Xbox360_Graphics::GetScreenWidth() / 2, Asura_Xbox360_Graphics::GetScreenHeight() / 2,
		D3DFMT_A8R8G8B8, D3DMULTISAMPLE_NONE, 0, FALSE, &g_pxBlurMapRT, &xEAASurfaceParams );
#endif

	g_pxEffect = Asura_Xbox360_Effect_System::CreateEffectFromMemory( g_auEdgeAntiAliasing );
	InitialiseEffectVariableHandles();
}

void Asura_Xbox360_EdgeAntiAliasing::Shutdown( void )
{
	if( g_pxEffect != NULL )
	{
		Asura_Xbox360_Effect_System::Destroy( g_pxEffect );
		g_pxEffect = NULL;
	}
}

void Asura_Xbox360_EdgeAntiAliasing::BeginFrame( void )
{
	s_uNumAntiAliasEntities = 0;
}

void Asura_Xbox360_EdgeAntiAliasing::EndFrame( void )
{
}

/*
// Helper class used to store/restore various dx states
class Asura_Xbox360_StoreRestoreDxSettings
{
public:
	Asura_Xbox360_StoreRestoreDxSettings()
	{
		Asura_Xbox360_Graphics::GetDevice()->GetRenderTarget( 0, &m_pxOldRT );
		Asura_Xbox360_Graphics::GetDevice()->GetDepthStencilSurface( &m_pxOldDepthStencil );
		for( u_int u = 0; u < 3; ++u )
		{
			Asura_Xbox360_Graphics::GetDevice()->GetSamplerState( u, D3DSAMP_ADDRESSU, &m_auAddressU[u] );
			Asura_Xbox360_Graphics::GetDevice()->GetSamplerState( u, D3DSAMP_ADDRESSV, &m_auAddressV[u] );
		}
	}

	~Asura_Xbox360_StoreRestoreDxSettings()
	{
		Asura_Xbox360_Graphics::GetDevice()->SetRenderTarget( 0, m_pxOldRT );
		Asura_Xbox360_Graphics::GetDevice()->SetDepthStencilSurface( m_pxOldDepthStencil );
		SAFE_RELEASE( m_pxOldRT );
		SAFE_RELEASE( m_pxOldDepthStencil );
		for( u_int u = 0; u < 3; ++u )
		{
			Asura_Xbox360_Graphics::GetDevice()->SetSamplerState( u, D3DSAMP_ADDRESSU, m_auAddressU[u] );
			Asura_Xbox360_Graphics::GetDevice()->SetSamplerState( u, D3DSAMP_ADDRESSV, m_auAddressV[u] );
		}
	}

private:
	Direct3D_Surface*	m_pxOldRT;
	Direct3D_Surface*	m_pxOldDepthStencil;
	DWORD				m_auAddressU[3], m_auAddressV[3];

};
*/

void	Asura_Xbox360_EdgeAntiAliasing::RenderEnvironment()
{
	if ( s_iDebugMode == AA_DEBUG )
	{
		//flip to a different shader & render additively (so can see overdraw)
		Asura_Render::CurrentStates.RequestTranslucencyMode( ASURA_TRANSLUCENCY_OFF );
		Asura_Render::CurrentStates.RequestTranslucencyMode( ASURA_TRANSLUCENCY_GLOWING );
		g_pxEffect->Begin(0, 1);
	}
	else
	{
		g_pxEffect->Begin();
	}

	for ( u_int uEntity=0; uEntity < s_uNumAntiAliasEntities; uEntity++ )
	{
		AntiAliasEntity& xEnt = s_axAntiAliasEntities[uEntity];

		D3DVERTEXELEMENT9 el[32];
		UINT elNum = 32;
		xEnt.m_pxVertexDecl->GetDeclaration(el, &elNum);
		Asura_Xbox360_Graphics::GetDevice()->SetVertexDeclaration( xEnt.m_pxVertexDecl );
		g_pxEffect->SetMatrix( g_xWorldViewProjHandle, xEnt.m_xWorldViewProj);

		xEnt.m_pxVertexBuffer->Bind();
		xEnt.m_pxIndexBuffer->Bind();

		for (int iTiling = 0; iTiling < Asura_Xbox360_Graphics::GetNumberOfPredicatedTilePasses(); ++iTiling)
		{
			if (Asura_Xbox360_Graphics::IsTiling2())
			{
				Asura_Xbox360_Graphics::GetDevice()->SetPredication(D3DPRED_TILE(iTiling));
				Asura_Xbox360_Graphics::CorrectVPOSForTiling(iTiling);
			}
			g_pxEffect->Commit();
			Asura_Xbox360_Graphics::GetDevice()->DrawIndexedVertices( D3DPT_LINELIST, 0, xEnt.m_iStartOffset, xEnt.m_iNumVerts );
		}

		g_uNumEdgesDrawn += xEnt.m_iNumVerts/2;
	}

	if (Asura_Xbox360_Graphics::IsTiling2())
	{
		Asura_Xbox360_Graphics::GetDevice()->SetPredication(0);
	}
	Asura_Xbox360_Graphics::CorrectVPOSForTiling(0);

	g_pxEffect->End();
}

void	Asura_Xbox360_EdgeAntiAliasing::RenderSkins()
{
	
}

//
// Draw over the edges with an anti-aliasing blur.
//
// The blur works by mimicking the rounding that originally created the jaggies, then sampling adjacent pixels to reconstruct the colour according to the
// true coverage of that pixel. e.g. if we're antialiasing the edge of an object in front of the sky, when we draw a line over the edge of that object half
// the pixels of the line will be on pixels of the object, and half will be on sky pixels. All of these pixels need to be anti-aliased to some extent, and
// we achieve this by sampling pixels either side of the line. So the object pixels gain a bit of sky colour, and the sky ones gain a bit of object colour.
// Since this is technically a directional blur of 1-pixel, some artefacts are to be expected on high-frequency patterns - they will appear to stretch by
// 1-pixel under the anti-aliasing.
//
void Asura_Xbox360_EdgeAntiAliasing::Render( void )
{
	g_uNumEntitiesDrawn = s_uNumAntiAliasEntities;
	g_uNumEdgesDrawn = 0;

	if ( s_uNumAntiAliasEntities == 0 || s_iDebugMode == AA_DISABLED )
	{
		s_uNumAntiAliasSkinEntities = 0;
		return;
	}

	SCOPEDPIXEVENT((0xff0080ff, "Render Edge Anti-Aliasing"));

	if( ReloadShader(g_pxEffect) )
		InitialiseEffectVariableHandles();

	Asura_Xbox360_Graphics::Resolve( Asura_Xbox360_Graphics::GetSceneResolveTexture() );

	{
		// Clear the dest alpha to 0 first
		float fWidth =  (float)Asura_Viewport::GetWidth();		
		float fHeight = (float)Asura_Viewport::GetHeight();			
		Asura_Overlay_Quad xQuad;
		xQuad.SetRectXY(0,0, fWidth, fHeight);
		xQuad.Colour = 0x00ffffff;
		xQuad.TextureIndex = ASURA_TEXTURE_NONE;
		xQuad.TranslucencyMode = ASURA_TRANSLUCENCY_OFF;	
		Asura_Render::CurrentStates.RequestAlphaTest( false , 0x00);
		Asura_Render::CurrentStates.RequestStencilEnabled( false);
		Asura_Xbox360_Graphics::GetDevice()->SetRenderState(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_ALPHA);
		xQuad.Render(false);
		Asura_Render::FlushDisplay();
		Asura_Xbox360_Graphics::GetDevice()->SetRenderState(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_RED|D3DCOLORWRITEENABLE_GREEN|D3DCOLORWRITEENABLE_BLUE|D3DCOLORWRITEENABLE_ALPHA);
	}

	g_pxEffect->SetTexture( g_xInputSamplerHandle, Asura_Xbox360_Graphics::GetSceneResolveTexture() );

	//Asura_Xbox360_StoreRestoreDxSettings srrt;
	//Asura_Xbox360_Graphics::GetDevice()->SetRenderTarget( 1, NULL );
	//Asura_Xbox360_Graphics::GetDevice()->SetRenderTarget( 2, NULL );
	//Asura_Xbox360_Graphics::GetDevice()->SetRenderTarget( 3, NULL );

	Asura_Render::CurrentStates.RequestAlphaTest( false );
	Asura_Render::CurrentStates.RequestCullMode( ASURA_CULLMODE_CW );
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled( false );
	Asura_Render::CurrentStates.RequestDynamicLights( false );

	// Force z-buffer state to be changed
	Asura_Render::CurrentStates.RequestZBufferEnabled( false );
	Asura_Render::CurrentStates.RequestZBufferEnabled( true );

	Asura_Render::CurrentStates.RequestZBufferCompareMode( ASURA_ZBUFFERCMP_LESSEQUAL );

	// Set the special additive transparency that removes/reduces problems with line overlap
	{
		D3DBLENDSTATE xBlendState;
		xBlendState.BlendOp = xBlendState.BlendOpAlpha = D3DBLENDOP_ADD;
		xBlendState.SrcBlend = D3DBLEND_INVDESTALPHA;
		xBlendState.DestBlend = D3DBLEND_DESTALPHA;

		xBlendState.SrcBlendAlpha = D3DBLEND_ONE;
		xBlendState.DestBlendAlpha = D3DBLEND_SRCALPHA;

		Asura_Xbox360_Graphics::GetDevice()->SetBlendState( 0, xBlendState );
	}

	g_pxEffect->SetFloat( g_fDepthOffsetHandle, g_fDepthOffset );

	FXLVECTOR xVec;
	xVec.x = (float)Asura_Viewport::GetWidth();
	xVec.y = (float)Asura_Viewport::GetHeight();
	xVec.z = 1.0f / (float)Asura_Viewport::GetWidth();
	xVec.w = 1.0f / (float)Asura_Viewport::GetHeight();
	g_pxEffect->SetVector( g_xConstantsHandle, xVec );

	RenderEnvironment();
	RenderSkins();

	// since we've bypassed Asura_Render::CurrentStates.RequestTranslucencyMode system above, reset it to prevent
	// problems afterwards
	Asura_Render::CurrentStates.RequestTranslucencyMode( ASURA_TRANSLUCENCY_NORMAL );
	Asura_Render::CurrentStates.RequestTranslucencyMode( ASURA_TRANSLUCENCY_OFF );

	s_uNumAntiAliasSkinEntities = 0;
}

#ifdef ASURA_SUPPORT_COMMAND_CONSOLE
bool Asura_Xbox360_EdgeAntiAliasing::SwitchAAModes ( bool bInc, bool bInterrogate, bool bHelp )
{
	
	if ( bHelp || bInterrogate )
	{
		Asura_CommandConsole::Printf( "%s\nentities: %d\nedges: %d", g_aszDebugModeName[s_iDebugMode], g_uNumEntitiesDrawn, g_uNumEdgesDrawn );
		return true;
	}

	s_iDebugMode += bInc?1:-1;
	if ( s_iDebugMode < 0 ) s_iDebugMode = NUM_AA_DEBUG_MODES-1;
	if ( s_iDebugMode >= NUM_AA_DEBUG_MODES ) s_iDebugMode = 0;

	return true;
}
#endif





#endif //#ifdef ASURA_EDGE_ANTIALIASING
