//////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Fog.cpp
//
// Copyright (c) 2006 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

/******************************************************************************/
/* Included Files                                                             */
/******************************************************************************/

#include "Core/Asura.h"

#include "Rendering/Asura_Fog.h"

// Asura Platform-independent
#include "Camera/Asura_Camera.h"
#include "Rendering/Asura_Colour.h"
#include "Rendering/Asura_Render.h"

// Asura Xbox 360
#include "Rendering/Asura_Xbox360_Effect_System.h"
#include "Rendering/Asura_Xbox360_Fog.h"
#include "Rendering/Asura_Xbox360_Graphics.h"
#include "Rendering/Asura_Xbox360_VertexBuffer.h"

#include "Debugging/Asura_Platform_GPUProfile.h"

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

// Misc
#include <xtl.h>

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

const u_int uFOG_MAX_BUFFERS	= 3;
const u_int uFOG_TEXTURE_SIZE	= 128;

/******************************************************************************/
/* Global Variables                                                           */
/******************************************************************************/

static u_int					g_uFogRenderTextureIndex	= 0;
static u_int					g_uFogUpdateTextureIndex	= 0;
static Direct3D_LineTexture*	g_apxFogTextures[uFOG_MAX_BUFFERS];

static Direct3D_LineTexture*	g_pxFogRenderTexture = NULL;
static Direct3D_LineTexture*	g_pxFogUpdateTexture = NULL;

static Asura_Xbox360_Effect_System::Handle	g_xFogTextureHandle = 0;
static Asura_Xbox360_Effect*				g_pxFogEffect = NULL;

static Asura_Xbox360_Effect_System::Handle g_xFogColourHandle = 0;
static Asura_Xbox360_Effect_System::Handle g_xFogNearPlaneHandle = 0;
static Asura_Xbox360_Effect_System::Handle g_xOneOverFogVolumeDepthHandle = 0;
static Asura_Xbox360_Effect_System::Handle g_xFarClipHandle = 0;

static bool g_bParamsUpdated = false;

