//////////////////////////////////////////////////////////////////////////////////////////
//
//  FILE        :  Asura_Direct3D_RenderTargetManager.cpp
//
//  CREATED     :  Richard J. Fine  (24.3.2005)
//
//  Copyright (C) 2005 Rebellion, All Rights Reserved.
//
//////////////////////////////////////////////////////////////////////////////////////////


#include "Core/Asura.h"

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

#include <dxerr9.h>

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Direct3D_RenderTarget
//
////////////////////////////////////////////////////////////////////////////////

HRESULT Asura_Direct3D_RenderTarget::CreateD3DResources()
{
	HRESULT hr;

	Asura_Assert(Asura_Direct3D::D3DDevice != NULL, "No Direct3D Device!");
	if(!Asura_Direct3D::D3DDevice) return E_FAIL;

	// work out the actual width/height
	if(m_bBackbufferRelativeSize)
	{
		m_uActualWidth = static_cast<u_int>(m_fWidth * Asura_Direct3D::GetPresentParameters().BackBufferWidth);
		m_uActualHeight = static_cast<u_int>(m_fHeight * Asura_Direct3D::GetPresentParameters().BackBufferHeight);
	}
	else
	{
		m_uActualWidth = static_cast<u_int>(m_fWidth);
		m_uActualHeight = static_cast<u_int>(m_fHeight);
	}
	
	if( false == Asura_Direct3D_Caps::IsTextureNonPow2Supported() && false == Asura_Direct3D_Caps::IsNonPow2Conditional())
	{
		const u_int uActualWidth = Asura_Maths::GetCeilPow2( this->m_uActualWidth);
		const u_int uActualHeight = Asura_Maths::GetCeilPow2( this->m_uActualHeight);
		
		this->m_uActualWidth = uActualWidth;
		this->m_uActualHeight = uActualHeight;
	}

	// Clear out any existing pointers.
	Clear();

	// create the textures
	for(u_int uRTIndex = 0; uRTIndex < m_uNumRenderTargets; ++uRTIndex)
	{
		hr = Asura_Direct3D::D3DDevice->CreateTexture(m_uActualWidth, m_uActualHeight, 1, D3DUSAGE_RENDERTARGET, m_axColourFmt[ uRTIndex], D3DPOOL_DEFAULT, &m_apxTextures[uRTIndex], NULL);
		if(FAILED(hr)) return hr;
	}

	// get the surfaces
	for(u_int uRTIndex = 0; uRTIndex < m_uNumRenderTargets; ++uRTIndex)
	{
		hr = m_apxTextures[uRTIndex]->GetSurfaceLevel(0, &m_apxSurfaces[uRTIndex]);
		if(FAILED(hr)) return hr;
	}

	if (m_bIsUsingExistingDepthStencilBuffer)
	{
		Asura_Direct3D::D3DDevice->GetDepthStencilSurface(&m_pxDepthStencilSurface);
	} 
	// if the user specified a depthstencil format, make one of them as well
	else if(m_xDepthFmt != D3DFMT_UNKNOWN)
	{
		hr = Asura_Direct3D::D3DDevice->CreateDepthStencilSurface(m_uActualWidth, m_uActualHeight, m_xDepthFmt, D3DMULTISAMPLE_NONE, 0, TRUE, &m_pxDepthStencilSurface, NULL);
		if(FAILED(hr)) return hr;
	}

	return S_OK;
}






void Asura_Direct3D_RenderTarget::ReleaseD3DResources()
{
	if(m_pxDepthStencilSurface)
	{
		m_pxDepthStencilSurface->Release();
		m_pxDepthStencilSurface = NULL;
	}

	for(u_int uRTIndex = 0; uRTIndex < m_uNumRenderTargets; ++uRTIndex)
	{
		if (m_apxSurfaces[uRTIndex])
		{
			m_apxSurfaces[uRTIndex]->Release();
		}
	}

	for(u_int uRTIndex = 0; uRTIndex < m_uNumRenderTargets; ++uRTIndex)
	{
		if (m_apxTextures[uRTIndex])
		{
			m_apxTextures[uRTIndex]->Release();
		}
	}
	
	// Clear out the pointers.
	Clear();
}


