//
// Asura_PS3_DeferredRendering.cpp
//
// Created by Catalin Arsenescu - 20 November 2007
//
// Copyright (C) 2005 Rebellion, All Rights Reserved.
//
#include "Core/Asura.h"

// This file's header.
#include "Rendering/Asura_PS3_DeferredRendering.h"

// Engine independent.
#include "Camera/Asura_Camera.h"
#include "Shadows/Asura_CSM.h"
#include "Visibility/Asura_Visibility_System.h"

// Engine platform-specific.
#include "Rendering/Asura_PS3_FrameBuffer.h"
#include "Rendering/Asura_PS3_Graphics.h"
#include "Rendering/Asura_PS3_Helper.h"
#include "Rendering/Asura_PS3_QuadOverlayRenderer.h"
#include "Rendering/Asura_PS3_RenderingStates.h"
#include "Rendering/Asura_PS3_RSXProfiling.h"
#include "Rendering/Asura_PS3_SwapChain.h"

#include <sys/process.h>
#include <sysutil/sysutil_sysparam.h>

Asura_PS3_FrameBuffer*	Asura_PS3_DeferredRendering::s_pxTempBuffer					= NULL;
Asura_PS3_FrameBuffer*	Asura_PS3_DeferredRendering::s_pxDeferredBuffer				= NULL;
Asura_PS3_FrameBuffer*	Asura_PS3_DeferredRendering::s_pxResolvedBuffer				= NULL;
Asura_PS3_FrameBuffer*	Asura_PS3_DeferredRendering::s_pxFinalBuffer				= NULL;
#if defined(ASURA_USE_CSM)
Asura_PS3_FrameBuffer*	Asura_PS3_DeferredRendering::s_pxCSMDepthBuffer				= NULL;
Asura_PS3_FrameBuffer*	Asura_PS3_DeferredRendering::s_pxCSMVarianceBuffer			= NULL;
#endif
bool					Asura_PS3_DeferredRendering::s_bAfterToneMapping			= false;
bool					Asura_PS3_DeferredRendering::s_bUseResolvedBuffer	= false;
bool					Asura_PS3_DeferredRendering::s_bUsingFrontBuffer	= false;

static bool	g_bClearNormalsBuffer		= false;
static bool	g_bClearAlbedoSpecular		= false; 
static bool g_bShowMagnifier			= false;




