//-------------------------------------------------------------------------
//
// File:		DX9BufferC.cpp
// Desc:		DX9 off-screen graphics rendering interface.
// Author:		memon <memon@inside.org>
//
//-------------------------------------------------------------------------
//	Copyright (c) 2004 Moppi Productions. All Rights Reserved.
//  This file is part of Moppi Demopaja SDK. For conditions of 
//  distribution and use, see the accompanying license.txt file.
//  http://moppi.inside.org/demopaja/
//-------------------------------------------------------------------------

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <d3d9.h>
#include <dxerr9.h>
#include "DX9BufferC.h"
#include "PajaTypes.h"
#include "DeviceInterfaceI.h"
#include "DataBlockI.h"
#include "ClassIdC.h"
#include "GraphicsDeviceI.h"
#include "DX9ViewportC.h"
#include "DX9DeviceC.h"

using namespace PajaTypes;
using namespace PajaSystem;
using namespace PluginClass;
using namespace Edit;


static
void
Trace( const char* szFormat, ...  )
{
	char	szMsg[256];
	va_list	rList;
	va_start( rList, szFormat );
	_vsnprintf( szMsg, 255, szFormat, rList );
	va_end( rList );
	OutputDebugString( szMsg );
}

#define				TRACE					Trace( "%s(%i) : ", __FILE__, __LINE__ ), Trace


DX9BufferC::DX9BufferC() :
	m_pParentDev( 0 ),
	m_pBufferTex( 0 ),
	m_pBufferSurf( 0 ),
	m_pBufferZ( 0 ),
	m_pBufferExtraTex( 0 ),
	m_pBufferExtraSurf( 0 ),
	m_pViewport( 0 ),
	m_pD3DDevice( 0 ),
	m_pStateBlockSaved( 0 ),
	m_dwClearFlagMask( 0 ),
	m_dwLastBindStage( 0 )
{
	m_ui32State = DEVICE_STATE_LOST;
}

DX9BufferC::DX9BufferC( GraphicsDeviceI* pDevice ) :
	m_pParentDev( (DX9DeviceC*)pDevice ),
	m_pBufferTex( 0 ),
	m_pBufferSurf( 0 ),
	m_pBufferZ( 0 ),
	m_pBufferExtraTex( 0 ),
	m_pBufferExtraSurf( 0 ),
	m_pD3DDevice( 0 ),
	m_pStateBlockSaved( 0 ),
	m_dwClearFlagMask( 0 ),
	m_dwLastBindStage( 0 )
{
	m_pViewport = DX9ViewportC::create_new( m_pParentDev );
	m_ui32State = DEVICE_STATE_LOST;
}

DX9BufferC::~DX9BufferC()
{
	if( m_pParentDev )
		m_pParentDev->unregister_buffer( this );

	if( m_pBufferSurf )
		m_pBufferSurf->Release();
	if( m_pBufferTex ) 
		m_pBufferTex->Release();

	if( m_pBufferExtraSurf )
		m_pBufferExtraSurf->Release();
	if( m_pBufferExtraTex ) 
		m_pBufferExtraTex->Release();

	if( m_pBufferZ )
		m_pBufferZ->Release();

	if( m_pStateBlockSaved )
		m_pStateBlockSaved->Release();

	if( m_pViewport )
		m_pViewport->release();
}

DataBlockI*
DX9BufferC::create()
{
	return new DX9BufferC( m_pParentDev );
}

DX9BufferC*
DX9BufferC::create_new( GraphicsDeviceI* pDevice )
{
	return new DX9BufferC( pDevice );
}

PluginClass::ClassIdC
DX9BufferC::get_class_id() const
{
	return CLASS_DX9_BUFFER;
}

const char*
DX9BufferC::get_class_name()
{
	return "DX9Buffer";
}

DeviceInterfaceI*
DX9BufferC::query_interface( const PluginClass::SuperClassIdC& rSuperClassId )
{
	if( rSuperClassId == GRAPHICSDEVICE_VIEWPORT_INTERFACE ) {
		return m_pViewport;
	}
	return 0;
}