// Clear the arrays of pointers.
void Asura_Direct3D_RenderTarget::Clear()
{
	memset(m_apxSurfaces, 0, sizeof(Direct3D_Surface*) * s_uMAX_NUM_RENDER_TARGETS);
	memset(m_apxTextures, 0, sizeof(Direct3D_Texture*) * s_uMAX_NUM_RENDER_TARGETS);
}


void Asura_Direct3D_RenderTarget::Bind()
{
	HRESULT xResult;
	
	SCOPEDPIXEVENT((0xff29f745, L"Bind RenderTarget object"));
	u_int u;
	for(u = 0; u < m_uNumRenderTargets; ++u)
	{
		xResult = Asura_Direct3D::D3DDevice->SetRenderTarget(u, GetSurface(u));
		ASURA_D3D_OK( xResult);
	}
	for(; u < Asura_Direct3D_Caps::GetMaxNumberOfRenderTargets(); ++u)
	{
		xResult = Asura_Direct3D::D3DDevice->SetRenderTarget(u, NULL);
		ASURA_D3D_OK( xResult);
	}
	xResult = Asura_Direct3D::D3DDevice->SetDepthStencilSurface( GetDepthStencilSurface());
	ASURA_D3D_OK( xResult);
	
	Asura_Direct3D::InvalidateSwapChain();
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Direct3D_RenderTargetManager
//
////////////////////////////////////////////////////////////////////////////////

Asura_Direct3D_RenderTargetManager::RenderTargetList	Asura_Direct3D_RenderTargetManager::s_xRenderTargetList;
Asura_LinkList<Asura_Direct3D_RenderTargetManager::ResetCallback> Asura_Direct3D_RenderTargetManager::s_xPreResetCallbacks;
Asura_LinkList<Asura_Direct3D_RenderTargetManager::ResetCallback> Asura_Direct3D_RenderTargetManager::s_xPostResetCallbacks;

void Asura_Direct3D_RenderTargetManager::Initialise()
{

}

void Asura_Direct3D_RenderTargetManager::Reset()
{
	Shutdown();
	Initialise();
}

void Asura_Direct3D_RenderTargetManager::Shutdown()
{
	RenderTargetListElt*	pxIterator;

	// Remove our render targets...
	for(pxIterator = s_xRenderTargetList.Front(); pxIterator != NULL; pxIterator = pxIterator->Next())
	{
		Asura_Direct3D_RenderTarget*	pxRenderTarget	= pxIterator->Data();

		delete(pxRenderTarget);
	}
	s_xRenderTargetList.Clear();

	// call all our callbacks
	Asura_ListElt<ResetCallback>* pxIt = s_xPreResetCallbacks.Front();
	while(pxIt)
	{
		(*pxIt->Data())();
		pxIt = pxIt->Next();
	}

	// wipe out our registered callbacks
	s_xPreResetCallbacks.Clear();
	s_xPostResetCallbacks.Clear();
}

void Asura_Direct3D_RenderTargetManager::PreD3DReset()
{
	RenderTargetListElt*	pxIterator;

	// wipe out all the D3D resources
	for(pxIterator = s_xRenderTargetList.Front(); pxIterator != NULL; pxIterator = pxIterator->Next())
	{
		Asura_Direct3D_RenderTarget*	pxRenderTarget	= pxIterator->Data();

		pxRenderTarget->ReleaseD3DResources();
	}

	// call all our callbacks
	Asura_ListElt<ResetCallback>* pxIt = s_xPreResetCallbacks.Front();
	while(pxIt)
	{
		(*pxIt->Data())();
		pxIt = pxIt->Next();
	}
}

void Asura_Direct3D_RenderTargetManager::PostD3DReset()
{
	RenderTargetListElt*	pxIterator;

	// recreate all the D3D resources
	for(pxIterator = s_xRenderTargetList.Front(); pxIterator != NULL; pxIterator = pxIterator->Next())
	{
		Asura_Direct3D_RenderTarget*	pxRenderTarget	= pxIterator->Data();

		pxRenderTarget->CreateD3DResources();
	}

	// call all our callbacks
	Asura_ListElt<ResetCallback>* pxIt = s_xPostResetCallbacks.Front();
	while(pxIt)
	{
		(*pxIt->Data())();
		pxIt = pxIt->Next();
	}
}

Asura_Direct3D_RenderTarget* Asura_Direct3D_RenderTargetManager::CreateRenderTarget(float fWidth, float fHeight, D3DFORMAT* peColourFmt, D3DFORMAT depthFmt, bool bBackbufferRelativeSize, u_int uRTCount, bool bUseExistingDepthStencilBuffer /* = false */)
{
	Asura_Direct3D_RenderTarget*	pxNewRenderTarget	= new Asura_Direct3D_RenderTarget;

	Asura_Assert( uRTCount < Asura_Direct3D_RenderTarget::s_uMAX_NUM_RENDER_TARGETS, "Code does not support this number of render targets");
	
	pxNewRenderTarget->m_fWidth = fWidth;
	pxNewRenderTarget->m_fHeight = fHeight;
//	pxNewRenderTarget->m_xColourFmt = *peColourFmt;
	for( u_int uRTIndex = 0; uRTIndex < uRTCount; uRTIndex += 1)
	{
		pxNewRenderTarget->m_axColourFmt[ uRTIndex] = peColourFmt[ uRTIndex];
	}
	pxNewRenderTarget->m_xDepthFmt = depthFmt;
	pxNewRenderTarget->m_bBackbufferRelativeSize = bBackbufferRelativeSize;
	pxNewRenderTarget->m_bIsInUse = true;
	pxNewRenderTarget->m_uNumRenderTargets = uRTCount;
	pxNewRenderTarget->m_bIsUsingExistingDepthStencilBuffer = bUseExistingDepthStencilBuffer;
			
	HRESULT hr = pxNewRenderTarget->CreateD3DResources();

	if(SUCCEEDED(hr))
	{
		s_xRenderTargetList.Push_Front(pxNewRenderTarget);
		return(pxNewRenderTarget);
	}
	else
	{
		Asura_Assertf(false, ("Render target (%d,%d) resource creation failed with %s", pxNewRenderTarget->m_uActualWidth, pxNewRenderTarget->m_uActualHeight, DXGetErrorString9(hr)));
		delete(pxNewRenderTarget);
		return(NULL);
	}
}

Asura_Direct3D_RenderTarget* Asura_Direct3D_RenderTargetManager::CreateRenderTarget(float fWidth, float fHeight, D3DFORMAT colourFmt, D3DFORMAT depthFmt, bool bBackbufferRelativeSize, u_int uRTCount /* = 1 */, bool bUseExistingDepthStencilBuffer /* = false */)
{
	Asura_Direct3D_RenderTarget*	pxNewRenderTarget	= new Asura_Direct3D_RenderTarget;

	Asura_Assert( uRTCount < Asura_Direct3D_RenderTarget::s_uMAX_NUM_RENDER_TARGETS, "Code does not support this number of render targets");

	pxNewRenderTarget->m_fWidth = fWidth;
	pxNewRenderTarget->m_fHeight = fHeight;
	//pxNewRenderTarget->m_xColourFmt = colourFmt;
	for( u_int uRTIndex = 0; uRTIndex < uRTCount; uRTIndex += 1)
	{
		pxNewRenderTarget->m_axColourFmt[ uRTIndex] = colourFmt;
	}
	pxNewRenderTarget->m_xDepthFmt = depthFmt;
	pxNewRenderTarget->m_bBackbufferRelativeSize = bBackbufferRelativeSize;
	pxNewRenderTarget->m_bIsInUse = true;
	pxNewRenderTarget->m_uNumRenderTargets = uRTCount;
	pxNewRenderTarget->m_bIsUsingExistingDepthStencilBuffer = bUseExistingDepthStencilBuffer;
			
	HRESULT hr = pxNewRenderTarget->CreateD3DResources();

	if(SUCCEEDED(hr))
	{
		s_xRenderTargetList.Push_Front(pxNewRenderTarget);
		return(pxNewRenderTarget);
	}
	else
	{
		Asura_Assertf(false, ("Render target (%d,%d) resource creation failed with %s", pxNewRenderTarget->m_uActualWidth, pxNewRenderTarget->m_uActualHeight, DXGetErrorString9(hr)));
		delete(pxNewRenderTarget);
		return(NULL);
	}
}

Asura_Direct3D_RenderTarget* Asura_Direct3D_RenderTargetManager::FindUnusedRenderTarget( const float fWidth, const float fHeight, const D3DFORMAT eBufferFormat, const D3DFORMAT eDepthFormat, const bool bBackbufferRelativeSize, u_int uRTCount /* = 1 */  , bool bAcceptLargerDimensions /* =true */)
{
	RenderTargetListElt*	pxIterator;

	const u_int uWidth = (true == bBackbufferRelativeSize) ? static_cast< u_int>( fWidth * Asura_Direct3D::GetPresentParameters().BackBufferWidth) : static_cast< u_int>( fWidth);
	const u_int uHeight = (true == bBackbufferRelativeSize) ? static_cast< u_int>( fHeight * Asura_Direct3D::GetPresentParameters().BackBufferHeight) : static_cast< u_int>( fHeight);
	
	for(pxIterator = s_xRenderTargetList.Front(); pxIterator != NULL; pxIterator = pxIterator->Next())
	{
		Asura_Direct3D_RenderTarget*	pxRenderTarget	= pxIterator->Data();
		
		// DLH - 17 July 2006 15:36:43 - The RT is presently in use by something, so ignore
		if( true == pxRenderTarget->m_bIsInUse)
		{
			continue;
		}
		
		if( uRTCount != pxRenderTarget->m_uNumRenderTargets)
		{
			continue;
		}
		
//		if( eBufferFormat != pxRenderTarget->m_xColourFmt)
//		{
//			continue;
//		}

		{
			bool bMatchingColourFmt = true;
			for( u_int uRTIndex = 0; uRTIndex < uRTCount; uRTIndex += 1)
			{
				if( pxRenderTarget->m_axColourFmt[ uRTIndex] != eBufferFormat)
				{
					bMatchingColourFmt = false;
					break;
				}
			}
			if( false == bMatchingColourFmt)
			{
				continue;
			}
		}
		
		if( eDepthFormat != pxRenderTarget->m_xDepthFmt)
		{
			continue;
		}

		if( pxRenderTarget->m_uActualWidth < uWidth || (pxRenderTarget->m_uActualWidth > uWidth && !bAcceptLargerDimensions))
		{
			continue;
		}
		
		if( pxRenderTarget->m_uActualHeight < uHeight  || (pxRenderTarget->m_uActualHeight > uHeight  && !bAcceptLargerDimensions))
		{
			continue;
		}
		
		// Mark it as used

		// DMR 25/01/07
		// Bugfix - wasn't actually flagging the RT as in use.
		pxRenderTarget->m_bIsInUse = true;

		return( pxRenderTarget );
	}
	
	//Asura_Assert(false, "Cannot find a Render target that is greater than the requested type!");
	//return( NULL);
	return( CreateRenderTarget( fWidth, fHeight, eBufferFormat, eDepthFormat, bBackbufferRelativeSize, uRTCount));
}




void Asura_Direct3D_RenderTargetManager::DestroyRenderTarget(Asura_Direct3D_RenderTarget* pxRenderTarget)
{
	if(!pxRenderTarget) return;

	if(s_xRenderTargetList.Remove(pxRenderTarget))
	{
		delete(pxRenderTarget);
	}
	else
	{
		Asura_Assert(false, "Failed to remove this render-target from the list. ");
	}
}

void Asura_Direct3D_RenderTargetManager::AddResetCallbacks(ResetCallback pfnPreReset, ResetCallback pfnPostReset)
{
	s_xPreResetCallbacks.Push_Back(pfnPreReset);
	s_xPostResetCallbacks.Push_Back(pfnPostReset);
}

void Asura_Direct3D_RenderTargetManager::RemoveResetCallbacks(ResetCallback pfnPreReset, ResetCallback pfnPostReset)
{
	Asura_ListElt<ResetCallback>* pxIt = s_xPreResetCallbacks.Find(pfnPreReset);
	if(pxIt) s_xPreResetCallbacks.Remove(pxIt);

	pxIt = s_xPostResetCallbacks.Find(pfnPreReset);
	if(pxIt) s_xPostResetCallbacks.Remove(pxIt);
}
