//
// Asura_PS3_Graphics.cpp
//
// Created by KJFL - 21 June 2005
// Modified Catalin Arsenescu - 27 March 2007
//
// Copyright (C) 2005 Rebellion, All Rights Reserved.
//
#define	VIDEO_OUT_CONFIGURE_NON_BLOCK	0

#include "Core/Asura.h"

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

// Stdlib platform-specific.
#include <stdlib.h>
#include <sdk_version.h>
#include <sysutil/sysutil_sysparam.h>
#include <cell/sysmodule.h>
#if VIDEO_OUT_CONFIGURE_NON_BLOCK
	#include <sys/timer.h>
#endif //#if VIDEO_OUT_CONFIGURE_NON_BLOCK

// Engine independent.
#include "Camera/Asura_Camera.h"
#include "Core/Asura_Maths.h"
#include "Core/Asura_Material_System.h"
#include "Core/Asura_Multithread.h"
#include "Core/Asura_Timers.h"
#include "Debugging/Asura_CPUProfile.h"
#if defined(ASURA_USE_PROFILING_SYSTEM)
#include "Debugging/Asura_CPUProfile_Renderer.h"
#endif
#include "Debugging/Asura_Logfile.h"
#if defined(ASURA_USE_SCREENSHOT_SYSTEM)
#include "Debugging/Asura_ScreenShot_System.h"
#endif
#include "GUIMenu/Asura_GUIMenu_System.h"
#include "Interface/Asura_CommandConsole.h"
#include "Rendering/Asura_Fog.h"
#include "Rendering/Asura_Render.h"
#include "Rendering/Asura_Viewport.h"

// Engine platform-specific.
#include "Debugging/Asura_PS3_DebugInfo.h"
#include "Debugging/Asura_PS3_CommandBufferInspector.h"
#include "Rendering/Asura_PS3_DeferredRendering.h"
#include "Rendering/Asura_PS3_DeferredShading.h"
#include "Rendering/Asura_PS3_FrameBuffer.h"
#include "Rendering/Asura_PS3_Helper.h"
#include "Rendering/Asura_PS3_Projector_System.h"
#include "Rendering/Asura_PS3_QuadOverlayRenderer.h"
#include "Rendering/Asura_PS3_RenderingStates.h"
#include "Rendering/Asura_PS3_RSXProfiling.h"
#include "Rendering/Asura_PS3_ScratchRenderMemory.h"
#include "Rendering/Asura_PS3_TextureManagement.h"
#include "Rendering/Asura_PS3_LineRenderer.h"
#include "Rendering/Asura_PS3_SwapChain.h"

// internal defines
#if defined(ASURA_PS3_SONY_SUBMISSION) && defined(ASURA_USE_GCM_REPLAY)
#error "should not mix sony sub with GCM replay"
#endif

// debugvars
DEBUGVAR bool dbg_bForce480i = false;

// internal globals

// Dimensions of internal screen to render to...
u_int*					Asura_PS3_Graphics::s_pxMainMemoryAddr			= 0;

unsigned int			Asura_PS3_Graphics::s_uScreenWidth				= 0;
unsigned int			Asura_PS3_Graphics::s_uScreenHeight				= 0;
unsigned int			Asura_PS3_Graphics::s_uOutputWidth				= 0;
unsigned int			Asura_PS3_Graphics::s_uOutputHeight				= 0;
unsigned int			Asura_PS3_Graphics::s_uScreenFormat				= CELL_VIDEO_OUT_BUFFER_COLOR_FORMAT_X8R8G8B8;
static	unsigned int	s_uiBytesPerPixel								= 4;
float					Asura_PS3_Graphics::s_fAspectRatio				= 16.0f / 9.0f;

bool					Asura_PS3_Graphics::s_bInShutdown				= false;
bool					Asura_PS3_Graphics::s_bIsInitialised			= false;
bool					Asura_PS3_Graphics::s_bSecondaryDrawThread		= false;

unsigned int			Asura_PS3_Graphics::s_uiFlushReference			= 0;
bool					Asura_PS3_Graphics::s_RenderingToSkybox			= false;
u_int					Asura_PS3_Graphics::s_uSCullModeHash			= 0;
float					Asura_PS3_Graphics::s_fGamma					= 0.5f;

Asura_Mutex				Asura_PS3_Graphics::s_xMutex;

volatile bool			g_bFrameStarted = false;
#define					ASURA_USE_TEST_ZCULL

Asura_AMatrix4x4 Asura_PS3_Graphics::s_xViewMatrix;
Asura_AMatrix4x4 Asura_PS3_Graphics::s_xProjectionMatrix;
Asura_AMatrix4x4 Asura_PS3_Graphics::s_xViewProjectionMatrix;
Asura_AMatrix4x4 Asura_PS3_Graphics::s_xWorldViewProjectionMatrix;

#if defined( ASURA_DEBUG_VARIABLE_SYSTEM )
bool					Asura_PS3_Graphics::s_bUseGCmMacros				= false;
bool					Asura_PS3_Graphics::s_bUseGammaCorrection		= true;
#endif

static bool				g_bEnableChangeScull = true;
static bool				g_bEnableReloadScull = true;