void
DX9BufferC::set_graphicsdevice( GraphicsDeviceI* pDevice )
{
	m_pParentDev = (DX9DeviceC*)pDevice;
	if( m_pParentDev )
	{
		m_pD3DDevice = m_pParentDev->get_d3ddevice();
		if( !m_pViewport )
			m_pViewport = DX9ViewportC::create_new( m_pParentDev );
	}
}

bool
DX9BufferC::init( uint32 ui32Flags, uint32 ui32Width, uint32 ui32Height )
{
	if( !m_pParentDev )
		return false;

	m_pD3DDevice = m_pParentDev->get_d3ddevice();

	if( m_pBufferSurf )
		m_pBufferSurf->Release();
	if( m_pBufferTex )
		m_pBufferTex->Release();

	if( m_pBufferExtraSurf )
		m_pBufferExtraSurf->Release();
	if( m_pBufferExtraTex )
		m_pBufferExtraTex->Release();

	if( m_pBufferZ )
		m_pBufferZ->Release();

	if( m_pStateBlockSaved )
		m_pStateBlockSaved->Release();

	m_pBufferSurf = 0;
	m_pBufferTex = 0;
	m_pBufferZ = 0;
	m_pBufferExtraSurf = 0;
	m_pBufferExtraTex = 0;
	m_pStateBlockSaved = 0;

	m_ui32InitFlags = ui32Flags;

	TRACE( "%d x %d\n", ui32Width, ui32Height );

	m_dwClearFlagMask = 0; //D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER;

	if( FAILED( m_pD3DDevice->CreateTexture( ui32Width, ui32Height, 1, 
		D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &m_pBufferTex, NULL ) ) )
	{
		TRACE( "could not create buffertex\n" );
		return false;
	}

	if( FAILED( m_pBufferTex->GetSurfaceLevel( 0, &m_pBufferSurf ) ) )
	{
		TRACE( "could not get tex surface\n" );
		return false;
	}

	m_dwClearFlagMask = D3DCLEAR_TARGET;

	m_ui32InitFlags |= GRAPHICSBUFFER_INIT_EXTRA;
//	if( m_ui32InitFlags & GRAPHICSBUFFER_INIT_EXTRA )
	{
		// Create shadow map texture and retrieve surface
		if( FAILED( m_pD3DDevice->CreateTexture( ui32Width, ui32Height, 1, 
			D3DUSAGE_RENDERTARGET, D3DFMT_G16R16F, D3DPOOL_DEFAULT, &m_pBufferExtraTex, NULL ) ) )
		{
			TRACE( "could not create Extra buffertex\n" );
			return false;
		}

		if( FAILED( m_pBufferExtraTex->GetSurfaceLevel( 0, &m_pBufferExtraSurf ) ) )
		{
			TRACE( "could not get tex Extra surface\n" );
			return false;
		}

		m_dwClearFlagMask |= D3DCLEAR_TARGET;
	}


	// DEBUG!!! force depth buffer.
//	if( m_ui32InitFlags & GRAPHICSBUFFER_INIT_DEPTH )
	m_ui32InitFlags |= GRAPHICSBUFFER_INIT_DEPTH;
	{
		// Create depth buffer for shadow map rendering
		if( FAILED( m_pD3DDevice->CreateDepthStencilSurface( ui32Width, ui32Height, 
			D3DFMT_D24S8, D3DMULTISAMPLE_NONE, 0, TRUE, &m_pBufferZ, NULL ) ) )
		{
			TRACE( "could not create depth stencil\n" );
			return false;
		}

		m_dwClearFlagMask |= D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL;
	}


	m_pViewport->set_dimension( 0, 0, ui32Width, ui32Height );

	// Create state block
	m_pD3DDevice->CreateStateBlock( D3DSBT_ALL, &m_pStateBlockSaved );

	m_rTexBounds[0][0] = 0;
	m_rTexBounds[0][1] = 0;
	m_rTexBounds[1][0] = 1;
	m_rTexBounds[1][1] = 1;

	m_ui32State = DEVICE_STATE_OK;

	return true;
}

void
DX9BufferC::begin_draw()
{
	if( m_pStateBlockSaved )
		m_pStateBlockSaved->Capture();
}