////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PS3_DeferredRendering::Initialise
// Author       : Catalin Arsenescu (20-November-2007)
////////////////////////////////////////////////////////////////////////////////
void Asura_PS3_DeferredRendering::Initialise( void )
{
	void* ppDepthBuffer	= NULL;
	
	//ASURA_PS3_VIDEOMEMORY_ALLOCLOCAL( 8 ); //screw up buffer alignments FTL

	// compute the dimensions we would use in HD resolutions, even if we're in standard def
	// this keeps memory usage consistent in SD
	const	float	fAspectRatio_16_9		= 16.0f / 9.0f;
	const	u_int	uAllocHDWidth			= (u_int)( (float)ASURA_PS3_INTERNAL_SCREEN_HEIGHT * fAspectRatio_16_9 );
	const	u_int	uAllocHDHeight			= ASURA_PS3_INTERNAL_SCREEN_HEIGHT;

#ifdef ASURA_PS3_USE_1080P_RESOLUTIONS
    #error frame buffer alloc size needs to be specified for 1080P
#else
    const u_int uFinalBufferAllocWidth = 1280; 
    const u_int uFinalBufferAllocHeight = 720;
#endif

	// deferred render targets
	s_pxDeferredBuffer	= new Asura_PS3_FrameBuffer( false, Asura_PS3_Graphics::GetScreenWidth(), Asura_PS3_Graphics::GetScreenHeight(), 3, ASURA_PS3_FRAMEBUFFER_RGBA16F, true, false, "Deferred Buffer", 2, true, &ppDepthBuffer, true, uAllocHDWidth, uAllocHDHeight );

	// resolve buffer, for copying to/from when distorting screen image (e.g. refraction, heat haze) and destination for tone mapping
	// after tone mapping the deferred targets are used for resolving, for FSFX.

	/*
	Place the resolve texture in MAIN RAM (not VRAM), because it makes rendering to it faster.

	It's to do with bandwidth. It is faster to write/read vram than main memory, but if we're reading a big (e.g. full-screen)
	texture at the same time as writing it's much faster to have one in vram and one in main RAM. This means any effect that is
	bandwidth bound & reads & writes full-screen textures will get faster using this system.

	The performance impact on things we render to the resolve buffer is:
	Fog, Corona, Light Shafts - *faster* - these all read the depth buffer, 
	FSFX, ResolveBackBuffer - *faster* - these involve copying a big buffer with a relatively simple shader, every call to
							  ResolveBackBuffer takes about half the time (0.92ms -> 0.50ms at 1280x720).
	Deferred Lighting - unaffected - this is heavily fragment-shader bound, so bandwidth changes have no impact.
	Tone-mapping - *SLOWER* - the final step is faster, but the first step involves sampling the whole buffer to a much smaller
					texture, i.e. it is slower because the texture it's reading from is now in main mem. This impact is only
					about 0.14ms, and is much smaller than the speed up to any of the other effects.
	PFX - Slower if PFX texture is in main memory (about 20% slower), faster if it's in VRAM (about 5% faster).
	*/
	s_pxResolvedBuffer	= new Asura_PS3_FrameBuffer( false, Asura_PS3_Graphics::GetScreenWidth(), Asura_PS3_Graphics::GetScreenHeight(), 1, ASURA_PS3_FRAMEBUFFER_RGBA16F, true, false, "Deferred Resolved Buffer", -1, true, &ppDepthBuffer, false, uAllocHDWidth, uAllocHDHeight, CELL_GCM_LOCATION_MAIN );

	s_pxTempBuffer		= new Asura_PS3_FrameBuffer( false, Asura_PS3_Graphics::GetScreenWidth() >> 1, Asura_PS3_Graphics::GetScreenHeight() >> 1, 1, ASURA_PS3_FRAMEBUFFER_RGBA, true, false, "Temp Buffer", -1, true, &ppDepthBuffer, false, uAllocHDWidth >> 1, uAllocHDHeight >> 1, CELL_GCM_LOCATION_MAIN );

	// front buffer
	s_pxFinalBuffer		= new Asura_PS3_FrameBuffer( false, Asura_PS3_Graphics::GetOutputWidth(), Asura_PS3_Graphics::GetOutputHeight(), 1, ASURA_PS3_FRAMEBUFFER_RGBA, false, true, "Final Buffer", 0, true, NULL, false, uFinalBufferAllocWidth, uFinalBufferAllocHeight );

#if defined(ASURA_USE_CSM)
	// alloc the CSM buffers
	
	// depth: 1 buffer
	s_pxCSMDepthBuffer = new Asura_PS3_FrameBuffer( true, ASURA_CSM_SIZE, ASURA_CSM_SIZE, 0, ASURA_PS3_FRAMEBUFFER_RGBA, true, false, "Cascaded Shadow Map", -1, true, NULL, false );
	
	// variance: 4 buffers arranged above each other, each is 2 FP16's, stored as 4FP16's in a half size buffer (e.g. odd numbered y positions could be read from .ba, even from .rg)
	s_pxCSMVarianceBuffer = new Asura_PS3_FrameBuffer( true, ASURA_CSM_SIZE, ASURA_CSM_SIZE*ASURA_CSM_MAX_MAPS/2, 1, ASURA_PS3_FRAMEBUFFER_RGBA16F, false, false, "Cascaded Shadow Map", -1, true, NULL, false );
#endif


#if defined( ASURA_DEBUG_VARIABLE_SYSTEM )
		Asura_CommandConsole::AddVar(L"Render.Deferred.ClearNormalsBuffer", &g_bClearNormalsBuffer, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddVar(L"Render.Deferred.ClearAlbedoSpecular", &g_bClearAlbedoSpecular, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
		Asura_CommandConsole::AddVar(L"Render.AntiAlias.ShowMagnifier", &g_bShowMagnifier, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
#endif

}

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PS3_DeferredRendering::Shutdown
// Author       : Catalin Arsenescu (20-November-2007)
////////////////////////////////////////////////////////////////////////////////
void Asura_PS3_DeferredRendering::Shutdown( void )
{
	Asura_PS3_Helper::WaitForIdleState();

	delete s_pxDeferredBuffer;
	delete s_pxResolvedBuffer;
	delete s_pxFinalBuffer;
	delete s_pxTempBuffer;

	s_pxDeferredBuffer	= NULL;
	s_pxResolvedBuffer	= NULL;
	s_pxFinalBuffer		= NULL;
	s_pxTempBuffer		= NULL;
}

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PS3_DeferredRendering::BeginFrame
// Author       : Catalin Arsenescu (20-November-2007)
////////////////////////////////////////////////////////////////////////////////
void Asura_PS3_DeferredRendering::BeginFrame( void )
{
	s_bUseResolvedBuffer	= false;
	s_bAfterToneMapping				= false;

	s_pxDeferredBuffer->Bind();
	
	// Clear our deferred buffers
	// In normal usage this is unnecessary, the sky effectively clears the front buffer after deferred rendering.
	u_int uMask = 0;
	if ( g_bClearNormalsBuffer ) uMask |= ASURA_PS3_MRT_MASK(ASURA_DEFERRED_RENDER_TARGET_NORMAL_MATERIAL);
	if ( g_bClearAlbedoSpecular ) uMask |= ASURA_PS3_MRT_MASK(ASURA_DEFERRED_RENDER_TARGET_ALBEDO_SPECULAR);
	if ( Asura_Render::GetEnableScreenClear() ) uMask |= ASURA_PS3_MRT_MASK(ASURA_DEFERRED_RENDER_TARGET_CURRENT_FRONT_BUFFER);

	// clear them all if we're in x-ray mode, it makes a mess of things otherwise.
	if ( Asura_Visibility_System::ShouldRenderInXRayMode() )
	{
		uMask = ASURA_PS3_MRT_MRT0123_ALL;
	}

	if ( uMask != 0 )
	{
		Asura_PS3_RenderingStates::SetColorMaskMrtState( uMask );

		Asura_PS3_FragmentShaderCopy *pxFP = Asura_PS3_GCM_ShadersLibrary::GetPermanentFP( ASURA_SHADER_HASH_GLOBAL, ASURA_GLOBAL_SHADER_BLANK_MRT );
		pxFP->Bind();

		static const float afCols[16] = { 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,Asura_Camera::GetFarClip(),0,0 };
		pxFP->SetArray( 0, 4, 4, afCols ); //BW this is nasty hard coded index of 0, perfectly safe but really it should do this through a wrapper

		Asura_Overlay_Quad xQuad;
		xQuad.Colour = 0x00000000; //ignored by FP
		xQuad.m_bNonRotated = true;
		xQuad.m_bGouraudShaded=false;
		xQuad.SetRectXY( 0.0f, 0.0f, Asura_PS3_Graphics::GetScreenWidth(), Asura_PS3_Graphics::GetScreenHeight() );

		Asura_PS3_QuadOverlayRenderer::RenderEx( &xQuad );
		Asura_PS3_QuadOverlayRenderer::Flush();

		Asura_PS3_RenderingStates::SetColorMaskMrtState( ASURA_PS3_MRT_MRT0123_ALL );
	}

	// depth should always be cleared
	cellGcmSetClearDepthStencilUnsafeInline( Asura_PS3_Graphics::GetGcmCurrentContext(), 0xffffff00 );
	cellGcmSetClearSurfaceUnsafeInline( Asura_PS3_Graphics::GetGcmCurrentContext(), CELL_GCM_CLEAR_Z | CELL_GCM_CLEAR_S );
}

void Asura_PS3_DeferredRendering::SwitchToFrontBuffer( void )
{
	Asura_Assert(!s_bUsingFrontBuffer, "Asura_PS3_DeferredRendering::SwitchToFrontBuffer() Tried to switch to front buffer twice");
	if (!s_bUsingFrontBuffer)
	{
		Asura_PS3_QuadOverlayRenderer::Flush();
		s_pxFinalBuffer->BindAsSingleRT();

		Asura_PS3_RenderingStates::SetSRGBWriteEnabled( true );

		//First copy the data in the FinalBuffer
		Asura_PS3_FrameBuffer *pxSource = NULL;
		if( s_bUseResolvedBuffer )
		{
			pxSource = s_pxResolvedBuffer;
		}
		else
		{
			pxSource = s_pxDeferredBuffer;
		}

		pxSource->ResolveToRenderTarget(s_pxFinalBuffer);

		s_bUsingFrontBuffer = true;
	}


}

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PS3_DeferredRendering::EndFrame
// Author       : Catalin Arsenescu (20-November-2007)
////////////////////////////////////////////////////////////////////////////////
void Asura_PS3_DeferredRendering::EndFrame( void )
{

    ASURA_PS3_RSX_PROFILE__BEGIN( ASURA_PS3_RSX_PROFILE__BFRRESOLVES, "End Frame -> Start" );

	if (!s_bUsingFrontBuffer)
	{
		SwitchToFrontBuffer();
	}
	s_bUsingFrontBuffer = false;

	if ( g_bShowMagnifier )
	{
		RenderMagnifier( 3, 400, 720 );
	}

	Asura_PS3_RenderingStates::SetSRGBWriteEnabled( false );

	ASURA_PS3_RSX_PROFILE__END( ASURA_PS3_RSX_PROFILE__BFRRESOLVES );

	s_pxFinalBuffer->Flip();

	Asura_PS3_QuadOverlayRenderer::EndFrame();

	cellSysutilCheckCallback();
}

#ifdef ASURA_ASSERT
////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PS3_DeferredRendering::AssertFlip
// Author       : Catalin Arsenescu (22-November-2007)
////////////////////////////////////////////////////////////////////////////////
void Asura_PS3_DeferredRendering::AssertFlip( void )
{
	Asura_PS3_SwapChain::SetDefaultCommandBuffer();
	Asura_PS3_RenderingStates::ClearSurface();
	s_pxFinalBuffer->BindAsSingleRT();
	s_pxFinalBuffer->AssertFlip();
	Asura_PS3_SwapChain::RestoreCommandBuffer();
}
#endif
////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PS3_DeferredRendering::EndMrt
// Author       : Catalin Arsenescu (20-November-2007)
////////////////////////////////////////////////////////////////////////////////
void Asura_PS3_DeferredRendering::EndMrt( void )
{
    ASURA_PS3_RSX_PROFILE__BEGIN( ASURA_PS3_RSX_PROFILE__BFRRESOLVES, "EndMrt" );

	//Copy data into the s_pxDeferredResolveBuffer and from now on use only this buffer
	s_pxResolvedBuffer->BindAsSingleRT();

	//use whole screen, even if we're drawing to a bordered region
	Asura_PS3_RenderingStates::SetViewport(0,0,Asura_PS3_Graphics::GetScreenWidth(),Asura_PS3_Graphics::GetScreenHeight());

	Asura_PS3_QuadOverlayRenderer::RenderHDR();
//	cellGcmSetZcullStatsEnableUnsafeInline( Asura_PS3_Graphics::GetGcmCurrentContext(), CELL_GCM_FALSE );

	//restore viewing area...
	Asura_PS3_Graphics::SetViewportToDefaults();


    ASURA_PS3_RSX_PROFILE__END( ASURA_PS3_RSX_PROFILE__BFRRESOLVES );

	s_bUseResolvedBuffer = true;
	Asura_PS3_QuadOverlayRenderer::Flush();
}

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PS3_DeferredRendering::Bind
// Author       : Catalin Arsenescu (20-November-2007)
////////////////////////////////////////////////////////////////////////////////
void Asura_PS3_DeferredRendering::Bind( void )
{
	if( s_bUseResolvedBuffer )
	{
		s_pxResolvedBuffer->Bind(false);
	}
	else
	{
		s_pxDeferredBuffer->Bind(false);
	}

	Asura_PS3_Graphics::ApplyCameraSettings();
}


////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PS3_DeferredRendering::BindAsSingleRT
// Author       : Catalin Arsenescu (20-November-2007)
////////////////////////////////////////////////////////////////////////////////
void Asura_PS3_DeferredRendering::BindAsSingleRT( void )
{
	if (s_bUsingFrontBuffer)
	{
		s_pxFinalBuffer->Bind();
	} 
	else if( s_bUseResolvedBuffer )
	{
		s_pxResolvedBuffer->Bind(false);
	}
	else
	{
		s_pxDeferredBuffer->BindAsSingleRT(false);
	}

	Asura_PS3_Graphics::ApplyCameraSettings();
}

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PS3_DeferredRendering::BindAsZPassOnly
// Author       : Ben Weston (06-March-2009)
////////////////////////////////////////////////////////////////////////////////
void Asura_PS3_DeferredRendering::BindAsZPassOnly( void )
{
	if( s_bUseResolvedBuffer )
	{
		s_pxResolvedBuffer->BindAsZPassOnly(false);
	}
	else
	{
		s_pxDeferredBuffer->BindAsZPassOnly(false);
	}
	
	Asura_PS3_Graphics::ApplyCameraSettings();
}

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PS3_DeferredRendering::RestoreBind
// Author       : Catalin Arsenescu (20-November-2007)
////////////////////////////////////////////////////////////////////////////////
void Asura_PS3_DeferredRendering::RestoreBind( void )
{
	if( !s_bUseResolvedBuffer )
	{
		s_pxDeferredBuffer->RestoreBind();
	}
}

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PS3_DeferredRendering::IsBindAsSingleRT
// Author       : Catalin Arsenescu (20-November-2007)
////////////////////////////////////////////////////////////////////////////////
bool Asura_PS3_DeferredRendering::IsBindAsSingleRT( void )
{
	if( s_bUseResolvedBuffer )
	{
		return true;
	}
	else
	{
		return s_pxDeferredBuffer->IsBindAsSingleRT();
	}
}

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PS3_DeferredRendering::GetRTAsTexture
// Author       : Ben Weston (06-March-2009)
////////////////////////////////////////////////////////////////////////////////
Asura_PS3_Texture* Asura_PS3_DeferredRendering::GetRTAsTexture( u_int uRenderTarget )
{
	if( s_bUseResolvedBuffer )
	{
		return s_pxResolvedBuffer->GetRTAsTexture(uRenderTarget);
	}
	else
	{
		return s_pxDeferredBuffer->GetRTAsTexture(uRenderTarget);
	}
}

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PS3_DeferredRendering::ResolveBackBuffer
// Author       : Catalin Arsenescu (22-November-2007)
////////////////////////////////////////////////////////////////////////////////
void Asura_PS3_DeferredRendering::ResolveBackBuffer( void )
{
	ASURA_PS3_RSX_PROFILE__BEGIN( ASURA_PS3_RSX_PROFILE__BFRRESOLVES, "Asura_PS3_DeferredRendering::ResolveBackBuffer" );

	// resolve onto a buffer we're not presently using (the front of the deferred buffer)
	s_pxDeferredBuffer->BindAsSingleRT();

	//use whole screen, even if we're drawing to a bordered region
	Asura_PS3_RenderingStates::SetViewport(0,0,Asura_PS3_Graphics::GetScreenWidth(),Asura_PS3_Graphics::GetScreenHeight());

	s_pxResolvedBuffer->ResolveToRenderTarget( s_pxDeferredBuffer );

	//restore viewing area...
	Asura_PS3_Graphics::SetViewportToDefaults();


	ASURA_PS3_RSX_PROFILE__END( ASURA_PS3_RSX_PROFILE__BFRRESOLVES );
}

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PS3_DeferredRendering::GetResolvedTexture
// Author       : Catalin Arsenescu (22-November-2007)
////////////////////////////////////////////////////////////////////////////////
Asura_PS3_Texture* Asura_PS3_DeferredRendering::GetResolvedTexture( void )
{
	return s_pxDeferredBuffer->GetRTAsTexture( ASURA_RENDER_TARGET_CURRENT_FRONT_BUFFER );
}


// Show zoomed in region for evaluating anti-aliasing
// width & height are size of magnifier's display, placed at bottom right of screen.
void Asura_PS3_DeferredRendering::RenderMagnifier( float fMagnification, u_int uWidth, u_int uHeight )
{
	s_pxFinalBuffer->BindAsSingleRT();

	Asura_PS3_Texture* pxSourceTexture = s_pxFinalBuffer->GetRTAsTexture( ASURA_RENDER_TARGET_CURRENT_FRONT_BUFFER );

	Asura_Overlay_Quad xQuad;

	float fW = ((float)uWidth)/(1280.0f*fMagnification);
	float fH = ((float)uHeight)/(720.0f*fMagnification);

	xQuad.SetRectUV( 0.5f-fW, 0.5f-fH/2, fW, fH );
	xQuad.SetRectXY(1280-uWidth, 720-uHeight, uWidth, uHeight );
	xQuad.TextureIndex = ASURA_TEXTURE_NONE;
	xQuad.TranslucencyMode = ASURA_TRANSLUCENCY_OFF;
	xQuad.Colour = 0xFFFFFFFF;

	Asura_PS3_QuadOverlayRenderer::RenderEx( xQuad, pxSourceTexture, true, 0, 0, true );
}