struct ResolutionEntry
{
    CellVideoOutResolutionId		xResolution;
    CellVideoOutDisplayAspect		xAspect;
};


////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PS3_Graphics::Initialise
// Author       : Catalin Arsenescu (26-March-2007)
////////////////////////////////////////////////////////////////////////////////
void Asura_PS3_Graphics::Initialise( const u_char ucHostMainMemorySizeInMB, unsigned char ucCommandBufferSizeInMB, unsigned char ucRenderTargets )
{
	Asura_PS3_SwapChain::Initialise();

	if(	s_pxMainMemoryAddr )
	{
		Asura_OutputToDebugger( ("Asura_PS3_Graphics::Initialise -> Host address already allocated. Possible because called twice or before a Shutdown.\n") );
		return;
	}

#ifdef CELL_GCM_DEBUG
	gCellGcmDebugCallback  = cellGcmDebugFinish;
#endif

	cellSysmoduleLoadModule(CELL_SYSMODULE_AVCONF_EXT); // for gamma adjustment

	SetGammaSetting(s_fGamma); // make sure the RSX is set to our initial value.

	SetGraphicMode();
	Asura_PS3_SwapChain::Create();

	s_bIsInitialised = true;

	//Initialise the video memory allocation system
    CellGcmConfig xGcmConfig;

	cellGcmGetConfiguration( &xGcmConfig );
	Asura_PS3_VideoMemoryManager::Initialise( xGcmConfig.localAddress, xGcmConfig.localSize );

	Asura_PS3_MapAllMemoryToRSX(Asura_PS3_SwapChain::s_pxDefaultCommandBuffer, Asura_PS3_SwapChain::s_uTotalSize);

	Asura_PS3_DeferredRendering::Initialise();
#if defined ASURA_PS3_ALLOW_STATS
	Asura_PS3_DebugInfo::Initialise();
#endif
#if defined ASURA_PS3_USE_COMMAND_BUFFER_INSPECTOR
	Asura_PS3_CommandBufferInspector::Initialise();
#endif

	Asura_PS3_RenderingStates::Initialise();


#ifndef ASURA_PS3_SONY_SUBMISSION
	#ifdef ASURA_PS3_USE_GPU_PROFILE_BARS
		Asura_PS3_RSXProfiling::Initialise();
	#endif
#endif //#ifndef ASURA_PS3_SONY_SUBMISSION

//BW for video capture:
// 	#ifdef ASURA_USE_GCM_REPLAY
// 		cell::Gcm::Replay::Capture::NotifyCaptureSurface( gCellGcmCurrentContext, pCellGcmSurface );
// 	#endif
//(maybe put this in deferred rendering init)

	Asura_PS3_ScratchRenderMemory::Initialise();

	//Clear the screen with the default background color (0xFF3333CC)
	cellGcmResetFlipStatus();

//BW this keeps breaking things, it's too early much of our setup isn't done yet but those functions call lots of other systems.
// 	BeginFrame();
// 	EndFrame();

#if defined( ASURA_DEBUG_VARIABLE_SYSTEM )
	Asura_CommandConsole::AddVar(L"Render.SCULL.EnableModeChange", &g_bEnableChangeScull, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
	Asura_CommandConsole::AddVar(L"Render.SCULL.EnableReload", &g_bEnableReloadScull, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
	Asura_CommandConsole::AddVar( L"Render.Test.UseGCMMacros", &s_bUseGCmMacros,	Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
	Asura_CommandConsole::AddVar( L"Render.Test.UseSRGBCorrection", &s_bUseGammaCorrection,	Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
	Asura_CommandConsole::AddVar( L"Render.Test.VideoOutGamma", &s_fGamma, 0.0f, 1.0f,	Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
#endif
}

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PS3_Graphics::Shutdown
// Author       : Catalin Arsenescu (26-March-2007)
////////////////////////////////////////////////////////////////////////////////
void Asura_PS3_Graphics::Shutdown( void )
{
	s_bInShutdown = true;

    Asura_PS3_Helper::WaitForIdleState();

    Asura_PS3_ScratchRenderMemory::Shutdown();
#if defined ASURA_PS3_ALLOW_STATS
	Asura_PS3_DebugInfo::Shutdown();
#endif
#ifndef ASURA_PS3_SONY_SUBMISSION
#ifdef ASURA_PS3_USE_GPU_PROFILE_BARS
    Asura_PS3_RSXProfiling::Shutdown();
#endif
#endif

	Asura_PS3_DeferredRendering::Shutdown();
	Asura_PS3_VideoMemoryManager::Shutdown();

	cellSysmoduleUnloadModule(CELL_SYSMODULE_AVCONF_EXT);

	free( s_pxMainMemoryAddr );
	s_pxMainMemoryAddr	= NULL;
}


// Make sure RSX doesn't try to access any data that's been freed immediately after this point
void Asura_PS3_Graphics::Reset( void )
{
	// discard current command buffer, we're never going to draw it.
	Asura_PS3_SwapChain::SkipFlip();
}

void Asura_PS3_Graphics::BeginFrame( void )
{
	if( g_bFrameStarted )
	{
		EndFrame();
	}
	LockMutex();
	g_bFrameStarted = true;

#ifdef ASURA_PS3_USE_GPU_PROFILE_BARS
    Asura_PS3_RSXProfiling::BeginFrame();
#endif

	ResetEnvironment();

	Asura_PS3_DeferredRendering::BeginFrame();
	Asura_PS3_LineRenderer::BeginFrame();
	Asura_PS3_DeferredShading::BeginFrame();
	Asura_PS3_ProjectorSystem::BeginFrame();
}

void Asura_PS3_Graphics::EndFrame( void )
{
	LockMutex();
	if( g_bFrameStarted )
	{
		g_bFrameStarted = false;
#ifndef ASURA_PS3_SONY_SUBMISSION
#ifdef ASURA_PS3_USE_GPU_PROFILE_BARS
        Asura_PS3_RSXProfiling::RenderBars();
#endif
#endif
#if defined ASURA_PS3_ALLOW_STATS
		Asura_PS3_DebugInfo::Render();
#endif
		
		Asura_PS3_QuadOverlayRenderer::Flush();
		Asura_PS3_LineRenderer::EndFrame();
		Asura_PS3_DeferredShading::EndFrame();
		Asura_PS3_ProjectorSystem::EndFrame();
		Asura_PS3_DeferredRendering::EndFrame();

#if defined(ASURA_USE_SCREENSHOT_SYSTEM)
		Asura_ScreenShot_System::DrawBufferIsReady	();
#endif	// defined(ASURA_USE_SCREENSHOT_SYSTEM)

#ifdef ASURA_PS3_USE_GPU_PROFILE_BARS
        Asura_PS3_RSXProfiling::EndFrame();
#endif
		UnlockMutex();
	}
	UnlockMutex();
	
#if defined(ASURA_DEBUG_VARIABLE_SYSTEM)
	static float fLastGamma = 0.5f;
	if ( s_fGamma != fLastGamma )
	{
		SetGammaSetting(s_fGamma);
		fLastGamma = s_fGamma;
	}
#endif
}

void Asura_PS3_Graphics::LockMutex()
{
	s_xMutex.Lock();
}

bool Asura_PS3_Graphics::LockMutexTimeOut( float fTimeoutInSeconds )
{
	u_int uWait = (u_int)(fTimeoutInSeconds*1000000.0f);

	int iRet = sys_lwmutex_lock( &s_xMutex.m_xMutex, uWait );
	if ( iRet == CELL_OK || iRet == EDEADLK )			// if it worked or we already have it locked.
	{
		s_xMutex.Lock(); // update lock counters and all that jazz
		return true;
	}
	
	if ( iRet != ETIMEDOUT )
	{
		Asura_OutputToDebugger_Retail(("Asura_PS3_Graphics::LockMutexTimeOut - unexpected return code from sys_lwmutex_lock %d\n", iRet));
	}
	
	return false;
}

void Asura_PS3_Graphics::UnlockMutex()
{
	s_xMutex.Unlock();
}

Asura_PS3_Graphics::Asura_PS3_Graphics( void )
{
}

Asura_PS3_Graphics::~Asura_PS3_Graphics( void )
{
}

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PS3_Graphics::SetGraphicMode
// Author       : Catalin Arsenescu (26-March-2007)
////////////////////////////////////////////////////////////////////////////////
void Asura_PS3_Graphics::SetGraphicMode( void )
{
#define MAX_RESOLUTION_TABLE	16
/* works only when a is 2^n */
#define roundup(x,a)			(((x)+(a)-1)&(~((a)-1)))

	struct ResolutionEntry sResolutionTable[ MAX_RESOLUTION_TABLE ] =
	{
#ifdef ASURA_PS3_USE_1080P_RESOLUTIONS
		{ CELL_VIDEO_OUT_RESOLUTION_1080,		CELL_VIDEO_OUT_ASPECT_16_9 },
		{ CELL_VIDEO_OUT_RESOLUTION_1600x1080,	CELL_VIDEO_OUT_ASPECT_16_9 },
		{ CELL_VIDEO_OUT_RESOLUTION_1440x1080,	CELL_VIDEO_OUT_ASPECT_16_9 },
		{ CELL_VIDEO_OUT_RESOLUTION_1280x1080,	CELL_VIDEO_OUT_ASPECT_16_9 },
		{ CELL_VIDEO_OUT_RESOLUTION_960x1080,	CELL_VIDEO_OUT_ASPECT_16_9 },
#endif
		{ CELL_VIDEO_OUT_RESOLUTION_720,		CELL_VIDEO_OUT_ASPECT_16_9 },
		{ CELL_VIDEO_OUT_RESOLUTION_480,		CELL_VIDEO_OUT_ASPECT_16_9 },
		{ CELL_VIDEO_OUT_RESOLUTION_576,		CELL_VIDEO_OUT_ASPECT_16_9 },
#ifdef ASURA_PS3_USE_1080P_RESOLUTIONS
		{ CELL_VIDEO_OUT_RESOLUTION_1080,		CELL_VIDEO_OUT_ASPECT_4_3 },
		{ CELL_VIDEO_OUT_RESOLUTION_1600x1080,	CELL_VIDEO_OUT_ASPECT_4_3 },
		{ CELL_VIDEO_OUT_RESOLUTION_1440x1080,	CELL_VIDEO_OUT_ASPECT_4_3 },
		{ CELL_VIDEO_OUT_RESOLUTION_1280x1080,	CELL_VIDEO_OUT_ASPECT_4_3 },
		{ CELL_VIDEO_OUT_RESOLUTION_960x1080,	CELL_VIDEO_OUT_ASPECT_4_3 },
#endif
		{ CELL_VIDEO_OUT_RESOLUTION_720,		CELL_VIDEO_OUT_ASPECT_4_3 },
		{ CELL_VIDEO_OUT_RESOLUTION_576,		CELL_VIDEO_OUT_ASPECT_4_3 },
		{ CELL_VIDEO_OUT_RESOLUTION_480,		CELL_VIDEO_OUT_ASPECT_4_3 },
	};

    CellVideoOutResolutionId		xResolutionId;
    CellVideoOutDisplayAspect		xAspect;
    CellVideoOutBufferColorFormat	xDisplayFormat = static_cast< CellVideoOutBufferColorFormat >( s_uScreenFormat );
    CellVideoOutResolution			xResolution;
    CellVideoOutConfiguration		xVideocfg;
    CellVideoOutState				xVideoState;

	int								ret						= 0;
	unsigned int					uBufferWidth			= 0;
	unsigned int					uColorPitch				= 0;
	bool bFoundRes = false;

	for(int i = -1; i < MAX_RESOLUTION_TABLE; ++i )
	{
		if (dbg_bForce480i)
		{
			i = MAX_RESOLUTION_TABLE - 1;
		}
		// NY - 10 September 2008 - before working through list, check res already in use.
		// this seems to not get set when botting from the debugger for some reason, so if it isn't, we'll fall back on the list
		if (i == -1)
		{
			CellVideoOutConfiguration xVideoConfig;
			int iRet = cellVideoOutGetConfiguration( CELL_VIDEO_OUT_PRIMARY, &xVideoConfig, 0 );

			if( iRet == CELL_OK 
				&& xVideoConfig.resolutionId != 0)
			{
				xResolutionId	= static_cast<CellVideoOutResolutionId>(xVideoConfig.resolutionId);
				xAspect			= static_cast<CellVideoOutDisplayAspect>(xVideoConfig.aspect);
				Asura_OutputToDebugger_Retail( ("Asura_PS3_Graphics::SetGraphicMode - trying res %u aspect %u from cellVideoOutGetConfiguration\n", xResolutionId, xAspect) );
			}
			else
			{
				continue;
			}
		}
		else
		{
			xResolutionId	= sResolutionTable[ i ].xResolution;
			xAspect			= sResolutionTable[ i ].xAspect;
			Asura_OutputToDebugger_Retail( ("Asura_PS3_Graphics::SetGraphicMode - trying res %u aspect %u from res table\n", xResolutionId, xAspect) );
		}
        /* test resolution availability */
		ret = cellVideoOutGetResolutionAvailability( CELL_VIDEO_OUT_PRIMARY, xResolutionId, xAspect, 0 );
		if(ret == 0)
		{
			continue;
		}

		ret = cellVideoOutGetResolution( xResolutionId, &xResolution );
		if(ret != CELL_OK)
		{
			Asura_OutputToDebugger( ("Asura_PS3_Graphics::SetGraphicMode -> cellVideoOutGetResolution failed (game will crash)\n") );
			return;
		}

		s_uOutputWidth			= xResolution.width;
		s_uOutputHeight			= xResolution.height;

		uBufferWidth			= s_uOutputWidth;
		uColorPitch				= cellGcmGetTiledPitchSize( uBufferWidth * s_uiBytesPerPixel ); // 64Bits color format

		if( uColorPitch == 0 )
		{
			Asura_OutputToDebugger( ("Asura_PS3_Graphics::SetGraphicMode -> cellGcmGetTiledPitchSize failed (game will crash)\n") );
			return;
		}

		memset( &xVideocfg, 0, sizeof( CellVideoOutConfiguration ) );
		xVideocfg.aspect		= xAspect;
		xVideocfg.format		= xDisplayFormat;
		xVideocfg.pitch			= uColorPitch;
		xVideocfg.resolutionId	= xResolutionId;

	#if VIDEO_OUT_CONFIGURE_NON_BLOCK
		ret = cellVideoOutConfigure( CELL_VIDEO_OUT_PRIMARY, &xVideocfg, NULL, 1 );
		if( ret != CELL_OK )
		{
			continue;
		}

		bool bError = false;
		do
		{
			ret = cellVideoOutGetState( CELL_VIDEO_OUT_PRIMARY, 0, &videoState );
			if (ret != CELL_OK)
			{
				bError = true;
				break;
			}
			sys_timer_usleep(5000);
		}while( videoState.state != CELL_VIDEO_OUT_OUTPUT_STATE_ENABLED );
		if( bError )
		{
			continue;
		}
	#else //#if VIDEO_OUT_CONFIGURE_NON_BLOCK
		ret = cellVideoOutConfigure( CELL_VIDEO_OUT_PRIMARY, &xVideocfg, NULL, 0 );
		if( ret != CELL_OK )
		{
			continue;
		}

		/* get video out state */
		ret = cellVideoOutGetState( CELL_VIDEO_OUT_PRIMARY, 0, &xVideoState );
		if( ret != CELL_OK )
		{
			continue;
		}
		else
		{
			bFoundRes = true;
			break;
		}
	#endif //#if VIDEO_OUT_CONFIGURE_NON_BLOCK
	}


	if( !bFoundRes)
	{
		Asura_OutputToDebugger( ("Asura_PS3_Graphics::SetGraphicMode -> can not find a good resolution to start the game (game will crash)\n") );
		return;
	}
	Asura_OutputToDebugger_Retail( ("Asura_PS3_Graphics::SetGraphicMode - using display res %u x %u\n", s_uOutputWidth, s_uOutputHeight) );

	s_fAspectRatio	= ( xVideoState.displayMode.aspect == CELL_VIDEO_OUT_ASPECT_4_3 )
					? (4.0f / 3.0f)
					: (16.0f / 9.0f);
	s_uScreenHeight	= ASURA_PS3_INTERNAL_SCREEN_HEIGHT;
	s_uScreenWidth	= static_cast<u_int>(ASURA_PS3_INTERNAL_SCREEN_HEIGHT * s_fAspectRatio);
	Asura_OutputToDebugger_Retail( ("Asura_PS3_Graphics::SetGraphicMode - using render res %u x %u\n", s_uScreenWidth, s_uScreenHeight) );

	Asura_Viewport::Set( s_uScreenWidth, s_uScreenHeight );

#ifdef ASURA_GUI_MENU_ALWAYS_WIDESCREEN
	Asura_GUIMenu_System::SetVirtualResolution( 1280.0f, (1280.0f / s_uScreenWidth) * s_uScreenHeight );
#endif

	Asura_Camera::SetPhysicalScreenAspectRatio( s_fAspectRatio );

#undef roundup
#undef MAX_RESOLUTION_TABLE
}

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PS3_Graphics::SetRenderTarget
// Author       : Catalin Arsenescu (26-March-2007)
////////////////////////////////////////////////////////////////////////////////
void Asura_PS3_Graphics::SetRenderTarget( void )
{
	Asura_PS3_DeferredRendering::Bind();
}

const Asura_AMatrix4x4& Asura_PS3_Graphics::GetWorldViewProjectionMatrix( const Asura_AMatrix4x4* xObjectMatrix )
{
	if( xObjectMatrix )
	{
		s_xWorldViewProjectionMatrix = (*xObjectMatrix) * s_xViewProjectionMatrix;
		return s_xWorldViewProjectionMatrix;
	}
	else
	{
		return s_xViewProjectionMatrix;
	}
}

void Asura_PS3_Graphics::SetViewProjMatricesAndViewport( float fViewportOutputMinZ, float fViewportOutputMaxZ )
{
	Asura_PS3_RenderingStates::SetViewport( Asura_PS3_RenderingStates::GetViewportX(), Asura_PS3_RenderingStates::GetViewportY(), Asura_PS3_RenderingStates::GetViewportWidth(), Asura_PS3_RenderingStates::GetViewportHeight(),
											fViewportOutputMinZ, fViewportOutputMaxZ,
											Asura_PS3_RenderingStates::GetViewportClipMinZ(), Asura_PS3_RenderingStates::GetViewportClipMaxZ() );
}


void Asura_PS3_Graphics::SetViewportToDefaults( bool bOverrun )
{
	// restore the viewport settings of the current camera, with default depth settings
	const Asura_ViewingArea* pxViewingArea = Asura_Camera::GetViewingArea();
	
	const CellGcmSurface* pxCurentSurface = Asura_PS3_RenderingStates::GetSurface();
	Asura_Assert( pxCurentSurface->width == s_uScreenWidth && pxCurentSurface->height == s_uScreenHeight, "SetViewportToDefaults: Cannot set viewport defaults when using different sized render target" ); 
	
	const int iViewX = Asura_Maths::FloatToInt( pxViewingArea->m_fXOffset * (float)s_uScreenWidth );
	const int iViewY = Asura_Maths::FloatToInt( pxViewingArea->m_fYOffset * (float)s_uScreenHeight );
	const int iViewW = Asura_Maths::FloatToInt( pxViewingArea->m_fWidth * (float)s_uScreenWidth );
	const int iViewH = Asura_Maths::FloatToInt( pxViewingArea->m_fHeight * (float)s_uScreenHeight );

	Asura_PS3_RenderingStates::SetViewport( iViewX, iViewY, iViewW, iViewH,
											 fASURA_PS3_DEFAULT_VIEWPORT_MIN_Z, fASURA_PS3_DEFAULT_VIEWPORT_MAX_Z,
											 fASURA_PS3_DEFAULT_VIEWPORT_MIN_Z, fASURA_PS3_DEFAULT_VIEWPORT_MAX_Z,
											 bOverrun );
}


////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PS3_Graphics::ResetEnvironment
// Author       : Catalin Arsenescu (26-March-2007)
// Need to be updated
////////////////////////////////////////////////////////////////////////////////
void Asura_PS3_Graphics::ResetEnvironment( void )
{
	Asura_PS3_RenderingStates::BeginFrame();

	//override a couple of the default settings for the defaults we actually want.
	Asura_PS3_RenderingStates::SetColorMaskMrtState( ASURA_PS3_MRT_MRT0123_ALL );
	SetRenderTarget();
	SetViewportToDefaults();

	cellGcmSetDitherEnableUnsafeInline( GetGcmCurrentContext(), CELL_GCM_FALSE );
	cellGcmSetCullFaceUnsafeInline( GetGcmCurrentContext(), CELL_GCM_FRONT );
	cellGcmSetRestartIndexEnableUnsafeInline( GetGcmCurrentContext(), CELL_GCM_TRUE );

#ifdef ASURA_USE_TEST_ZCULL
	static float moveForward = 0x100;
	static float pushBack = 0x100;
	static int32_t dir = 1;
	static float step = 0x0001;
	static float prev = 0.00f;
	int32_t	numTilesTested = cellGcmGetReport(CELL_GCM_ZCULL_STATS2, 0);
	int32_t	numTilesCulled = cellGcmGetReport(CELL_GCM_ZCULL_STATS3, 1);
	float ratioTilesCulled = numTilesTested ? (float)numTilesCulled / numTilesTested : 0;

	if (ratioTilesCulled > prev) {
		step = ( int32_t )( step * 1.5f );
	}
	else {
		step = ( int32_t )( step * 0.5f );
		dir = -dir;
	}
	prev = ratioTilesCulled;

	cellGcmSetClearReportUnsafeInline( GetGcmCurrentContext(), CELL_GCM_ZCULL_STATS );
	cellGcmSetZcullStatsEnableUnsafeInline( GetGcmCurrentContext(), CELL_GCM_TRUE );

#ifndef max
#define max(a,b)            (((a) > (b)) ? (a) : (b))
	moveForward = max( 0x0001, moveForward + dir * step );
	pushBack = max( 0x0001, ( int32_t )( moveForward * 0.5f ) );
#undef max
#else
	moveForward = max(0x0001, moveForward + dir * step);
	pushBack = max(0x0001, moveForward * 0.5);
#endif

	cellGcmSetZcullLimitUnsafeInline( GetGcmCurrentContext(), (uint16_t)moveForward, (uint16_t)pushBack );
#else
	cellGcmSetZcullStatsEnableUnsafeInline( GetGcmCurrentContext(), CELL_GCM_FALSE );
//	cellGcmSetZcullLimitUnsafeInline( GetGcmCurrentContext(), 256, 128 ); //values obtained using the USE_TEST_ZCULL
#endif
}

void Asura_PS3_Graphics::ApplyCameraSettings( void )
{
	const Asura_ViewingArea* pxViewingArea = Asura_Camera::GetViewingArea();
	
	const CellGcmSurface* pxCurentSurface = Asura_PS3_RenderingStates::GetSurface(); //BW use this because it is the only thing that has definitely been set for the current render-target
	const u_int uTargetWidth = pxCurentSurface->width;
	const u_int uTargetHeight= pxCurentSurface->height;
	
	const int iViewX = Asura_Maths::FloatToInt( pxViewingArea->m_fXOffset * (float)uTargetWidth );
	const int iViewY = Asura_Maths::FloatToInt( pxViewingArea->m_fYOffset * (float)uTargetHeight );
	const int iViewW = Asura_Maths::FloatToInt( pxViewingArea->m_fWidth * (float)uTargetWidth );
	const int iViewH = Asura_Maths::FloatToInt( pxViewingArea->m_fHeight * (float)uTargetHeight );

	Asura_PS3_RenderingStates::SetViewport( iViewX, iViewY, iViewW, iViewH,
				Asura_PS3_RenderingStates::GetViewportOutputMinZ(), Asura_PS3_RenderingStates::GetViewportOutputMaxZ(), // don't mess up the depth values
				Asura_PS3_RenderingStates::GetViewportClipMinZ(), Asura_PS3_RenderingStates::GetViewportClipMaxZ(), true );

	const u_int uViewportWidth  = Asura_Viewport::GetWidth();
	const u_int uViewportHeight = Asura_Viewport::GetHeight();

	const Asura_Vector_2&		xSubPixelOffset		= Asura_Render::GetSubPixelOffset();
	const float					fOffsetX			= xSubPixelOffset.x / (uViewportWidth  * 0.5f);
	const float					fOffsetY			= xSubPixelOffset.y / (uViewportHeight * 0.5f);
	float						fZnear				= Asura_Camera::GetNearClip();
	float						fZfar				= Asura_Camera::GetFarClip();
	float						fFovy				= Asura_Camera::GetVerticalFieldOfView();
	float						fAspect				= Asura_Camera::GetXOverYAspectRatio();

	Asura_AMatrix4x4			matSubPixelOffset;
	Asura_AMatrix4x4			matProj;
	Asura_AMatrix4x4			matInvView;

	Asura_Camera::GetViewMatrix( matInvView );
	matInvView.InvertOrthogonal();

	matSubPixelOffset.Identity();
	matSubPixelOffset.SetTranslation( Asura_AVector3( fOffsetX, -fOffsetY, 0.0f ) );

	matProj.SetPerspectiveForLH( fFovy, fAspect, fZnear, fZfar );
	
	// adjust for viewport
	// BW - we could do this with the viewport's 2D scales, but doing it this way means all 3D stuff works, and 2D stuff
	// is still in screen-relative coordinates, which helps HUD, FSFX, edge anti-aliasing and anything else that cares.
	Asura_AMatrix4x4	xViewportScaleOffset(
			pxViewingArea->m_fWidth, 0, 0, pxViewingArea->m_fWidth+pxViewingArea->m_fXOffset*2.0f-1.0f,
			0, pxViewingArea->m_fHeight, 0, pxViewingArea->m_fHeight+pxViewingArea->m_fYOffset*2.0f-1.0f,
			0,0,1,0,
			0,0,0,1
		);
	xViewportScaleOffset.Apply( matProj, matProj );


	if (Asura_Render::ShouldRenderMirrored())
	{
		matProj.SetElement( 0, 0, -matProj.GetElement( 0, 0 ) );
	}

	matProj	= matSubPixelOffset * matProj;

	s_xViewMatrix = matInvView;
	s_xProjectionMatrix = matProj;

	s_xViewProjectionMatrix = s_xViewMatrix * s_xProjectionMatrix;
}

void Asura_PS3_Graphics::ClearZBuffer( void )
{
	cellGcmSetClearSurfaceUnsafeInline( GetGcmCurrentContext(), CELL_GCM_CLEAR_Z | CELL_GCM_CLEAR_S );
}

void Asura_PS3_Graphics::CheckExitCallBack( void )
{
	cellSysutilCheckCallback();
}

// skybox must already have been drawn, it writes this value into the stencil
void Asura_PS3_Graphics::BeginRenderToSkybox()
{
	Asura_Assert(!s_RenderingToSkybox,"Asura_PS3_Graphics::BeginRenderToSkybox - already rendering to skybox");

	// read stencil
	Asura_PS3_RenderingStates::SetStencilTestEnabled( true );
	Asura_PS3_RenderingStates::SetStencilMask(ASURA_STENCIL_MASK_SKY);
	Asura_PS3_RenderingStates::SetStencilFunction( ASURA_PS3_STENCIL_EQUAL, ASURA_STENCIL_MASK_SKY, ASURA_STENCIL_MASK_SKY );
	Asura_PS3_RenderingStates::SetStencilOperation( ASURA_PS3_STENCIL_OPERATION_KEEP, ASURA_PS3_STENCIL_OPERATION_KEEP, ASURA_PS3_STENCIL_OPERATION_KEEP );
	
	// disable z-buffer writing
	Asura_PS3_RenderingStates::RequestZBufferWriteEnabled( false );
	
	s_RenderingToSkybox = true;
}

void Asura_PS3_Graphics::EndRenderToSkybox()
{
	Asura_Assert(s_RenderingToSkybox,"Asura_PS3_Graphics::EndRenderToSkybox - called when not rendering to skybox");

	Asura_PS3_RenderingStates::SetStencilOperation( ASURA_PS3_STENCIL_OPERATION_KEEP, ASURA_PS3_STENCIL_OPERATION_KEEP, ASURA_PS3_STENCIL_OPERATION_KEEP );
	Asura_PS3_RenderingStates::SetStencilMask(0);
	Asura_PS3_RenderingStates::SetStencilTestEnabled( false );

	Asura_PS3_RenderingStates::RequestZBufferWriteEnabled( true );

	s_RenderingToSkybox = false;
}


// SCULL usage examples - when to use SwitchStencilCullMode and ReloadStencilCull
// ====================
// 
// Bad Usage - no performance gain:
// write shapes with value 1
// write shapes with value 2
// SetStencilFunction(equals, 1)	//next thing drawn will test value 1
// draw								//no fast-culling, SCULL not used - i.e. pixels that fail the test will take just as long to draw
// SetStencilFunction(equals, 2)	//next thing drawn will test value 2
// draw								//no fast-culling, SCULL not used
// 
// Good Usage - fast-cull with overhead for realoading SCULL:
// write shapes with value 1
// write shapes with value 2
// SetStencilFunction(equals, 1)	//next thing drawn will test value 1
// SwitchStencilCullMode(equals, 1)	//scull is ready to optimize things testing value 1
// ReloadStencilCull()				//scull reloaded by testing every pixel on screen against value 1 - this takes a little time
// draw								//fast-culled
// SetStencilFunction(equals, 2)	//next thing drawn will test value 2
// SwitchStencilCullMode(equals, 2) //scull is ready to optimize things testing value 2
// ReloadStencilCull()				//scull reloaded by testing every pixel on screen against value 2 - this takes a little time
// draw								//fast-culled
// 
// Best Usage - fast-cull and no overhead for reloading SCULL:
// SwitchStencilCullMode(equals, 1)	//scull is ready to optimize things testing value 1
// write shapes with value 1		//scull is updated as these are drawn
// SetStencilFunction(equals, 1)	//next thing drawn will test value 1
// draw								//fast-culled
// SwitchStencilCullMode(equals, 2)	//scull is ready to optimize things testing value 2
// write shapes with value 2		//scull is updated as these are drawn
// SetStencilFunction(equals, 2)	//next thing drawn will test value 2
// draw								//fast-culled
// 
// Obviously there are situations where the order of writing to and testing the stencil buffer cannot be arranged like the best usage,
// in those cases the good usage example should be followed.

//
// Asura_PS3_Graphics::ReloadStencilCull
//
// Ben Weston 20/08/2008
//
// Reload the SCULL buffer from the current stencil buffer.
//
// This takes a little time (.19ms), see the SCULL usage example (big comment above) for when best to call this (and when not to)
//
void Asura_PS3_Graphics::ReloadStencilCull( void )
{
	if ( !g_bEnableReloadScull ) return;

	Asura_PS3_RenderingStates::SetStencilTestEnabled(true);
	Asura_PS3_RenderingStates::SetStencilMask(0xff);
	Asura_PS3_RenderingStates::SetStencilFunction( ASURA_PS3_STENCIL_LESS, 0xff, 0xff ); // will always fail
	Asura_PS3_RenderingStates::SetStencilOperation( ASURA_PS3_STENCIL_OPERATION_KEEP, ASURA_PS3_STENCIL_OPERATION_KEEP, ASURA_PS3_STENCIL_OPERATION_ZERO ); // keep, keep, not-keep

	Asura_PS3_RenderingStates::SetCullFace( false );
	Asura_PS3_RenderingStates::SetAlphaBlending( false );

	Asura_Overlay_Quad xQuad;
	xQuad.SetRectXY(0,0,Asura_PS3_Graphics::GetScreenWidth(),Asura_PS3_Graphics::GetScreenHeight());
	Asura_PS3_GCM_ShadersLibrary::GetPermanentFP(ASURA_SHADER_HASH_GLOBAL, ASURA_GLOBAL_SHADER_BLANK)->Bind();
	Asura_PS3_QuadOverlayRenderer::RenderEx( &xQuad ); //render it without changing any more render states.
}


//
// Asura_PS3_Graphics::SwitchStencilCullMode
//
// Ben Weston 20/08/2008
//
// Change the current state stored in SCULL memory, and flush it to 0.
//
// To make optimal use of the stencil buffer, the SCULL render states need to be updated to match the current stencil function, and
// then the SCULL buffer must be cleared (since changing states invalidates it).
//
// If SCULL does not match the most recent SetStencilFunction() it will not cause any glitches, but culled pixels will take as long
// to draw as non-culled ones. i.e. you only need to call this when you want culling to be fast.
//
// This function takes almost no RSX time, since the SCULL buffer is very small so flushing it is a negligible overhead.
//
// (glossary: SCULL is a block of memory for storing the hierarchical stencil-cull buffer, which has 1-bit per 4x4 block of pixels
// indicating if all those pixels fail the stencil test)
//
void Asura_PS3_Graphics::SwitchStencilCullMode( u_int eStencilFunction, u_int uiRef, u_int uiMask )
{
	u_int uNewCullModeHash = (uiRef<<24)|(uiMask<<16)|eStencilFunction;

	if( s_uSCullModeHash != uNewCullModeHash && g_bEnableChangeScull )
	{
		s_uSCullModeHash = uNewCullModeHash;

		//note: SCULL functions use different enum to stencil function, but in the same order.   CELL_GCM_SCULL_SFUNC_GEQUAL = 6, CELL_GCM_GEQUAL = 0x206
		u_int scullFunc = eStencilFunction & 0xf;

		// setup stencil cull - this buffer does early-out on 4x4 blocks of pixels
		// invalidate the scull & setup for lighting pass
		cellGcmSetScullControlUnsafeInline( Asura_PS3_Graphics::GetGcmCurrentContext(), scullFunc, uiRef, uiMask );

		// scull buffer is now invalid, so it won't be used. to fix that we need to clear it (this takes almost no time and makes a huge difference to speed)
		Asura_PS3_RenderingStates::SetStencilMask(0xff);
		cellGcmSetClearDepthStencilUnsafeInline( Asura_PS3_Graphics::GetGcmCurrentContext(), 0xffffff00 | uiRef ); // clear to ref value so no pixel groups fast-fail
		cellGcmSetClearZcullSurfaceUnsafeInline( Asura_PS3_Graphics::GetGcmCurrentContext(), CELL_GCM_FALSE, CELL_GCM_TRUE );
	}
	else
	{
		if ( !g_bEnableChangeScull )
		{
			// make sure it doesn't optimise anything, for comparison
			cellGcmSetScullControlUnsafeInline( Asura_PS3_Graphics::GetGcmCurrentContext(), CELL_GCM_SCULL_SFUNC_EQUAL, 0, 0 );
		}
	}
}


void Asura_PS3_Graphics::SetGammaSetting( float fGamma )
{
	Asura_Assert( fGamma >= 0.0f && fGamma <= 1.0f, "Asura_PS3_Graphics::SetGammaSetting() gamma setting is out of range" );

	s_fGamma = fGamma;

	float fRealGamma = Asura_Maths::Lerp( 1.2f, 0.8f, fGamma ); // convert [0,1] control value to a [0.8,1.2] gamma value, as required by sony library.
	
	//BW if Sony allowed a wider range of values the best match for PC/Xbox behaviour would be:
	// float fRealGamma = pow( 2.0f, fGamma*2.0f-1.0f ); // gamma in the range 0.5 to 2.0, with 1.0 at the middle of the range.

	cellVideoOutSetGamma( CELL_VIDEO_OUT_PRIMARY, fRealGamma );
}