void
DX9BufferC::end_draw()
{
	if( m_pStateBlockSaved )
		m_pStateBlockSaved->Apply();
}

void
DX9BufferC::flush()
{
	// Empty
}

BBox2C&
DX9BufferC::get_tex_coord_bounds()
{
	return m_rTexBounds;
}

void
DX9BufferC::bind_texture( PajaSystem::DeviceInterfaceI* pInterface, uint32 ui32Stage, uint32 ui32Properties )
{
	if( ui32Stage & 0x8000 )
	{
		ui32Stage &= 0xfff;

		if( m_pD3DDevice && m_pBufferExtraTex )
		{
	//		TRACE( "bind\n" );
			if( FAILED( m_pD3DDevice->SetTexture( ui32Stage, m_pBufferExtraTex ) ) )
				TRACE( "settexture Extra failed\n" );
		}
		else
		{
			TRACE( "no device or no Extra buffer\n" );
		}
	}
	else
	{
		if( m_pD3DDevice && m_pBufferTex )
		{
	//		TRACE( "bind\n" );
			if( FAILED( m_pD3DDevice->SetTexture( ui32Stage, m_pBufferTex ) ) )
				TRACE( "settexture failed\n" );
		}
		else
		{
			TRACE( "no device or no buffer\n" );
		}
	}

	m_dwLastBindStage = (DWORD)ui32Stage;
}

void
DX9BufferC::read_pixels( uint32 ui32Flags, void* pData )
{
	// Empty
}

void
DX9BufferC::activate()
{
	if( !m_pD3DDevice )
		return;

	if( !m_pBufferSurf )
	{
		TRACE( "no surface\n" );
		return;
	}
	if( (m_ui32InitFlags & GRAPHICSBUFFER_INIT_DEPTH) && !m_pBufferZ )
	{
		TRACE( "no zbuffer\n" );
		return;
	}

	// Make sure that the texture of this buffer is not still bind.
	m_pD3DDevice->SetTexture( m_dwLastBindStage, 0 );


//	TRACE( "activate %dx%d\n",m_pViewport->get_width(), m_pViewport->get_height() );

	// Set new render target
//	TRACE( " - color\n" );
	HRESULT	hRes;
	if( FAILED( hRes = m_pD3DDevice->SetRenderTarget( 0, m_pBufferSurf ) ) )
	{
		TRACE( "set render target failed: %s\n", DXGetErrorString9( hRes ) );
		return;
	}

//	TRACE( " - extra\n" );
	if( m_ui32InitFlags & GRAPHICSBUFFER_INIT_EXTRA )
	{
		if( FAILED( m_pD3DDevice->SetRenderTarget( 1, m_pBufferExtraSurf ) ) )
		{
			TRACE( "set render target extra failed: %s\n", DXGetErrorString9( hRes ) );
			return;
		}
	}

//	TRACE( " - depth\n" );
	if( m_ui32InitFlags & GRAPHICSBUFFER_INIT_DEPTH )
	{
		if( FAILED( m_pD3DDevice->SetDepthStencilSurface( m_pBufferZ ) ) )
		{
			TRACE( "set depth stencil failed\n" );
			return;
		}
	}

//	TRACE( " - viewport\n" );

	D3DVIEWPORT9	rDXViewport;
	rDXViewport.X = 0;
	rDXViewport.Y = 0;
	rDXViewport.Width = m_pViewport->get_width();
	rDXViewport.Height = m_pViewport->get_height();
	rDXViewport.MinZ = 0.0f;
	rDXViewport.MaxZ = 1.0f;
	if( FAILED( m_pD3DDevice->SetViewport( &rDXViewport ) ) )
		TRACE( "set viewport failed\n ");

	// Reset matrices.
	m_pViewport->activate();

//	TRACE( " - done!\n" );
}