/******************************************************************************/
/* Asura_Fog Static Class Functions                                           */
/******************************************************************************/

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Fog::Platform_UpdateForNewParameters()
//
// Updates platform fog data when parameters change. Called on level load
//
// Parameters:		None
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Fog::Platform_UpdateForNewParameters()
{
	Asura_Xbox360_Fog::UpdateForNewParameters();
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Fog::Platform_Render()
//
// Renders the fog as a full-screen post-processing pass
//
// Parameters:		None
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Fog::Platform_Render()
{
	Asura_Xbox360_Fog::RenderFog();
}

/******************************************************************************/
/* Asura_Xbox360_Fog Static Class Functions                                   */
/******************************************************************************/

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Xbox360_Fog::StepOntoNextTexture
// Author       : Andy Tate
// Date         : 20-Feb-2009
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Fog::StepOntoNextTexture(void)
{
	g_uFogRenderTextureIndex		= g_uFogUpdateTextureIndex;
	g_uFogUpdateTextureIndex		= (g_uFogUpdateTextureIndex + 1) % uFOG_MAX_BUFFERS;

	g_pxFogRenderTexture = g_pxFogUpdateTexture;
	g_pxFogUpdateTexture = g_apxFogTextures[g_uFogUpdateTextureIndex];
}

/// End of function 'Asura_Xbox360_Fog::StepOntoNextTexture'. ///


////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Fog::Initialise()
//
// Initialises the Xbox 360 fog system
//
// Parameters:		None
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Fog::Initialise()
{
	Direct3D_Device* const pxDevice = Asura_Xbox360_Graphics::GetDevice();

	// Set up the effect

	g_pxFogEffect = Asura_Xbox360_Effect_System::CreateEffectFromMemory(g_auFog);

	g_xFogColourHandle = g_pxFogEffect->GetParameterHandle( "g_xFogColour" );
	g_xFogNearPlaneHandle = g_pxFogEffect->GetParameterHandle( "g_fFogNearPlane" );
	g_xOneOverFogVolumeDepthHandle = g_pxFogEffect->GetParameterHandle( "g_fOneOverFogVolumeDepth" );
	g_xFarClipHandle = g_pxFogEffect->GetParameterHandle( "g_fFarClipPlane" );

	const Asura_Xbox360_Effect_System::Handle xDepthTextureHandle = g_pxFogEffect->GetParameterHandle( "g_xDepthTexture" );
	g_pxFogEffect->GetInterface()->SetSampler( xDepthTextureHandle, Asura_Xbox360_Graphics::GetDepthMtlTexture() );

	// Create the fog lookup texture (linear format to make it easier to write into)
	for(u_int u = 0; u < uFOG_MAX_BUFFERS; u++)
	{
		pxDevice->CreateLineTexture( uFOG_TEXTURE_SIZE, 1, 0, D3DFMT_LIN_A8, D3DPOOL_DEFAULT, &g_apxFogTextures[u], NULL );
		Asura_Assertf( g_apxFogTextures[u], ("Unable to create fog texture %d.", u) );
	}

	g_uFogUpdateTextureIndex	= 0;
	g_xFogTextureHandle			= g_pxFogEffect->GetParameterHandle( "g_xFogTexture" );
	StepOntoNextTexture();

	g_bParamsUpdated = true;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Fog::Shutdown()
//
// Shuts the Xbox 360 fog system down
//
// Parameters:		None
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Fog::Shutdown()
{
	Asura_Xbox360_Effect_System::Destroy( g_pxFogEffect );
	g_pxFogEffect = NULL;

	for(u_int u = 0; u < uFOG_MAX_BUFFERS; u++)
	{
		g_apxFogTextures[u]->Release();
		g_apxFogTextures[u] = NULL;
	}

}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Fog::UpdateForNewParameters()
//
// Updates platform fog data when parameters change. Called on level load
//
// Parameters:		None
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Fog::UpdateForNewParameters()
{
	g_bParamsUpdated = true;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Fog::GetRenderTexture()
//
// Retrieves a pointer to the current fog render texture, and updates the update
// texture and main effect if params have been changed
//
////////////////////////////////////////////////////////////////////////////////

Direct3D_LineTexture* Asura_Xbox360_Fog::GetRenderTexture()
{
	// In keeping with the GetFogParameters() function that was here before, lock the update texture
	// now if params have changes, and update the main effect

	if( g_bParamsUpdated )
	{
		g_bParamsUpdated = false;

		// Ensure the fog texture is not still bound
		for( u_int u = 0; u < 16; ++u )
		{
			Asura_Xbox360_Graphics::GetDevice()->SetTexture( u, NULL );
		}

		// Fill the lookup texture based on the fog function

		D3DLOCKED_RECT xLockedRect = {0};
		g_pxFogUpdateTexture->LockRect( 0, &xLockedRect, NULL, 0 );
		Asura_Assert( xLockedRect.pBits, "Failed to lock fog texture." );

		u_char* const pucTexels = static_cast< u_char* >( xLockedRect.pBits );

		float fDistance = Asura_Fog::GetFogNearPlane();

		const float fDelta = (Asura_Fog::GetFogFarPlane() - Asura_Fog::GetFogNearPlane())
			/ static_cast< float >( uFOG_TEXTURE_SIZE - 1 );

		for( u_int u = 0; u < uFOG_TEXTURE_SIZE; ++u )
		{
			const float fDensity = Asura_Fog::GetFogDensityAtDistance( fDistance );

			// Need to clamp this, as peaks in the graph can exceed 1 / drop below 0
			const float fClamped = Asura_Maths::ClampToRange( fDensity, 0.0f, 1.0f );

			pucTexels[u] = static_cast< u_char >( fClamped * 255.0f );
			fDistance += fDelta;
		}

		g_pxFogUpdateTexture->UnlockRect(0);

		const Asura_RGBA& xFogColour = Asura_Fog::GetFogColour();
		const FXLVECTOR xFogColourVec =
			{ xFogColour.m_fR, xFogColour.m_fG, xFogColour.m_fB, xFogColour.m_fA };

		g_pxFogEffect->SetVector( g_xFogColourHandle, xFogColourVec );

		g_pxFogEffect->SetFloat( g_xFogNearPlaneHandle, Asura_Fog::GetFogNearPlane() );

		g_pxFogEffect->SetFloat( g_xOneOverFogVolumeDepthHandle,
			Asura_Fog::GetOneOverFogPlaneSeparation() );
	}

	return g_pxFogRenderTexture;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Fog::RenderFog()
//
// Renders the fog pass on Xbox 360
//
// Parameters:		None
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Fog::RenderFog()
{
	ASURA_GPU_PROFILE_START(ASURA_GPUPROFILE_FOG);

	// Swap fog-textures to prevent stalls locking the texture currently being used by the GPU...
	StepOntoNextTexture();
	g_pxFogEffect->SetTexture( g_xFogTextureHandle, g_pxFogRenderTexture );

	// make sure everything has been refreshed if needed
	GetRenderTexture();

	g_pxFogEffect->SetFloat( g_xFarClipHandle, Asura_Camera::GetFarClip() );

	PIXBeginNamedEvent( D3DCOLOR_XRGB( 0, 0x80, 0x80 ), "Fog" );

	// Pixel shader will output fog colour with fog factor as alpha
	Asura_Render::CurrentStates.RequestTranslucencyMode( ASURA_TRANSLUCENCY_NORMAL );
	Asura_Render::CurrentStates.RequestStencilEnabled(false);

	Asura_Xbox360_Graphics::GetFullScreenVB().Bind();
	Asura_Xbox360_Graphics::GetDevice()->SetVertexDeclaration( Asura_Xbox360_Graphics::GetFullScreenVD() );

	// Taken these settings out of the effect, as they were screwing up the cached states
	Asura_Render::CurrentStates.RequestZBufferEnabled(false);
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(false);
	Asura_Xbox360_Graphics::GetDevice()->SetRenderState_Inline( D3DRS_HIGHPRECISIONBLENDENABLE, TRUE );

	g_pxFogEffect->Begin();

	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_pxFogEffect->Commit();
		Asura_Xbox360_Graphics::GetDevice()->DrawVertices( D3DPT_RECTLIST, 0, 3 );
	}
	if (Asura_Xbox360_Graphics::IsTiling2())
	{
		Asura_Xbox360_Graphics::GetDevice()->SetPredication(0);
	}


	g_pxFogEffect->End();

	Asura_Xbox360_Graphics::GetDevice()->SetRenderState_Inline( D3DRS_HIGHPRECISIONBLENDENABLE, FALSE );

	PIXEndNamedEvent(); // Fog

	ASURA_GPU_PROFILE_END(ASURA_GPUPROFILE_FOG);
}






// Asura_Xbox360_FogEffectParameters class
// Used for adding fogging to existing shaders, using fog.fxh. Currently used for PFX

void Asura_Xbox360_FogEffectParameters::Apply(bool bEnabled, ASURA_TRANSLUCENCY_MODE eTranslucency)
{
	if (m_pxEffect)
	{		
		if (!bEnabled)
		{
			m_pxEffect->SetBool(m_xFogEnabledHandle, bEnabled);
		}
		else
		{
			m_pxEffect->SetBool(m_xFogEnabledHandle, Asura_Fog::IsFogEnabled());

			if (Asura_Fog::IsFogEnabled())
			{				
				m_pxEffect->SetTexture(m_xTextureHandle, Asura_Xbox360_Fog::GetRenderTexture());

				const FXLVECTOR xParams = {Asura_Fog::GetFogNearPlane(),
					Asura_Fog::GetOneOverFogPlaneSeparation(), 0.0f, 0.0f};

				m_pxEffect->SetVector(m_xFogParamsHandle, xParams);

				D3DXVECTOR4 xCol;

				switch (eTranslucency)
				{
				case ASURA_TRANSLUCENCY_GLOWING:
				case ASURA_TRANSLUCENCY_INVCOLOUR:
					// Additive-blended materials fog out to black, not the fog colour
					xCol.x = xCol.y = xCol.z = 0;
					break;

				default:
					xCol.x = Asura_Fog::GetFogColour().m_fR;
					xCol.y = Asura_Fog::GetFogColour().m_fG;
					xCol.z = Asura_Fog::GetFogColour().m_fB;
					break;
				}

				m_pxEffect->SetVector(m_xColourHandle, (float*)&xCol);					
			}
		}			
	}
};



void Asura_Xbox360_FogEffectParameters::Capture(Asura_Xbox360_Effect *pxEffect)
{
	m_pxEffect = pxEffect;

	if (pxEffect)
	{
		m_xFogEnabledHandle = m_pxEffect->GetParameterHandle( "g_bFogEnabled" );
		m_xTextureHandle    = m_pxEffect->GetParameterHandle( "g_xFogTexture" );
		m_xFogParamsHandle  = m_pxEffect->GetParameterHandle( "g_xFogParams" );
		m_xColourHandle     = m_pxEffect->GetParameterHandle( "g_xFogColour" );
	}		
};