void
DX9BufferC::init_invalidate()
{
	{
		uint32	ui32Width = m_pViewport->get_width();
		uint32	ui32Height = m_pViewport->get_height();
		TRACE( "invalidate %dx%d\n", ui32Width, ui32Height );
	}

	// Make sure that the texture of this buffer is not still bind.
//	m_pD3DDevice->SetTexture( m_dwLastBindStage, 0 );

	if( m_pStateBlockSaved )
		m_pStateBlockSaved->Release();
	m_pStateBlockSaved = 0;

	if( m_pBufferSurf )
		m_pBufferSurf->Release();
	if( m_pBufferTex )
		m_pBufferTex->Release();

	if( m_pBufferExtraSurf )
		m_pBufferExtraSurf->Release();
	if( m_pBufferExtraTex )
		m_pBufferExtraTex->Release();

	if( m_pBufferZ )
		m_pBufferZ->Release();

	m_pBufferSurf = 0;
	m_pBufferTex = 0;
	m_pBufferExtraSurf = 0;
	m_pBufferExtraTex = 0;
	m_pBufferZ = 0;

	m_ui32State = DEVICE_STATE_LOST;
}

void
DX9BufferC::init_validate()
{
	if( !m_pD3DDevice )
		return;

	if( !m_pStateBlockSaved )
	{
		HRESULT	hr;
		if( FAILED( hr = m_pD3DDevice->CreateStateBlock( D3DSBT_ALL, &m_pStateBlockSaved ) ) )
			TRACE( "CreateStateBlock failed\n ");
	}

	if( m_pBufferTex || m_pBufferTex || m_pBufferExtraTex || m_pBufferExtraTex || m_pBufferZ )
		return;

	uint32	ui32Width = m_pViewport->get_width();
	uint32	ui32Height = m_pViewport->get_height();

	TRACE( "validate: %d x %d\n", ui32Width, ui32Height );

	if( ui32Width > 0 && ui32Height > 0 )
	{
		HRESULT	hRes;
		// Create shadow map texture and retrieve surface
		if( FAILED( hRes = m_pD3DDevice->CreateTexture( ui32Width, ui32Height, 1, 
			D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &m_pBufferTex, NULL ) ) )
		{
			TRACE( "create texture failed: %s\n", DXGetErrorString9( hRes ) );
			return;
		}

		if( FAILED( hRes = m_pBufferTex->GetSurfaceLevel( 0, &m_pBufferSurf ) ) )
		{
			TRACE( "get surface failed: %s\n", DXGetErrorString9( hRes ) );
			return;
		}

		if( m_ui32InitFlags & GRAPHICSBUFFER_INIT_EXTRA )
		{
			// Create shadow map texture and retrieve surface
			if( FAILED( m_pD3DDevice->CreateTexture( ui32Width, ui32Height, 1, 
				D3DUSAGE_RENDERTARGET, D3DFMT_G16R16F, D3DPOOL_DEFAULT, &m_pBufferExtraTex, NULL ) ) )
			{
				TRACE( "could not create Extra buffertex\n" );
				return;
			}

			if( FAILED( m_pBufferExtraTex->GetSurfaceLevel( 0, &m_pBufferExtraSurf ) ) )
			{
				TRACE( "could not get tex Extra surface\n" );
				return;
			}
		}

		if( m_ui32InitFlags & GRAPHICSBUFFER_INIT_DEPTH )
		{
			// Create depth buffer for shadow map rendering
			if( FAILED( hRes = m_pD3DDevice->CreateDepthStencilSurface( ui32Width, ui32Height, 
				D3DFMT_D24S8, D3DMULTISAMPLE_NONE, 0, TRUE, &m_pBufferZ, NULL ) ) )
			{
				TRACE( "create depthstencil failed: %s\n", DXGetErrorString9( hRes ) );
				return;
			}
		}
	}

	m_ui32State = DEVICE_STATE_OK;
}

uint32
DX9BufferC::get_flags()
{
	return m_ui32InitFlags;
}

LPDIRECT3DTEXTURE9
DX9BufferC::get_texture()
{
	return m_pBufferTex;
}

LPDIRECT3DTEXTURE9
DX9BufferC::get_extra_texture()
{
	return m_pBufferExtraTex;
}

DWORD
DX9BufferC::get_clear_flag_mask()
{
	return m_dwClearFlagMask;
}