#include "../../Direct3D/Engine/RuEngineD3D.h"

#pragma unmanaged

// ************************************************************************************************************************************************************

ruRTTI_DEFINE_SUBCLASS(CRuVertexDeclarationD3D, IRuVertexDeclaration)
ruHEAP_DEFINE_SUBCLASS(CRuVertexDeclarationD3D, "CRuVertexDeclarationD3D", "IRuVertexDeclaration")
ruCLASSGUID_DEFINE(CRuVertexDeclarationD3D, 0x7F1172C4, 0x06A043b8, 0x8C0EFCE5, 0x31F150F0)

ruRTTI_DEFINE_SUBCLASS(CRuVertexBufferD3D, IRuVertexBuffer)
ruHEAP_DEFINE_SUBCLASS(CRuVertexBufferD3D, "CRuVertexBufferD3D", "IRuVertexBuffer")
ruCLASSGUID_DEFINE(CRuVertexBufferD3D, 0xC5971A69, 0x12AA4c66, 0xBE304EA4, 0x7D0D3C60)

ruRTTI_DEFINE_SUBCLASS(CRuIndexBufferD3D, IRuIndexBuffer)
ruHEAP_DEFINE_SUBCLASS(CRuIndexBufferD3D, "CRuIndexBufferD3D", "IRuIndexBuffer")
ruCLASSGUID_DEFINE(CRuIndexBufferD3D, 0x59C671F0, 0x31194b38, 0xB582F7B1, 0xCCC18DAA)

ruRTTI_DEFINE_SUBCLASS(CRuSurfaceD3D, IRuSurface)
ruHEAP_DEFINE_SUBCLASS(CRuSurfaceD3D, "CRuSurfaceD3D", "IRuSurface")
ruCLASSGUID_DEFINE(CRuSurfaceD3D, 0xFD8C5893, 0xF4384506, 0xA0BF500C, 0xFB76B226)

ruRTTI_DEFINE_SUBCLASS(CRuTextureD3D, IRuTexture)
ruHEAP_DEFINE_SUBCLASS(CRuTextureD3D, "CRuTextureD3D", "IRuTexture")
ruCLASSGUID_DEFINE(CRuTextureD3D, 0x3DCCB679, 0xD5A34a14, 0x9B3B2221, 0x3D5E0D77)

ruRTTI_DEFINE_SUBCLASS(CRuVolumeTextureD3D, IRuVolumeTexture)
ruHEAP_DEFINE_SUBCLASS(CRuVolumeTextureD3D, "CRuVolumeTextureD3D", "IRuVolumeTexture")
ruCLASSGUID_DEFINE(CRuVolumeTextureD3D, 0xBAE288B6, 0xF7FD4e93, 0x95A209DE, 0xD123CBE8)

ruRTTI_DEFINE_SUBCLASS(CRuSwapChainD3D, IRuSwapChain)
ruHEAP_DEFINE_SUBCLASS(CRuSwapChainD3D, "CRuSwapChainD3D", "IRuSwapChain")
ruCLASSGUID_DEFINE(CRuSwapChainD3D, 0xEAED0EFB, 0xFA524c9c, 0xBD03FFFE, 0x90B9D875)

ruRTTI_DEFINE_SUBCLASS(CRuEngineD3D, IRuEngine)
ruHEAP_DEFINE_SUBCLASS(CRuEngineD3D, "CRuEngineD3D", "IRuEngine")
ruCLASSGUID_DEFINE(CRuEngineD3D, 0xBF583062, 0x346C4504, 0x8778DFFC, 0x79C8102F)

// ************************************************************************************************************************************************************

CRuVertexDeclarationD3D::CRuVertexDeclarationD3D(IDirect3DVertexDeclaration9 *vertexDeclaration, const CRuVertexElement *vertexElements)
:	m_vertexDeclaration(vertexDeclaration)
{
	if(m_vertexDeclaration)
	{
		// Copy the vertex elements
		UINT32 numElements = GetNumVertexElements(vertexElements);
		m_vertexElements = ruNEW CRuVertexElement [numElements];
		memcpy(m_vertexElements, vertexElements, numElements * sizeof(CRuVertexElement));

		// Increment reference count on the declaration interface
		m_vertexDeclaration->AddRef();

		// Store a handle to the parent device
		m_vertexDeclaration->GetDevice(&m_parentDevice);
		m_parentDevice->Release();

		// Determine number of vertex streams
		for(UINT32 i = 0; i < numElements; ++i)
		{
			if(!m_vertexStreams.Contains(m_vertexElements[i].m_stream))
			{
				m_vertexStreams.Add(StreamDesc(m_vertexElements[i].m_stream));
			}
		}

		// Determine stream stride for each vertex stream
		for(INT32 i = 0; i < m_vertexStreams.Count(); ++i)
		{
			for(UINT32 j = 0; j < numElements; ++j)
			{
				if(m_vertexStreams[i].m_streamIndex == m_vertexElements[j].m_stream)
				{
					m_vertexStreams[i].m_streamStride = m_vertexElements[j].m_offset + RuGetVertexElementSize(static_cast<RuVertexElementType>(m_vertexElements[j].m_type));
				}
			}
		}
	}
}

CRuVertexDeclarationD3D::~CRuVertexDeclarationD3D()
{
	if(m_vertexDeclaration)
	{
		m_vertexDeclaration->Release();
	}

	delete[] m_vertexElements;
}

void CRuVertexDeclarationD3D::Invalidate()
{
}

BOOL CRuVertexDeclarationD3D::IsInvalidated() const
{
	return FALSE;
}

BOOL CRuVertexDeclarationD3D::Restore()
{
	return TRUE;
}

const CRuVertexElement *CRuVertexDeclarationD3D::GetVertexElements() const
{
	return m_vertexElements;
}

void CRuVertexDeclarationD3D::Select() const
{
	if(m_vertexDeclaration)
	{
		// Set the vertex declaration through the engine
		static_cast<CRuEngineD3D *>(m_engine)->SetVertexDeclaration(m_vertexDeclaration);
	}
}

INT32 CRuVertexDeclarationD3D::GetNumStreams() const
{
	return m_vertexStreams.Count();
}

INT32 CRuVertexDeclarationD3D::GetStreamStride(INT32 streamIndex) const
{
	return m_vertexStreams[streamIndex].m_streamStride;
}

// ************************************************************************************************************************************************************

CRuVertexBufferD3D::CRuVertexBufferD3D(IDirect3DVertexBuffer9 *vertexBuffer, BOOL isDynamic)
: 	m_parentDevice(NULL),
	m_vertexBuffer(vertexBuffer),
	m_isDynamic(isDynamic),
	m_offset(0),
	m_size(0),
	m_shared(FALSE),
	m_chainedVB(NULL)
{
	if(m_vertexBuffer)
	{
		D3DVERTEXBUFFER_DESC desc;
		m_vertexBuffer->GetDesc(&desc);
		m_size = desc.Size;

		m_vertexBuffer->AddRef();

		// Store a handle to the parent device
		m_vertexBuffer->GetDevice(&m_parentDevice);
		m_parentDevice->Release();
	}
}

CRuVertexBufferD3D::CRuVertexBufferD3D(IDirect3DVertexBuffer9 *vertexBuffer, UINT32 offset, UINT32 size, BOOL shared, BOOL isDynamic)
: 	m_parentDevice(NULL),
	m_vertexBuffer(vertexBuffer),
	m_isDynamic(isDynamic),
	m_offset(offset),
	m_size(size),
	m_shared(shared),
	m_chainedVB(NULL)
{
	if(m_vertexBuffer)
	{
		m_vertexBuffer->AddRef();

		// Store a handle to the parent device
		m_vertexBuffer->GetDevice(&m_parentDevice);
		m_parentDevice->Release();
	}
}

CRuVertexBufferD3D::~CRuVertexBufferD3D()
{
	if(m_vertexBuffer != NULL)
	{
		m_vertexBuffer->Release();
		m_vertexBuffer = NULL;
	}

	FreeVBChain(this);
}

void *CRuVertexBufferD3D::Lock(UINT offset, UINT size)
{
	if(m_vertexBuffer != NULL)
	{
		void *data;

		// If lock size is zero, set it to equal the vertex buffer size
		if(size == 0)
			size = m_size;

		if(m_isDynamic)
		{
			if(FAILED(m_vertexBuffer->Lock(m_offset + offset, size, (void **) &data, D3DLOCK_DISCARD)))
				return NULL;
		}
		else
		{
			if(FAILED(m_vertexBuffer->Lock(m_offset + offset, size, (void **) &data, 0)))
				return NULL;
		}

		return data;
	}

	return NULL;
}

void *CRuVertexBufferD3D::Lock(UINT offset, UINT size, DWORD lockFlags)
{
	if(m_vertexBuffer != NULL)
	{
		// Setup D3D lock flags, but ignore the DISCARD flag if the vertex buffer is sharing the D3D VB with other objects!
		DWORD d3dLockFlags = 0;
		if(lockFlags & ruLOCK_READONLY)
			d3dLockFlags |= D3DLOCK_READONLY;
		if(lockFlags & ruLOCK_DISCARD && !m_shared)
			d3dLockFlags |= D3DLOCK_DISCARD;
		if(lockFlags & ruLOCK_NOOVERWRITE)
			d3dLockFlags |= D3DLOCK_NOOVERWRITE;

		void *data;

		// If lock size is zero, set it to equal the vertex buffer size
		if(size == 0)
			size = m_size;

		if(FAILED(m_vertexBuffer->Lock(m_offset + offset, size, (void **) &data, d3dLockFlags)))
			return NULL;

		return data;
	}

	return NULL;
}

void CRuVertexBufferD3D::Unlock()
{
	if(m_vertexBuffer != NULL)
		m_vertexBuffer->Unlock();
}

BOOL CRuVertexBufferD3D::Subdivide(INT32 numChildVBs, IRuVertexBuffer **childVBOut)
{
	if(childVBOut)
	{
		// Free any existing VB chains
		FreeVBChain(this);

		CRuVertexBufferD3D *nextChainHead = this;
		INT32 childVBSize = m_size / numChildVBs;

		for(INT32 i = 0; i < numChildVBs; ++i)
		{
			// Create child VB
			childVBOut[i] = ruNEW CRuVertexBufferD3D(m_vertexBuffer, m_offset + i * childVBSize, childVBSize, TRUE, m_isDynamic);
			childVBOut[i]->SetEngine(m_engine);

			// Link the child VB to the VB chain
			nextChainHead->m_chainedVB = static_cast<CRuVertexBufferD3D *>(childVBOut[i]);
			nextChainHead = static_cast<CRuVertexBufferD3D *>(childVBOut[i]);

			// Increment reference on the child VB
			childVBOut[i]->AddRef();
		}

		return TRUE;
	}

	return FALSE;
}

void CRuVertexBufferD3D::FreeVBChain(CRuVertexBufferD3D *currentVB)
{
	if(currentVB->m_chainedVB)
	{
		// Recursively invoke on next element in chain
		FreeVBChain(currentVB->m_chainedVB);

		// Release reference to the chained VB
		currentVB->m_chainedVB->Release();

		currentVB->m_chainedVB = NULL;
	}
}

// ************************************************************************************************************************************************************

CRuIndexBufferD3D::CRuIndexBufferD3D(IDirect3DIndexBuffer9 *indexBuffer, BOOL isDynamic)
:	m_parentDevice(NULL),
	m_indexBuffer(indexBuffer),
	m_isDynamic(isDynamic),
	m_offset(0),
	m_size(0),
	m_shared(FALSE)
{
	if(m_indexBuffer)
	{
		D3DINDEXBUFFER_DESC desc;
		m_indexBuffer->GetDesc(&desc);
		m_size = desc.Size;
		m_indexBuffer->AddRef();

		// Store a handle to the parent device
		m_indexBuffer->GetDevice(&m_parentDevice);
		m_parentDevice->Release();
	}
}

CRuIndexBufferD3D::CRuIndexBufferD3D(IDirect3DIndexBuffer9 *indexBuffer, UINT32 offset, UINT32 size, BOOL shared)
:	m_parentDevice(NULL),
	m_indexBuffer(indexBuffer),
	m_isDynamic(FALSE),
	m_offset(offset),
	m_size(size),
	m_shared(shared)
{
	if(m_indexBuffer)
	{
		m_indexBuffer->AddRef();

		// Store a handle to the parent device
		m_indexBuffer->GetDevice(&m_parentDevice);
		m_parentDevice->Release();
	}
}

CRuIndexBufferD3D::~CRuIndexBufferD3D()
{
	if(m_indexBuffer != NULL)
	{
		m_indexBuffer->Release();
		m_indexBuffer = NULL;
	}
}

void *CRuIndexBufferD3D::Lock(UINT offset, UINT size)
{
	if(m_indexBuffer != NULL)
	{
		void *data;

		// If lock size is zero, set it to equal the index buffer size
		if(size == 0)
			size = m_size;

		if(m_isDynamic)
		{
			if(FAILED(m_indexBuffer->Lock(m_offset + offset, size, (void **) &data, D3DLOCK_DISCARD)))
				return NULL;
		}
		else
		{
			if(FAILED(m_indexBuffer->Lock(m_offset + offset, size, (void **) &data, 0)))
				return NULL;
		}

		return data;
	}

	return NULL;
}

void *CRuIndexBufferD3D::Lock(UINT offset, UINT size, DWORD lockFlags)
{
	if(m_indexBuffer != NULL)
	{
		// Setup D3D lock flags, but ignore the DISCARD flag if the index buffer is sharing the D3D IB with other objects!
		DWORD d3dLockFlags = 0;
		if(lockFlags & ruLOCK_READONLY)
			d3dLockFlags |= D3DLOCK_READONLY;
		if(lockFlags & ruLOCK_DISCARD && !m_shared)
			d3dLockFlags |= D3DLOCK_DISCARD;
		if(lockFlags & ruLOCK_NOOVERWRITE)
			d3dLockFlags |= D3DLOCK_NOOVERWRITE;

		void *data;

		// If lock size is zero, set it to equal the index buffer size
		if(size == 0)
			size = m_size;

		if(FAILED(m_indexBuffer->Lock(m_offset + offset, size, (void **) &data, d3dLockFlags)))
			return NULL;

		return data;
	}

	return NULL;
}

void CRuIndexBufferD3D::Unlock()
{
	if(m_indexBuffer != NULL)
		m_indexBuffer->Unlock();
}

// ************************************************************************************************************************************************************

CRuSurfaceD3D::CRuSurfaceD3D(IDirect3DSurface9 *surface)
:	m_parentDevice(NULL),
	m_surface(NULL),
	m_resourcePool(ruRESPOOL_DEFAULT),
	m_surfaceFormat(ruSURFACEFMT_A8R8G8B8)
{
	if(surface)
	{
		m_surface = surface;

		// Get the surface description
		m_surface->GetDesc(&m_surfaceDesc);

		m_resourcePool = ToRuResourcePool(m_surfaceDesc.Pool);
		m_surfaceFormat = ToRuSurfaceFormat(m_surfaceDesc.Format);

		// Store a handle to the parent device
		m_surface->GetDevice(&m_parentDevice);
		m_parentDevice->Release();
	}
}

CRuSurfaceD3D::~CRuSurfaceD3D()
{
	if(m_surface)
	{
		m_surface->Release();
	}
}

void CRuSurfaceD3D::Invalidate()
{
	if(m_surface)
	{
		m_surface->Release();
		m_surface = NULL;
	}
}

BOOL CRuSurfaceD3D::IsInvalidated() const
{
	return (m_surface == NULL);
}

BOOL CRuSurfaceD3D::Restore()
{
	if(m_parentDevice && m_surface == NULL && m_resourcePool == ruRESPOOL_DEFAULT)
	{
		if(m_surfaceDesc.Usage == D3DUSAGE_DEPTHSTENCIL)
		{
			// Create depth stencil surface
			HRESULT hr = m_parentDevice->CreateDepthStencilSurface(m_surfaceDesc.Width, m_surfaceDesc.Height, m_surfaceDesc.Format, m_surfaceDesc.MultiSampleType, m_surfaceDesc.MultiSampleQuality, TRUE, &m_surface, NULL);
			if(FAILED(hr))
				return FALSE;

			return TRUE;
		}
	}

	return FALSE;
}

RuResourcePool CRuSurfaceD3D::GetPool() const
{
	return m_resourcePool;
}

RuSurfaceFormat CRuSurfaceD3D::GetSurfaceFormat() const
{
	return m_surfaceFormat;
}

UINT32 CRuSurfaceD3D::GetWidth(UINT32 level) const
{
	return m_surfaceDesc.Width;
}

UINT32 CRuSurfaceD3D::GetHeight(UINT32 level) const
{
	return m_surfaceDesc.Height;
}

BOOL CRuSurfaceD3D::Lock(void **bitsOut, INT32 *pitchOut)
{
	if(m_surface)
	{
		D3DLOCKED_RECT lockedRect;
		if(m_surface->LockRect(&lockedRect, NULL, 0) == D3D_OK)
		{
			*bitsOut = lockedRect.pBits;
			*pitchOut = lockedRect.Pitch;

			return TRUE;
		}
	}

	return FALSE;
}

BOOL CRuSurfaceD3D::Unlock()
{
	if(m_surface)
	{
		if(m_surface->UnlockRect() == D3D_OK)
		{
			return TRUE;
		}
	}

	return FALSE;
}

BOOL CRuSurfaceD3D::SelectRenderTarget()
{
	if(m_parentDevice)
	{
		if(m_parentDevice->SetRenderTarget(0, m_surface) == D3D_OK)
		{
			return TRUE;
		}
	}

	return FALSE;
}

BOOL CRuSurfaceD3D::SelectDepthStencil()
{
	if(m_parentDevice)
	{
		if(m_parentDevice->SetDepthStencilSurface(m_surface) == D3D_OK)
		{
			return TRUE;
		}
	}

	return FALSE;
}

// ************************************************************************************************************************************************************

CRuTextureD3D::CRuTextureD3D(IDirect3DTexture9 *texture, BOOL isDynamic, BOOL isMatchedToBackBuffer)
:	m_parentDevice(NULL),
	m_texture(NULL),
	m_levelCount(0),
	m_surfaces(NULL),
	m_textureName(NULL),
	m_isDynamic(FALSE),
	m_isRenderTarget(FALSE),
	m_isMatchedToBackBuffer(FALSE),
	m_resourcePool(ruRESPOOL_DEFAULT),
	m_surfaceFormat(ruSURFACEFMT_A8R8G8B8)
{
	if(texture)
	{
		m_texture = texture;
		m_isDynamic = isDynamic;
		m_isMatchedToBackBuffer = isMatchedToBackBuffer;

		// Get the surface description
		m_levelCount = m_texture->GetLevelCount();
		m_texture->GetLevelDesc(0, &m_surfaceDesc);

		if(m_surfaceDesc.Usage & D3DUSAGE_RENDERTARGET)
			m_isRenderTarget = TRUE;

		m_resourcePool = ToRuResourcePool(m_surfaceDesc.Pool);
		m_surfaceFormat = ToRuSurfaceFormat(m_surfaceDesc.Format);

		// Allocate surfaces
		m_surfaces = ruNEW CRuSurfaceD3D * [m_levelCount];
		for(UINT32 i = 0; i < m_levelCount; ++i)
		{
			IDirect3DSurface9 *d3dSurface = NULL;
			if(m_texture->GetSurfaceLevel(0, &d3dSurface) == D3D_OK)
			{
				m_surfaces[i] = ruNEW CRuSurfaceD3D(d3dSurface);
			}
		}

		// Store a handle to the parent device
		m_texture->GetDevice(&m_parentDevice);
		m_parentDevice->Release();
	}
}

CRuTextureD3D::~CRuTextureD3D()
{
	if(m_texture)
	{
		m_texture->Release();
	}

	if(m_textureName)
	{
		delete[] m_textureName;
	}

	// Release surfaces
	if(m_surfaces)
	{
		for(UINT32 i = 0; i < m_levelCount; ++i)
		{
			if(m_surfaces[i])
			{
				m_surfaces[i]->Release();
			}
		}

		delete[] m_surfaces;
	}
}

void CRuTextureD3D::Invalidate()
{
	if(m_texture && m_resourcePool == ruRESPOOL_DEFAULT)
	{
		// Release surfaces
		if(m_surfaces)
		{
			for(UINT32 i = 0; i < m_levelCount; ++i)
			{
				if(m_surfaces[i])
				{
					m_surfaces[i]->Release();
				}
			}

			delete[] m_surfaces;
			m_surfaces = NULL;
		}

		// Release texture
		m_texture->Release();
		m_texture = NULL;
	}
}

BOOL CRuTextureD3D::IsInvalidated() const
{
	return m_texture == NULL;
}

BOOL CRuTextureD3D::Restore()
{
	if(m_parentDevice && m_texture == NULL && m_resourcePool == ruRESPOOL_DEFAULT)
	{
		// If this texture is always matched to the backbuffer, adjust its dimensions
		if(m_isMatchedToBackBuffer)
		{
			IDirect3DSurface9 *backBuffer;
			if(m_parentDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backBuffer) == D3D_OK)
			{
				D3DSURFACE_DESC bbDesc;
				backBuffer->GetDesc(&bbDesc);

				m_surfaceDesc.Width = bbDesc.Width;
				m_surfaceDesc.Height = bbDesc.Height;

				backBuffer->Release();
			}
		}

		if(m_parentDevice->CreateTexture(m_surfaceDesc.Width, m_surfaceDesc.Height, m_levelCount, m_isRenderTarget ? D3DUSAGE_RENDERTARGET : 0, m_surfaceDesc.Format, m_surfaceDesc.Pool, &m_texture, NULL) == D3D_OK)
		{
			// Allocate surfaces
			m_surfaces = ruNEW CRuSurfaceD3D * [m_levelCount];
			for(UINT32 i = 0; i < m_levelCount; ++i)
			{
				IDirect3DSurface9 *d3dSurface = NULL;
				if(m_texture->GetSurfaceLevel(0, &d3dSurface) == D3D_OK)
				{
					m_surfaces[i] = ruNEW CRuSurfaceD3D(d3dSurface);
				}
			}

			return TRUE;
		}
	}

	return FALSE;
}

const char *CRuTextureD3D::GetTextureName() const
{
	if(m_textureName)
	{
		return m_textureName;
	}

	return "";
}

void CRuTextureD3D::SetTextureName(const char *textureName)
{
	if(m_textureName)
	{
		delete[] m_textureName;
	}

	UINT32 textureNameLength = strlen(textureName) + 1;
	m_textureName = ruNEW char [textureNameLength];
	strcpy(m_textureName, textureName);
}

void CRuTextureD3D::Select(UINT32 samplerIndex)
{
	// Set the texture through the engine
	static_cast<CRuEngineD3D *>(m_engine)->SetTexture(samplerIndex, m_texture);
}

BOOL CRuTextureD3D::GenerateMipLevels()
{
	if(m_texture)
	{
		D3DXFilterTexture(m_texture, NULL, 0, D3DX_DEFAULT);
		return TRUE;
	}

	return FALSE;
}

BOOL CRuTextureD3D::IsDynamic() const
{
	return m_isDynamic;
}

BOOL CRuTextureD3D::IsRenderTarget() const
{
	return m_isRenderTarget;
}

UINT32 CRuTextureD3D::GetLevelCount() const
{
	if(m_texture)
	{
		return m_texture->GetLevelCount();
	}

	return 0;
}

UINT32 CRuTextureD3D::GetLOD() const
{
	if(m_texture)
	{
		return m_texture->GetLOD();
	}

	return 0;
}

UINT32 CRuTextureD3D::SetLOD(UINT32 lod) const
{
	if(m_texture)
	{
		return m_texture->SetLOD(lod);
	}

	return 0;
}

RuResourcePool CRuTextureD3D::GetPool() const
{
	return m_resourcePool;
}

RuSurfaceFormat CRuTextureD3D::GetSurfaceFormat() const
{
	return m_surfaceFormat;
}

BOOL CRuTextureD3D::SelectRenderTarget(UINT32 level)
{
	if(m_texture)
	{
		IDirect3DSurface9 *rtSurface = NULL;

		if(m_texture->GetSurfaceLevel(level, &rtSurface) == D3D_OK)
		{
			// Set the selected level as the RT and immediately release the surface
			if(m_parentDevice->SetRenderTarget(0, rtSurface) == D3D_OK)
			{
				rtSurface->Release();
				return TRUE;
			}

			rtSurface->Release();
		}
	}

	return FALSE;
}

UINT32 CRuTextureD3D::GetLevelWidth(UINT32 level) const
{
	if(m_texture)
	{
		D3DSURFACE_DESC surfDesc;
		m_texture->GetLevelDesc(level, &surfDesc);
		return surfDesc.Width;
	}

	return 0;
}

UINT32 CRuTextureD3D::GetLevelHeight(UINT32 level) const
{
	if(m_texture)
	{
		D3DSURFACE_DESC surfDesc;
		m_texture->GetLevelDesc(level, &surfDesc);
		return surfDesc.Height;
	}

	return 0;
}

IRuSurface *CRuTextureD3D::GetLevelSurface(UINT32 level)
{
	if(m_texture && level < m_levelCount)
	{
		return m_surfaces[level];
	}

	return NULL;
}

BOOL CRuTextureD3D::Lock(UINT32 level, void **bitsOut, INT32 *pitchOut)
{
	if(m_texture)
	{
		D3DLOCKED_RECT lockedRect;
		if(m_texture->LockRect(level, &lockedRect, NULL, 0) == D3D_OK)
		{
			*bitsOut = lockedRect.pBits;
			*pitchOut = lockedRect.Pitch;

			return TRUE;
		}
	}

	return FALSE;
}

BOOL CRuTextureD3D::Unlock(UINT32 level)
{
	if(m_texture)
	{
		m_texture->UnlockRect(level);
	}

	return FALSE;
}

IDirect3DTexture9 *CRuTextureD3D::GetIDirect3DTexture()
{
	return m_texture;
}

// ************************************************************************************************************************************************************

CRuVolumeTextureD3D::CRuVolumeTextureD3D(IDirect3DVolumeTexture9 *texture, BOOL isDynamic, BOOL isMatchedToBackBuffer)
:	m_parentDevice(NULL),
	m_texture(NULL),
	m_levelCount(0),
	m_textureName(NULL),
	m_isDynamic(FALSE),
	m_resourcePool(ruRESPOOL_DEFAULT),
	m_surfaceFormat(ruSURFACEFMT_A8R8G8B8)
{
	if(texture)
	{
		m_texture = texture;
		m_isDynamic = isDynamic;

		// Get the surface description
		m_levelCount = m_texture->GetLevelCount();
		m_texture->GetLevelDesc(0, &m_surfaceDesc);

		m_resourcePool = ToRuResourcePool(m_surfaceDesc.Pool);
		m_surfaceFormat = ToRuSurfaceFormat(m_surfaceDesc.Format);
/*
		// Allocate surfaces
		m_surfaces = ruNEW CRuSurfaceD3D * [m_levelCount];
		for(UINT32 i = 0; i < m_levelCount; ++i)
		{
			IDirect3DSurface9 *d3dSurface = NULL;
			if(m_texture->GetSurfaceLevel(0, &d3dSurface) == D3D_OK)
			{
				m_surfaces[i] = ruNEW CRuSurfaceD3D(d3dSurface);
			}
		}
*/
		// Store a handle to the parent device
		m_texture->GetDevice(&m_parentDevice);
		m_parentDevice->Release();
	}
}

CRuVolumeTextureD3D::~CRuVolumeTextureD3D()
{
	if(m_texture)
	{
		m_texture->Release();
	}

	if(m_textureName)
	{
		delete[] m_textureName;
	}
/*
	// Release surfaces
	if(m_surfaces)
	{
		for(UINT32 i = 0; i < m_levelCount; ++i)
		{
			if(m_surfaces[i])
			{
				m_surfaces[i]->Release();
			}
		}

		delete[] m_surfaces;
	}
*/
}

void CRuVolumeTextureD3D::Invalidate()
{
	if(m_texture && m_resourcePool == ruRESPOOL_DEFAULT)
	{
/*
		// Release surfaces
		if(m_surfaces)
		{
			for(UINT32 i = 0; i < m_levelCount; ++i)
			{
				if(m_surfaces[i])
				{
					m_surfaces[i]->Release();
				}
			}

			delete[] m_surfaces;
			m_surfaces = NULL;
		}
*/
		// Release texture
		m_texture->Release();
		m_texture = NULL;
	}
}

BOOL CRuVolumeTextureD3D::IsInvalidated() const
{
	return m_texture == NULL;
}

BOOL CRuVolumeTextureD3D::Restore()
{
	if(m_parentDevice && m_texture == NULL && m_resourcePool == ruRESPOOL_DEFAULT)
	{
		if(m_parentDevice->CreateVolumeTexture(m_surfaceDesc.Width, m_surfaceDesc.Height, m_surfaceDesc.Depth, m_levelCount, 0, m_surfaceDesc.Format, m_surfaceDesc.Pool, &m_texture, NULL) == D3D_OK)
		{
/*
			// Allocate surfaces
			m_surfaces = ruNEW CRuSurfaceD3D * [m_levelCount];
			for(UINT32 i = 0; i < m_levelCount; ++i)
			{
				IDirect3DSurface9 *d3dSurface = NULL;
				if(m_texture->GetSurfaceLevel(0, &d3dSurface) == D3D_OK)
				{
					m_surfaces[i] = ruNEW CRuSurfaceD3D(d3dSurface);
				}
			}
*/
			return TRUE;
		}
	}

	return FALSE;
}

const char *CRuVolumeTextureD3D::GetTextureName() const
{
	if(m_textureName)
	{
		return m_textureName;
	}

	return "";
}

void CRuVolumeTextureD3D::SetTextureName(const char *textureName)
{
	if(m_textureName)
	{
		delete[] m_textureName;
	}

	UINT32 textureNameLength = strlen(textureName) + 1;
	m_textureName = ruNEW char [textureNameLength];
	strcpy(m_textureName, textureName);
}

void CRuVolumeTextureD3D::Select(UINT32 samplerIndex)
{
	// Set the texture through the engine
	static_cast<CRuEngineD3D *>(m_engine)->SetTexture(samplerIndex, m_texture);
}

BOOL CRuVolumeTextureD3D::GenerateMipLevels()
{
	if(m_texture)
	{
		D3DXFilterTexture(m_texture, NULL, 0, D3DX_DEFAULT);
		return TRUE;
	}

	return FALSE;
}

BOOL CRuVolumeTextureD3D::IsDynamic() const
{
	return m_isDynamic;
}

BOOL CRuVolumeTextureD3D::IsRenderTarget() const
{
	return FALSE;
}

UINT32 CRuVolumeTextureD3D::GetLevelCount() const
{
	if(m_texture)
	{
		return m_texture->GetLevelCount();
	}

	return 0;
}

UINT32 CRuVolumeTextureD3D::GetLOD() const
{
	if(m_texture)
	{
		return m_texture->GetLOD();
	}

	return 0;
}

UINT32 CRuVolumeTextureD3D::SetLOD(UINT32 lod) const
{
	if(m_texture)
	{
		return m_texture->SetLOD(lod);
	}

	return 0;
}

RuResourcePool CRuVolumeTextureD3D::GetPool() const
{
	return m_resourcePool;
}

RuSurfaceFormat CRuVolumeTextureD3D::GetSurfaceFormat() const
{
	return m_surfaceFormat;
}

BOOL CRuVolumeTextureD3D::SelectRenderTarget(UINT32 level)
{
	return FALSE;
}

UINT32 CRuVolumeTextureD3D::GetLevelWidth(UINT32 level) const
{
	if(m_texture)
	{
		D3DVOLUME_DESC surfDesc;
		m_texture->GetLevelDesc(level, &surfDesc);
		return surfDesc.Width;
	}

	return 0;
}

UINT32 CRuVolumeTextureD3D::GetLevelHeight(UINT32 level) const
{
	if(m_texture)
	{
		D3DVOLUME_DESC surfDesc;
		m_texture->GetLevelDesc(level, &surfDesc);
		return surfDesc.Height;
	}

	return 0;
}

UINT32 CRuVolumeTextureD3D::GetLevelDepth(UINT32 level) const
{
	if(m_texture)
	{
		D3DVOLUME_DESC surfDesc;
		m_texture->GetLevelDesc(level, &surfDesc);
		return surfDesc.Depth;
	}

	return 0;
}

BOOL CRuVolumeTextureD3D::Lock(UINT32 level, void **bitsOut, INT32 *rowPitchOut, INT32 *slicePitchOut)
{
	if(m_texture)
	{
		D3DLOCKED_BOX lockedBox;
		if(m_texture->LockBox(level, &lockedBox, NULL, 0) == D3D_OK)
		{
			*bitsOut = lockedBox.pBits;
			*rowPitchOut = lockedBox.RowPitch;
			*slicePitchOut = lockedBox.SlicePitch;

			return TRUE;
		}
	}

	return FALSE;
}

BOOL CRuVolumeTextureD3D::Unlock(UINT32 level)
{
	if(m_texture)
	{
		m_texture->UnlockBox(level);
	}

	return FALSE;
}

IDirect3DVolumeTexture9 *CRuVolumeTextureD3D::GetIDirect3DVolumeTexture()
{
	return m_texture;
}

// ************************************************************************************************************************************************************

CRuSwapChainD3D::CRuSwapChainD3D(IDirect3DSwapChain9 *swapChain)
:	m_parentDevice(NULL),
	m_backBuffer(NULL)
{
	SetD3DSwapChain(swapChain);
}

CRuSwapChainD3D::~CRuSwapChainD3D()
{
	Invalidate();
}

void CRuSwapChainD3D::Invalidate()
{
	// Release swap chain object
	if(m_swapChain)
		m_swapChain->Release();

	// Reset member variables
	m_parentDevice = NULL;
	m_swapChain = NULL;
	m_backBuffer = NULL;
}

BOOL CRuSwapChainD3D::IsInvalidated() const
{
	// Swap chain is lost if there is no swap chain object
	return (m_swapChain == NULL);
}

BOOL CRuSwapChainD3D::Restore()
{
	// Swap chains can never be restored manually
	return FALSE;
}

BOOL CRuSwapChainD3D::SelectRenderTarget()
{
	if(m_backBuffer && m_parentDevice)
	{
		// Set render target to the back buffer
		if(m_parentDevice->SetRenderTarget(0, m_backBuffer) == D3D_OK)
		{
			return TRUE;
		}
	}

	return FALSE;
}

UINT32 CRuSwapChainD3D::GetWidth() const
{
	return m_presentParameters.BackBufferWidth;
}

UINT32 CRuSwapChainD3D::GetHeight() const
{
	return m_presentParameters.BackBufferHeight;
}

BOOL CRuSwapChainD3D::Present()
{
	if(m_swapChain)
	{
		// Present the back buffer
		m_swapChain->Present(NULL, NULL, NULL, NULL, 0);

		return TRUE;
	}

	return FALSE;
}

IDirect3DSurface9 *CRuSwapChainD3D::GetIDirect3DSurface()
{
	return m_backBuffer;
}

BOOL CRuSwapChainD3D::SetD3DSwapChain(IDirect3DSwapChain9 *swapChain)
{
	if(swapChain)
	{
		m_swapChain = swapChain;

		// Store a pointer to the parent device
		if(m_swapChain->GetDevice(&m_parentDevice) == D3D_OK)
		{
			// Release the parent device reference count, since we do not need to actually hold the device
			m_parentDevice->Release();
		}

		// Store a pointer to the back buffer (assume double-buffering only)
		if(m_swapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &m_backBuffer) == D3D_OK)
		{
			// Release the back buffer reference count, since we do not need to actually hold the buffer
			m_backBuffer->Release();
		}

		// Store a copy of the present parameters
		m_swapChain->GetPresentParameters(&m_presentParameters);

		return TRUE;
	}

	return FALSE;
}

D3DPRESENT_PARAMETERS &CRuSwapChainD3D::PresentParameters()
{
	return m_presentParameters;
}

// ************************************************************************************************************************************************************

/*!
	CRuEngineD3D::RuDeviceInfoD3D::~RuDeviceInfoD3D()
	Standard destructor
*/
CRuEngineD3D::RuDeviceInfoD3D::~RuDeviceInfoD3D()
{
	for(INT32 i = 0; i < m_deviceComboList.Count(); i++)
	{
		delete m_deviceComboList[i];
	}

	m_deviceComboList.Clear();
}

// ************************************************************************************************************************************************************

/*!
	CRuEngineD3D::RuAdapterInfoD3D::~RuAdapterInfoD3D()
	Standard destructor
*/
CRuEngineD3D::RuAdapterInfoD3D::~RuAdapterInfoD3D()
{
	for(INT32 i = 0; i < m_deviceInfoList.Count(); i++)
	{
		delete m_deviceInfoList[i];
	}

	m_deviceInfoList.Clear();
}

// ************************************************************************************************************************************************************

/*!
	CRuEngineD3D::RuAdapterInfoD3D *CRuEngineD3D::RuEngineSettingD3D::AdapterInfo()
	Returns pointer to the currently selected mode's adapter info
	@return RuAdapterInfoD3D*
*/
CRuEngineD3D::RuAdapterInfoD3D *CRuEngineD3D::RuEngineSettingD3D::AdapterInfo()
{
	return m_isWindowed ? m_windowedAdapterInfo : m_fullscreenAdapterInfo;
}

/*!
	CRuEngineD3D::RuDeviceInfoD3D *CRuEngineD3D::RuEngineSettingD3D::DeviceInfo()
	Returns pointer to the currently selected mode's device info
	@return RuDeviceInfoD3D*
*/
CRuEngineD3D::RuDeviceInfoD3D *CRuEngineD3D::RuEngineSettingD3D::DeviceInfo()
{
	return m_isWindowed ? m_windowedDeviceInfo : m_fullscreenDeviceInfo;
}

/*!
	CRuEngineD3D::RuDeviceComboD3D *CRuEngineD3D::RuEngineSettingD3D::DeviceCombo()
	Returns pointer to the currently selected mode's device combo
	@return RuDeviceComboD3D*
*/
CRuEngineD3D::RuDeviceComboD3D *CRuEngineD3D::RuEngineSettingD3D::DeviceCombo()
{
	return m_isWindowed ? m_windowedDeviceCombo : m_fullscreenDeviceCombo;
}

/*!
	D3DDISPLAYMODE &CRuEngineD3D::RuEngineSettingD3D::DisplayMode()
	Returns reference to the currently selected mode's display mode
	@return D3DDISPLAYMODE&
*/
D3DDISPLAYMODE &CRuEngineD3D::RuEngineSettingD3D::DisplayMode()
{
	return m_isWindowed ? m_windowedDisplayMode : m_fullscreenDisplayMode;
}

/*!
	D3DFORMAT &CRuEngineD3D::RuEngineSettingD3D::DepthStencilBufferFormat()
	Returns reference to the currently selected mode's depth/stencil buffer format
	@return D3DFORMAT&
*/
D3DFORMAT &CRuEngineD3D::RuEngineSettingD3D::DepthStencilBufferFormat()
{
	return m_isWindowed ? m_windowedDepthStencilBufferFormat : m_fullscreenDepthStencilBufferFormat;
}

/*!
	D3DMULTISAMPLE_TYPE &CRuEngineD3D::RuEngineSettingD3D::MultisampleType()
	Returns reference to the currently selected mode's multisample type
	@return D3DMULTISAMPLE_TYPE&
*/
D3DMULTISAMPLE_TYPE &CRuEngineD3D::RuEngineSettingD3D::MultisampleType()
{
	return m_isWindowed ? m_windowedMultisampleType : m_fullscreenMultisampleType;
}

/*!
	DWORD &CRuEngineD3D::RuEngineSettingD3D::MultisampleQuality()
	Returns reference to the currently selected mode's multisample quality
	@return DWORD&
*/
DWORD &CRuEngineD3D::RuEngineSettingD3D::MultisampleQuality()
{
	return m_isWindowed ? m_windowedMultisampleQuality : m_fullscreenMultisampleQuality;
}

/*!
	RuEngineMode &CRuEngineD3D::RuEngineSettingD3D::EngineMode()
	Returns reference to the currently selected mode's engine mode
	@return RuEngineMode&
*/
RuEngineMode &CRuEngineD3D::RuEngineSettingD3D::EngineMode()
{
	return m_isWindowed ? m_windowedEngineMode : m_fullscreenEngineMode;
}

/*!
	UINT32 &CRuEngineD3D::RuEngineSettingD3D::PresentInterval()
	Returns reference to the currently selected mode's present interval
	@return UINT32&
*/
UINT32 &CRuEngineD3D::RuEngineSettingD3D::PresentInterval()
{
	return m_isWindowed ? m_windowedPresentInterval : m_fullscreenPresentInterval;
}

/*!
	INT32 CRuEngineD3D::RuEngineSettingD3D::GetAdapterOrdinal()
	Returns the currently selected mode's adapter ordinal
	@return INT32
*/
INT32 CRuEngineD3D::RuEngineSettingD3D::GetAdapterOrdinal()
{
	return m_isWindowed ? m_windowedDeviceCombo->m_adapterOrdinal : m_fullscreenDeviceCombo->m_adapterOrdinal;
}

/*!
	D3DDEVTYPE CRuEngineD3D::RuEngineSettingD3D::GetDeviceType()
	Returns the currently selected mode's device type
	@return D3DDEVTYPE
*/
D3DDEVTYPE CRuEngineD3D::RuEngineSettingD3D::GetDeviceType()
{
	return m_isWindowed ? m_windowedDeviceCombo->m_deviceType : m_fullscreenDeviceCombo->m_deviceType;
}

/*!
	D3DFORMAT CRuEngineD3D::RuEngineSettingD3D::GetBackBufferFormat()
	Returns the currently selected mode's back buffer format
	@return D3DFORMAT
*/
D3DFORMAT CRuEngineD3D::RuEngineSettingD3D::GetBackBufferFormat()
{
	return m_isWindowed ? m_windowedDeviceCombo->m_backBufferFormat : m_fullscreenDeviceCombo->m_backBufferFormat;
}

BOOL CRuEngineD3D::RuEngineSettingD3D::FindBestWindowedMode(IDirect3D9 *d3dObject, CRuArrayList<RuAdapterInfoD3D *> &adapterInfoList, BOOL requireHAL, BOOL requireREF)
{
	D3DDISPLAYMODE primaryDesktopDisplayMode;
	RuAdapterInfoD3D *bestAdapterInfo = NULL;
	RuDeviceInfoD3D *bestDeviceInfo = NULL;
	RuDeviceComboD3D *bestDeviceCombo = NULL;

	// Get display mode of primary adapter (which is assumed to be where the window will appear)
	d3dObject->GetAdapterDisplayMode(0, &primaryDesktopDisplayMode);

	for(INT32 adapterIndex = 0; adapterIndex < adapterInfoList.Count(); adapterIndex++)
	{
		RuAdapterInfoD3D *adapterInfo = adapterInfoList[adapterIndex];

		for(INT32 deviceIndex = 0; deviceIndex < adapterInfo->m_deviceInfoList.Count(); deviceIndex++)
		{
			RuDeviceInfoD3D *deviceInfo = adapterInfo->m_deviceInfoList[deviceIndex];

			// Check for HAL & REF constraints
			if(requireHAL && deviceInfo->m_deviceType != D3DDEVTYPE_HAL)
				continue;
			if(requireREF && deviceInfo->m_deviceType != D3DDEVTYPE_REF)
				continue;

			for(INT32 dcIndex = 0; dcIndex < deviceInfo->m_deviceComboList.Count(); dcIndex++)
			{
				RuDeviceComboD3D *deviceCombo = deviceInfo->m_deviceComboList[dcIndex];

				BOOL adapterMatchesBB = (deviceCombo->m_backBufferFormat == deviceCombo->m_adapterFormat);

				// Skip non-windowed combinations
				if(!deviceCombo->m_isWindowed)
					continue;

				// Skip formats which does not match the current desktop format
				if(deviceCombo->m_adapterFormat != primaryDesktopDisplayMode.Format)
					continue;

				// If we haven't found a compatible DeviceCombo yet, or if this set is better
				// (beCRuse it's a HAL, and/or beCRuse formats match better), save it
				if(	bestDeviceCombo == NULL ||
					(bestDeviceCombo->m_deviceType != D3DDEVTYPE_HAL && deviceCombo->m_deviceType == D3DDEVTYPE_HAL) ||
					(deviceCombo->m_deviceType == D3DDEVTYPE_HAL && adapterMatchesBB))
				{
					bestAdapterInfo = adapterInfo;
					bestDeviceInfo = deviceInfo;
					bestDeviceCombo = deviceCombo;

					// This windowed device combo looks great -- take it
					if(deviceCombo->m_deviceType == D3DDEVTYPE_HAL && adapterMatchesBB)
					{
						adapterIndex = adapterInfoList.Count();
						deviceIndex = adapterInfo->m_deviceInfoList.Count();
						break;
					}
				}
			}
		}
	}

	// No good combo found
	if(bestDeviceCombo == NULL)
		return FALSE;

	// Set D3D settings according to the selected device combo
	m_isWindowed = TRUE;
	m_windowedAdapterInfo = bestAdapterInfo;
	m_windowedDeviceInfo = bestDeviceInfo;
	m_windowedDeviceCombo = bestDeviceCombo;
	m_windowedDisplayMode = primaryDesktopDisplayMode;
	m_windowedDepthStencilBufferFormat = bestDeviceCombo->m_depthStencilFormatList[0];
	m_windowedMultisampleType = bestDeviceCombo->m_multisampleTypeList[0];
	m_windowedMultisampleQuality = 0;
	m_windowedEngineMode =  bestDeviceCombo->m_engineModeList[0];
	m_windowedPresentInterval = bestDeviceCombo->m_presentIntervalList[0];

	return TRUE;
}

BOOL CRuEngineD3D::RuEngineSettingD3D::FindBestFullscreenMode(IDirect3D9 *d3dObject, CRuArrayList<RuAdapterInfoD3D *> &adapterInfoList, BOOL requireHAL, BOOL requireREF)
{
	// For fullscreen, default to first HAL DeviceCombo that supports the current desktop 
	// display mode, or any display mode if HAL is not compatible with the desktop mode, or 
	// non-HAL if no HAL is available
	D3DDISPLAYMODE adapterDesktopDisplayMode;
	D3DDISPLAYMODE bestAdapterDesktopDisplayMode;
	D3DDISPLAYMODE bestDisplayMode;
	RuAdapterInfoD3D *bestAdapterInfo = NULL;
	RuDeviceInfoD3D *bestDeviceInfo = NULL;
	RuDeviceComboD3D *bestDeviceCombo = NULL;

	bestAdapterDesktopDisplayMode.Width = 0;
	bestAdapterDesktopDisplayMode.Height = 0;
	bestAdapterDesktopDisplayMode.Format = D3DFMT_UNKNOWN;
	bestAdapterDesktopDisplayMode.RefreshRate = 0;

	for(INT32 adapterIndex = 0; adapterIndex < adapterInfoList.Count(); adapterIndex++)
	{
		RuAdapterInfoD3D *adapterInfo = adapterInfoList[adapterIndex];
		d3dObject->GetAdapterDisplayMode(adapterInfo->m_adapterOrdinal, &adapterDesktopDisplayMode);

		for(INT32 deviceIndex = 0; deviceIndex < adapterInfo->m_deviceInfoList.Count(); deviceIndex++)
		{
			RuDeviceInfoD3D *deviceInfo = adapterInfo->m_deviceInfoList[deviceIndex];

			// Check for HAL & REF constraints
			if(requireHAL && deviceInfo->m_deviceType != D3DDEVTYPE_HAL)
				continue;
			if(requireREF && deviceInfo->m_deviceType != D3DDEVTYPE_REF)
				continue;

			for(INT32 dcIndex = 0; dcIndex < deviceInfo->m_deviceComboList.Count(); dcIndex++)
			{
				RuDeviceComboD3D *deviceCombo = deviceInfo->m_deviceComboList[dcIndex];

				BOOL adapterMatchesBB = (deviceCombo->m_backBufferFormat == deviceCombo->m_adapterFormat);
				BOOL adapterMatchesDesktop = (deviceCombo->m_adapterFormat == adapterDesktopDisplayMode.Format);

				// Skip windowed combinations
				if(deviceCombo->m_isWindowed)
					continue;

				// If we haven't found a compatible DeviceCombo yet, or if this set is better
				// (beCRuse it's a HAL, and/or beCRuse formats match better), save it
				if(	bestDeviceCombo == NULL ||
					(bestDeviceCombo->m_deviceType != D3DDEVTYPE_HAL && deviceCombo->m_deviceType == D3DDEVTYPE_HAL) ||
					(deviceCombo->m_deviceType == D3DDEVTYPE_HAL && bestDeviceCombo->m_adapterFormat != adapterDesktopDisplayMode.Format && adapterMatchesDesktop) ||
					(deviceCombo->m_deviceType == D3DDEVTYPE_HAL && adapterMatchesDesktop && adapterMatchesBB))
				{
					bestAdapterInfo = adapterInfo;
					bestDeviceInfo = deviceInfo;
					bestDeviceCombo = deviceCombo;

					// This fullscreen device combo looks great -- take it
					if(deviceCombo->m_deviceType == D3DDEVTYPE_HAL && adapterMatchesDesktop && adapterMatchesBB)
					{
						adapterIndex = adapterInfoList.Count();
						deviceIndex = adapterInfo->m_deviceInfoList.Count();
						break;
					}
				}
			}
		}
	}

	// No good combo found
	if(bestDeviceCombo == NULL)
		return FALSE;

	// Find a display mode on the best adapter that uses bestDeviceCombo->m_adapterFormat and is as close to our preferred resolution/refresh as possible
	if(m_preferredFullscreenWidth == 0 || m_preferredFullscreenHeight == 0)
	{
		m_preferredFullscreenWidth = bestAdapterDesktopDisplayMode.Width;
		m_preferredFullscreenHeight = bestAdapterDesktopDisplayMode.Height;
		m_preferredFullscreenRefreshRate = bestAdapterDesktopDisplayMode.RefreshRate;
	}
	bestDisplayMode.Width = 0;
	bestDisplayMode.Height = 0;
	bestDisplayMode.Format = D3DFMT_UNKNOWN;
	bestDisplayMode.RefreshRate = 0;
	for(INT32 displayModeIndex = 0; displayModeIndex < bestAdapterInfo->m_displayModeList.Count(); displayModeIndex++)
	{
		D3DDISPLAYMODE &dm = bestAdapterInfo->m_displayModeList[displayModeIndex];

		// Skip incompatible formats
		if(dm.Format != bestDeviceCombo->m_adapterFormat)
			continue;

		if(dm.Width == m_preferredFullscreenWidth &&
			dm.Height == m_preferredFullscreenHeight && 
			dm.RefreshRate == m_preferredFullscreenRefreshRate)
		{
			// Found a perfect match
			bestDisplayMode = dm;
			break;
		}
		else if(dm.Width == m_preferredFullscreenWidth &&
			dm.Height == m_preferredFullscreenHeight && 
			dm.RefreshRate > m_preferredFullscreenRefreshRate)
		{
			// Refresh rate doesn't match, but width/height match, keep this and keep looking
			bestDisplayMode = dm;
		}
		else if(dm.Width == m_preferredFullscreenWidth)
		{
			// Width matches, so keep this and keep looking
			bestDisplayMode = dm;
		}
		else if(bestDisplayMode.Width == 0)
		{
			// We don't have anything better yet, so keep this and keep looking
			bestDisplayMode = dm;
		}
	}

	// Set D3D settings according to the selected device combo
	m_isWindowed = FALSE;
	m_fullscreenAdapterInfo = bestAdapterInfo;
	m_fullscreenDeviceInfo = bestDeviceInfo;
	m_fullscreenDeviceCombo = bestDeviceCombo;
	m_fullscreenDisplayMode = bestDisplayMode;
	m_fullscreenDepthStencilBufferFormat = bestDeviceCombo->m_depthStencilFormatList[0];
	m_fullscreenMultisampleType = bestDeviceCombo->m_multisampleTypeList[0];
	m_fullscreenMultisampleQuality = 0;
	m_fullscreenEngineMode =  bestDeviceCombo->m_engineModeList[0];
	m_fullscreenPresentInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

	return TRUE;
}

BOOL CRuEngineD3D::RuEngineSettingD3D::FindBestFullscreenMode()
{
	D3DDISPLAYMODE bestDisplayMode;

	// Find a display mode on the current adapter that uses bestDeviceCombo->m_adapterFormat and is as close to our preferred resolution/refresh as possible
	bestDisplayMode.Width = 0;
	bestDisplayMode.Height = 0;
	bestDisplayMode.Format = D3DFMT_UNKNOWN;
	bestDisplayMode.RefreshRate = 0;
	for(INT32 displayModeIndex = 0; displayModeIndex < m_fullscreenAdapterInfo->m_displayModeList.Count(); displayModeIndex++)
	{
		D3DDISPLAYMODE &dm = m_fullscreenAdapterInfo->m_displayModeList[displayModeIndex];

		// Skip incompatible formats
		if(dm.Format != m_fullscreenDeviceCombo->m_adapterFormat)
			continue;

		if(dm.Width == m_preferredFullscreenWidth &&
			dm.Height == m_preferredFullscreenHeight && 
			dm.RefreshRate == m_preferredFullscreenRefreshRate)
		{
			// Found a perfect match
			bestDisplayMode = dm;
			break;
		}
		else if(dm.Width == m_preferredFullscreenWidth &&
			dm.Height == m_preferredFullscreenHeight && 
			dm.RefreshRate > m_preferredFullscreenRefreshRate)
		{
			// Refresh rate doesn't match, but width/height match, keep this and keep looking
			bestDisplayMode = dm;
		}
		else if(dm.Width == m_preferredFullscreenWidth)
		{
			// Width matches, so keep this and keep looking
			bestDisplayMode = dm;
		}
		else if(bestDisplayMode.Width == 0)
		{
			// We don't have anything better yet, so keep this and keep looking
			bestDisplayMode = dm;
		}
	}

	return TRUE;
}

void CRuEngineD3D::RuEngineSettingD3D::BuildPresentParamsFromSettings(HWND deviceWindow, D3DPRESENT_PARAMETERS &presentParamsOut)
{
	presentParamsOut.Windowed = m_isWindowed;
	presentParamsOut.BackBufferCount = 1;
	presentParamsOut.MultiSampleType = MultisampleType();
	presentParamsOut.MultiSampleQuality = MultisampleQuality();
	presentParamsOut.SwapEffect = D3DSWAPEFFECT_DISCARD;
	presentParamsOut.EnableAutoDepthStencil = TRUE;
	presentParamsOut.hDeviceWindow = deviceWindow;
	presentParamsOut.Flags = D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL;
	presentParamsOut.AutoDepthStencilFormat = DepthStencilBufferFormat();

	if(m_isWindowed)
	{
		presentParamsOut.BackBufferWidth = m_windowedWidth;
		presentParamsOut.BackBufferHeight = m_windowedHeight;
		presentParamsOut.BackBufferFormat = DeviceCombo()->m_backBufferFormat;
		presentParamsOut.FullScreen_RefreshRateInHz = 0;
		presentParamsOut.PresentationInterval = PresentInterval();
	}
	else
	{
		presentParamsOut.BackBufferWidth  = DisplayMode().Width;
		presentParamsOut.BackBufferHeight = DisplayMode().Height;
		presentParamsOut.BackBufferFormat = DeviceCombo()->m_backBufferFormat;
		presentParamsOut.FullScreen_RefreshRateInHz = m_fullscreenDisplayMode.RefreshRate;
		presentParamsOut.PresentationInterval = PresentInterval();
	}
}

void CRuEngineD3D::RuEngineSettingD3D::BuildPresentParamsForWindowed(HWND deviceWindow, UINT32 width, UINT32 height,  D3DPRESENT_PARAMETERS &presentParamsOut)
{
	presentParamsOut.Windowed = TRUE;
	presentParamsOut.BackBufferCount = 1;
	presentParamsOut.MultiSampleType = MultisampleType();
	presentParamsOut.MultiSampleQuality = MultisampleQuality();
	presentParamsOut.SwapEffect = D3DSWAPEFFECT_DISCARD;
	presentParamsOut.EnableAutoDepthStencil = TRUE;
	presentParamsOut.hDeviceWindow = deviceWindow;
	presentParamsOut.Flags = D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL;
	presentParamsOut.AutoDepthStencilFormat = DepthStencilBufferFormat();
	presentParamsOut.BackBufferWidth = width;
	presentParamsOut.BackBufferHeight = height;
	presentParamsOut.BackBufferFormat = DeviceCombo()->m_backBufferFormat;
	presentParamsOut.FullScreen_RefreshRateInHz = 0;
	presentParamsOut.PresentationInterval = PresentInterval();
}

// ************************************************************************************************************************************************************

BOOL CRuEngineD3D::RuVEC::operator==(const RuVEC &ve) const
{
	return IsEqual(m_vertexElements,  ve.m_vertexElements);
}

// ************************************************************************************************************************************************************

/*!
	CRuEngineD3D::CRuEngineD3D()
	Standard constructor
*/
CRuEngineD3D::CRuEngineD3D()
:	m_d3dObject(NULL),
	m_d3dDevice(NULL),
	m_defaultDepthStencilSurface(NULL),
	m_resourceArenaInvalidated(TRUE),
	m_vbToIBRatio(1.5f),
	m_vbPageSize(8192),
	m_vbBufferSize(16),
	m_vbMaxAllocationSize(4194304),
	m_ibPageSize(4096),
	m_ibBufferSize(16),
	m_ibMaxAllocationSize(4194304),
	m_allocatedVertexBuffers(NULL),
	m_allocatedIndexBuffers(NULL)
{
	m_appMinFullscreenWidth = 800;
	m_appMinFullscreenHeight = 600;
	m_appMinColorChannelBits = 8;
	m_appMinAlphaChannelBits = 8;
	m_appMinDepthBits = 24;
	m_appMinStencilBits = 8;
	m_appUsesDepthBuffer = TRUE;
	m_appUsesMixedMode = FALSE;
	m_appRequiresWindowed = FALSE;
	m_appRequiresFullscreen = FALSE;
	
	// Set default values for engine settings
	m_engineSetting.m_windowedWidth = 800;
	m_engineSetting.m_windowedHeight = 600;
	m_engineSetting.m_preferredFullscreenWidth = 800;
	m_engineSetting.m_preferredFullscreenHeight = 600;
}

/*!
	CRuEngineD3D::~CRuEngineD3D()
	Standard destructor
*/
CRuEngineD3D::~CRuEngineD3D()
{
	Shutdown();
}

/*!
	BOOL CRuEngineD3D::Startup()
	Starts the engine core
	@return BOOL
	@retval TRUE The startup succeeded
	@retval FALSE The startup failed
*/
BOOL CRuEngineD3D::Startup(HWND primaryWindow)
{
	m_d3dObject = Direct3DCreate9(D3D_SDK_VERSION);

	if(m_d3dObject == NULL)
		return FALSE;

	m_primaryWindow = primaryWindow;

	// Setup hashmap for vertex declarations
	m_vertexDeclarations = ruNEW CRuHashMap<RuVEC, IRuVertexDeclaration *>(1024);

	// Reset state cache
	ClearStateCache();

	return TRUE;
}

/*!
	BOOL CRuEngineD3D::Shutdown()
	Shuts down the engine core
	@return BOOL
	@retval TRUE The shutdown succeeded
	@retval FALSE The shutdown failed
*/
BOOL CRuEngineD3D::Shutdown()
{
	// Release all vertex declarations
	ClearVertexDeclarationCache();

	delete m_vertexDeclarations;

	// Destroy resource arena
	DestroyResourceArena();

	// Destroy any unreleased depth stencil surfaces
	for(INT32 i = 0; i < m_depthStencilSurfaces.Count(); ++i)
	{
		m_depthStencilSurfaces[i]->Release();
	}
	m_depthStencilSurfaces.Clear();

	// Destroy any unreleased render targets
	for(INT32 i = 0; i < m_renderTargets.Count(); ++i)
	{
		m_renderTargets[i]->Release();
	}
	m_renderTargets.Clear();

	// Destroy any unreleased swap chains
	for(INT32 i = 0; i < m_swapChains.Count(); ++i)
	{
		m_swapChains[i]->Release();
	}
	m_swapChains.Clear();

	// Release d3d objects
	if(m_defaultDepthStencilSurface)
		m_defaultDepthStencilSurface->Release();

	if(m_d3dDevice)
		m_d3dDevice->Release();

	if(m_d3dObject)
		m_d3dObject->Release();

	// Clear adapter information
	for(INT32 i = 0; i < m_adapterInfoList.Count(); i++)
	{
		delete m_adapterInfoList[i];
	}
	m_adapterInfoList.Clear();
	m_allowedAdapterFormatList.Clear();

	m_d3dObject = NULL;
	m_d3dDevice = NULL;
	m_defaultDepthStencilSurface = NULL;

	return TRUE;
}

void CRuEngineD3D::ClearStateCache()
{
	// Set render states to uninitialized values
	for(INT32 i = 0; i < s_maxRenderStates; ++i)
	{
		m_renderStatesOK[i] = FALSE;
	}

	// Set texture & sampler stage states to uninitialized values
	for(INT32 i = 0; i < s_maxStages; ++i)
	{
		for(INT32 j = 0; j < s_maxTextureStates; ++j)
		{
			m_textureStageStatesOK[i][j] = FALSE;
		}

		for(INT32 j = 0; j < s_maxSamplerStates; ++j)
		{
			m_samplerStageStatesOK[i][j] = FALSE;
		}
	}

	// Reset vertex and pixel shader settings
	m_currentVertexDeclarationOK = FALSE;
	m_currentVertexShaderOK = FALSE;
	m_currentPixelShaderOK = FALSE;

	m_currentVertexDeclaration = NULL;
	m_currentVertexShader = NULL;
	m_currentPixelShader = NULL;

	// Reset vertex buffer states
	for(INT32 i = 0; i < s_maxVertexStreams; ++i)
	{
		m_currentVertexStreamOK[i] = FALSE;
		m_currentVertexStream[i] = NULL;
		m_currentVertexStreamOffset[i] = 0;
		m_currentVertexStreamStride[i] = 0;
	}

	// Reset index buffer states
	m_currentIndexStreamOK = FALSE;
	m_currentIndexStream = NULL;

	// Reset sampler texture states
	for(INT32 i = 0; i < s_maxSamplers; ++i)
	{
		m_currentTextureOK[i] = FALSE;
		m_currentTexture[i] = NULL;
	}
}

void CRuEngineD3D::ClearSampler(DWORD sampler)
{
	SetTexture(sampler, NULL);
}

BOOL CRuEngineD3D::BeginScene()
{
	if(m_d3dDevice == NULL)
		return FALSE;

	if(m_d3dDevice->BeginScene() != D3D_OK)
		return FALSE;

	// Reset metric values
	m_numStateChanges = 0;

	return TRUE;
}

BOOL CRuEngineD3D::EndScene()
{
	if(m_d3dDevice == NULL)
		return FALSE;

	if(m_d3dDevice->EndScene() != D3D_OK)
		return FALSE;

	return TRUE;
}

BOOL CRuEngineD3D::Clear(DWORD clearFlags, RUCOLOR color, float z, DWORD stencil)
{
	if(m_d3dDevice == NULL)
		return FALSE;

	if(m_d3dDevice->Clear(0, NULL, clearFlags, color, z, stencil) != D3D_OK)
		return FALSE;

	return TRUE;
}

HRESULT CRuEngineD3D::SetRenderState(D3DRENDERSTATETYPE state, DWORD value)
{
	if(m_d3dDevice == NULL)
		return D3DERR_INVALIDCALL;

	if(state > s_maxRenderStates || m_renderStates[state] != value || m_renderStatesOK[state] == FALSE)
	{
		++m_numStateChanges;

		HRESULT result = m_d3dDevice->SetRenderState(state, value);
		if(FAILED(result))
			return result;

		if(state < s_maxRenderStates)
		{
			m_renderStatesOK[state] = TRUE;
			m_renderStates[state] = value;
		}
//		else
//			MessageBox(NULL, "WARNING: Some render states are not being cached", "CDirect3DStateCache", MB_OK);
	}

	return D3D_OK;
}

HRESULT CRuEngineD3D::SetTextureStageState(DWORD stage, D3DTEXTURESTAGESTATETYPE state, DWORD value)
{
	if(m_d3dDevice == NULL || stage >= s_maxStages)
		return D3DERR_INVALIDCALL;

	if(state > s_maxTextureStates || m_textureStageStates[stage][state] != value || m_textureStageStatesOK[stage][state] == FALSE)
	{
		++m_numStateChanges;

		HRESULT result = m_d3dDevice->SetTextureStageState(stage, state, value);
		if(FAILED(result))
			return result;

		if(state < s_maxTextureStates)
		{
			m_textureStageStatesOK[stage][state] = TRUE;
			m_textureStageStates[stage][state] = value;
		}
//		else
//			MessageBox(NULL, "WARNING: Some texture stage states are not being cached", "CDirect3DStateCache", MB_OK);
	}

	return D3D_OK;
}

HRESULT CRuEngineD3D::SetSamplerState(DWORD stage, D3DSAMPLERSTATETYPE state, DWORD value)
{
	if(m_d3dDevice == NULL || stage >= s_maxStages)
		return D3DERR_INVALIDCALL;

	if(state > s_maxSamplerStates || m_samplerStageStates[stage][state] != value || m_samplerStageStatesOK[stage][state] == FALSE)
	{
		++m_numStateChanges;

		HRESULT result = m_d3dDevice->SetSamplerState(stage, state, value);
		if(FAILED(result))
			return result;

		if(state < s_maxSamplerStates)
		{
			m_samplerStageStatesOK[stage][state] = TRUE;
			m_samplerStageStates[stage][state] = value;
		}
//		else
//			MessageBox(NULL, "WARNING: Some sampler states are not being cached", "CDirect3DStateCache", MB_OK);
	}

	return D3D_OK;
}

HRESULT CRuEngineD3D::SetVertexDeclaration(IDirect3DVertexDeclaration9 *decl)
{
	if(m_d3dDevice == NULL)
		return D3DERR_INVALIDCALL;

	if(m_currentVertexDeclaration != decl || m_currentVertexDeclarationOK == FALSE)
	{
		++m_numStateChanges;

		HRESULT result = m_d3dDevice->SetVertexDeclaration(decl);
		if(FAILED(result))
			return result;

		m_currentVertexDeclarationOK = TRUE;
		m_currentVertexDeclaration = decl;
	}

	return D3D_OK;
}

HRESULT CRuEngineD3D::SetVertexShader(IDirect3DVertexShader9 *vs)
{
	if(m_d3dDevice == NULL)
		return D3DERR_INVALIDCALL;

	if(m_currentVertexShader != vs || m_currentVertexShaderOK == FALSE)
	{
		++m_numStateChanges;

		HRESULT result = m_d3dDevice->SetVertexShader(vs);
		if(FAILED(result))
			return result;

		m_currentVertexShaderOK = TRUE;
		m_currentVertexShader = vs;
	}

	return D3D_OK;
}

HRESULT CRuEngineD3D::SetPixelShader(IDirect3DPixelShader9 *ps)
{
	if(m_d3dDevice == NULL)
		return D3DERR_INVALIDCALL;

	if(m_currentPixelShader != ps || m_currentPixelShaderOK == FALSE)
	{
		++m_numStateChanges;

		HRESULT result = m_d3dDevice->SetPixelShader(ps);
		if(FAILED(result))
			return result;

		m_currentPixelShaderOK = TRUE;
		m_currentPixelShader = ps;
	}

	return D3D_OK;
}

HRESULT CRuEngineD3D::SetStreamSource(UINT32 streamNumber, IDirect3DVertexBuffer9 *vertexBuffer, UINT32 offset, UINT32 stride)
{
	if(m_d3dDevice == NULL)
		return D3DERR_INVALIDCALL;

	if(m_currentVertexStream[streamNumber] != vertexBuffer || m_currentVertexStreamOffset[streamNumber] != offset || m_currentVertexStreamStride[streamNumber] != stride || m_currentVertexStreamOK[streamNumber] == FALSE)
	{
		++m_numStateChanges;

		HRESULT result = m_d3dDevice->SetStreamSource(streamNumber, vertexBuffer, offset, stride);
		if(FAILED(result))
			return result;

		m_currentVertexStreamOK[streamNumber] = TRUE;
		m_currentVertexStream[streamNumber] = vertexBuffer;
		m_currentVertexStreamOffset[streamNumber] = offset;
		m_currentVertexStreamStride[streamNumber] = stride;
	}

	return D3D_OK;
}

HRESULT CRuEngineD3D::SetIndices(IDirect3DIndexBuffer9 *indexBuffer)
{
	if(m_d3dDevice == NULL)
		return D3DERR_INVALIDCALL;

	if(m_currentIndexStream != indexBuffer || m_currentIndexStreamOK == FALSE)
	{
		++m_numStateChanges;

		HRESULT result = m_d3dDevice->SetIndices(indexBuffer);
		if(FAILED(result))
			return result;

		m_currentIndexStreamOK = TRUE;
		m_currentIndexStream = indexBuffer;
	}

	return D3D_OK;
}

HRESULT CRuEngineD3D::SetTexture(DWORD sampler, IDirect3DBaseTexture9 *texture)
{
	if(m_d3dDevice == NULL)
		return D3DERR_INVALIDCALL;

	if(m_currentTexture[sampler] != texture || m_currentTextureOK[sampler] == FALSE)
	{
		++m_numStateChanges;

		HRESULT result = m_d3dDevice->SetTexture(sampler, texture);
		if(FAILED(result))
			return result;

		m_currentTextureOK[sampler] = TRUE;
		m_currentTexture[sampler] = texture;
	}

	return D3D_OK;
}

void CRuEngineD3D::FlushStateChanges()
{
	return;
}

BOOL CRuEngineD3D::RestoreDefaultDepthStencil()
{
	if(m_d3dDevice)
	{
		HRESULT hr = m_d3dDevice->SetDepthStencilSurface(m_defaultDepthStencilSurface);
		if(FAILED(hr))
			return FALSE;

		return TRUE;
	}

	return FALSE;
}

BOOL CRuEngineD3D::SetScissorRect(RECT *scissorRect)
{
	if(m_d3dDevice)
	{
		if(scissorRect)
		{
			m_d3dDevice->SetScissorRect(scissorRect);
			SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE);
		}
		else
		{
			SetRenderState(D3DRS_SCISSORTESTENABLE, FALSE);
		}
	}

	return FALSE;
}

BOOL CRuEngineD3D::SetResourceArenaSize(UINT32 arenaSize)
{
	UINT32 vbArenaPages, ibArenaPages;
	UINT32 numBuffers, pagesRemaining;

	// Destroy resource arena
	DestroyResourceArena();

	// Calculate vertex and index buffer arena sizes in pages
	vbArenaPages = ((UINT32) ((arenaSize / (m_vbToIBRatio + 1.0f)) * m_vbToIBRatio)) / m_vbPageSize;
	ibArenaPages = ((UINT32) (arenaSize / (m_vbToIBRatio + 1.0f))) / m_ibPageSize;

	m_vbBufferSize = m_vbMaxAllocationSize / m_vbPageSize;
	m_ibBufferSize = m_ibMaxAllocationSize / m_ibPageSize;

	// Create vertex buffer management structures
	numBuffers = (vbArenaPages / m_vbBufferSize) + (vbArenaPages % m_vbBufferSize ? 1 : 0);
	pagesRemaining = vbArenaPages;
	for(UINT32 i = 0; i < numBuffers; i++, pagesRemaining -= m_vbBufferSize)
	{
		// Create new managed buffer, without allocating a D3D vertex buffer
		RuManagedVertexBuffer *managedVB = ruNEW RuManagedVertexBuffer();
		managedVB->m_vertexBuffer = NULL;
		managedVB->m_bufferSize = min(m_vbBufferSize, pagesRemaining);

		// Add managed buffer to list
		m_vertexBuffers.Add(managedVB);
	}

	// Create index buffer management structures
	numBuffers = (ibArenaPages / m_ibBufferSize) + (ibArenaPages % m_ibBufferSize ? 1 : 0);
	pagesRemaining = ibArenaPages;
	for(UINT32 i = 0; i < numBuffers; i++, pagesRemaining -= m_ibBufferSize)
	{
		// Create new managed buffer, without allocating a D3D index buffer
		RuManagedIndexBuffer *managedIB = ruNEW RuManagedIndexBuffer();
		managedIB->m_indexBuffer = NULL;
		managedIB->m_bufferSize = min(m_ibBufferSize, pagesRemaining);

		// Add managed buffer to list
		m_indexBuffers.Add(managedIB);
	}

	// Create vertex buffer allocation hash
	m_allocatedVertexBuffers = ruNEW CRuHashMap<DWORD, RuMemoryBlock>(vbArenaPages * 4);

	// Create index buffer allocation hash
	m_allocatedIndexBuffers = ruNEW CRuHashMap<DWORD, RuMemoryBlock>(ibArenaPages * 4);

	// Set resource arena to invalidated state
	m_resourceArenaInvalidated = TRUE;

	return TRUE;
}

BOOL CRuEngineD3D::GetResourceArenaSize(UINT32 &vbArenaSizeOut, UINT32 &ibArenaSizeOut, UINT32 &vbArenaPagesOut, UINT32 &ibArenaPagesOut)
{
	vbArenaSizeOut = 0;
	ibArenaSizeOut = 0;
	vbArenaPagesOut = 0;
	ibArenaPagesOut = 0;

	for(INT32 i = 0; i < m_vertexBuffers.Count(); i++)
	{
		vbArenaSizeOut += m_vertexBuffers[i]->m_bufferSize * m_vbPageSize;
		vbArenaPagesOut += m_vertexBuffers[i]->m_bufferSize;
	}

	for(INT32 i = 0; i < m_indexBuffers.Count(); i++)
	{
		ibArenaSizeOut += m_indexBuffers[i]->m_bufferSize * m_ibPageSize;
		ibArenaPagesOut += m_indexBuffers[i]->m_bufferSize;
	}

	return TRUE;
}

/*!
	BOOL CRuEngineD3D::DestroyResourceArena()
	Destroys vertex and index buffers used by the resource arena
	@return BOOL
	@retval TRUE Destruction was successful
	@retval FALSE Destruction failed
*/
BOOL CRuEngineD3D::DestroyResourceArena()
{
	// Free allocated resources
	InvalidateResourceArena();

	// Delete managed vertex buffer data
	for(INT32 i = 0; i < m_vertexBuffers.Count(); i++)
	{
		delete m_vertexBuffers[i];
	}

	// Delete managed index buffer data
	for(INT32 i = 0; i < m_indexBuffers.Count(); i++)
	{
		delete m_indexBuffers[i];
	}

	// Delete allocation hashes
	delete m_allocatedVertexBuffers;
	delete m_allocatedIndexBuffers;

	m_allocatedVertexBuffers = NULL;
	m_allocatedIndexBuffers = NULL;

	return TRUE;
}

/*!
	BOOL CRuEngineD3D::InvalidateResourceArena()
	Invalidates vertex and index buffers used by the resource arena
	@return BOOL
	@retval TRUE Invalidation was successful
	@retval FALSE Invalidation failed
*/
BOOL CRuEngineD3D::InvalidateResourceArena()
{
	// Invalidate all render targets
	for(INT32 i = 0; i < m_renderTargets.Count(); ++i)
	{
		m_renderTargets[i]->Invalidate();
	}

	// Invalidate all depth stencil surfaces
	for(INT32 i = 0; i < m_depthStencilSurfaces.Count(); ++i)
	{
		m_depthStencilSurfaces[i]->Invalidate();
	}

	for(INT32 i = 0; i < m_vertexBuffers.Count(); i++)
	{
		// Release managed vertex buffer
		if(m_vertexBuffers[i]->m_vertexBuffer)
		{
			m_vertexBuffers[i]->m_vertexBuffer->Release();
			m_vertexBuffers[i]->m_vertexBuffer = NULL;
		}

		// Clear free list
		m_vertexBuffers[i]->m_freeList.Clear();
	}

	for(INT32 i = 0; i < m_indexBuffers.Count(); i++)
	{
		// Release managed index buffer
		if(m_indexBuffers[i]->m_indexBuffer)
		{
			m_indexBuffers[i]->m_indexBuffer->Release();
			m_indexBuffers[i]->m_indexBuffer = NULL;
		}

		// Clear free list
		m_indexBuffers[i]->m_freeList.Clear();
	}

	// Release managed dynamic vertex buffers
	for(INT32 i = 0; i < m_dynamicVertexBuffers.Count(); ++i)
	{
		// Invalidate the buffer
		m_dynamicVertexBuffers[i]->Invalidate();
	}

	// Release managed dynamic index buffers
	for(INT32 i = 0; i < m_dynamicIndexBuffers.Count(); ++i)
	{
		// Invalidate the buffer
		m_dynamicIndexBuffers[i]->Invalidate();
	}

	if(m_allocatedVertexBuffers)
	{
		// Invalidate all allocated vertex buffers and clear the allocation hash
		m_allocatedVertexBuffers->ForAllElements(ResArenaVBInvalidateCallback, this);
		m_allocatedVertexBuffers->Clear();
	}

	if(m_allocatedIndexBuffers)
	{
		// Invalidate all allocated index buffers and clear the allocation hash
		m_allocatedIndexBuffers->ForAllElements(ResArenaIBInvalidateCallback, this);
		m_allocatedIndexBuffers->Clear();
	}

	// Flag manager as in an invalidated state
	m_resourceArenaInvalidated = TRUE;

	return TRUE;
}

/*!
	BOOL CRuEngineD3D::RestoreResourceArena()
	Restores vertex and index buffers used by the resource arena
	@return BOOL
	@retval TRUE Restoration was successful
	@retval FALSE Restoration failed
*/
BOOL CRuEngineD3D::RestoreResourceArena()
{
	if(m_resourceArenaInvalidated)
	{
		IDirect3DVertexBuffer9 *vb = NULL;
		IDirect3DIndexBuffer9 *ib = NULL;

		// Restore all render targets
		for(INT32 i = 0; i < m_renderTargets.Count(); ++i)
		{
			m_renderTargets[i]->Restore();
		}

		// Recreate depth stencil surfaces
		for(INT32 i = 0; i < m_depthStencilSurfaces.Count(); ++i)
		{
			m_depthStencilSurfaces[i]->Restore();
		}

		// Allocate vertex buffers
		for(INT32 i = 0; i < m_vertexBuffers.Count(); i++)
		{
			if(FAILED(m_d3dDevice->CreateVertexBuffer(m_vertexBuffers[i]->m_bufferSize * m_vbPageSize, D3DUSAGE_WRITEONLY, 0, D3DPOOL_DEFAULT, &vb, NULL)))
//			if(FAILED(m_d3dDevice->CreateVertexBuffer(m_vertexBuffers[i]->m_bufferSize * m_vbPageSize, D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, 0, D3DPOOL_DEFAULT, &vb, NULL)))
			{
				// Handle error..
				MessageBox(NULL, "VB Error", "", MB_OK);
			}
			else
			{
				// Initialize initial free block
				RuMemoryBlock freeBlock;
				freeBlock.m_index = i;
				freeBlock.m_offset = 0;
				freeBlock.m_blockSize = m_vertexBuffers[i]->m_bufferSize;

				m_vertexBuffers[i]->m_vertexBuffer = vb;
				m_vertexBuffers[i]->m_freeList.Add(freeBlock);
			}
		}

		// Allocate index buffers
		for(INT32 i = 0; i < m_indexBuffers.Count(); i++)
		{
			if(FAILED(m_d3dDevice->CreateIndexBuffer(m_indexBuffers[i]->m_bufferSize * m_ibPageSize, D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &ib, NULL)))
//			if(FAILED(m_d3dDevice->CreateIndexBuffer(m_indexBuffers[i]->m_bufferSize * m_ibPageSize, D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &ib, NULL)))
			{
				// Handle error..
				MessageBox(NULL, "IB Error", "", MB_OK);
			}
			else
			{
				// Initialize initial free block
				RuMemoryBlock freeBlock;
				freeBlock.m_index = i;
				freeBlock.m_offset = 0;
				freeBlock.m_blockSize = m_indexBuffers[i]->m_bufferSize;

				m_indexBuffers[i]->m_indexBuffer = ib;
				m_indexBuffers[i]->m_freeList.Add(freeBlock);
			}
		}

		// Restore managed dynamic vertex buffers
		for(INT32 i = 0; i < m_dynamicVertexBuffers.Count(); ++i)
		{
			// Restore the buffer
			m_dynamicVertexBuffers[i]->Restore();
		}

		// Restore managed dynamic index buffers
		for(INT32 i = 0; i < m_dynamicIndexBuffers.Count(); ++i)
		{
			// Restore the buffer
			m_dynamicIndexBuffers[i]->Restore();
		}

		m_resourceArenaInvalidated = FALSE;
	}

	return TRUE;
}

/*!
	IRuVertexDeclaration *CRuEngineD3D::CreateVertexDeclaration(const CRuVertexElement *vertexElements)
	Creates a vertex declaration
	@param vertexElements
	@return IRuVertexDeclaration*
	@retval NULL Could not create the vertex declaration
*/
IRuVertexDeclaration *CRuEngineD3D::CreateVertexDeclaration(const CRuVertexElement *vertexElements)
{
	// Calculate hash value for the vertex element array
	DWORD hashCode = GetHashCode(vertexElements);

	RuVEC vec;
	vec.m_vertexElements = vertexElements;

	// Attempt to find an existing vertex declaration that matches the vertex element pattern
	IRuVertexDeclaration *vertexDeclaration = NULL;
	if(m_vertexDeclarations->Get(vec, hashCode, vertexDeclaration))
	{
		// Increment reference count on the declaration and return it
		vertexDeclaration->AddRef();
		return vertexDeclaration;
	}

	// Create D3D vertex declaration
	IDirect3DVertexDeclaration9	*d3dDecl;
	if(m_d3dDevice->CreateVertexDeclaration((D3DVERTEXELEMENT9 *) vertexElements, &d3dDecl) == D3D_OK)
	{
		// Create Rune vertex declaration
		vertexDeclaration = ruNEW CRuVertexDeclarationD3D(d3dDecl, vertexElements);
		vertexDeclaration->SetEngine(this);

		// Release local reference to the d3d declaration
		d3dDecl->Release();

		// Add vertex declaration to the hash
		INT32 numVertexElements = GetNumVertexElements(vertexElements) + 1;
		vec.m_vertexElements = new CRuVertexElement [numVertexElements];
		memcpy(const_cast<CRuVertexElement *>(vec.m_vertexElements), vertexElements, sizeof(CRuVertexElement) * numVertexElements);
//		vec.m_vertexElements = vertexElements;
		m_vertexDeclarations->Add(vec, hashCode, vertexDeclaration);

		// Increment reference count on the declaration and return it
		vertexDeclaration->AddRef();
		return vertexDeclaration;
	}

	return NULL;
}

/*!
	IRuVertexBuffer *CRuEngineD3D::CreateVertexBuffer(UINT32 size)
	Creates a static vertex buffer
	@param size The size of the vertex buffer
	@return IRuVertexBuffer*
	@retval NULL Could not create the vertex buffer
*/
IRuVertexBuffer *CRuEngineD3D::CreateVertexBuffer(UINT32 size)
{
	UINT32 numPagesRequired = (size / m_vbPageSize) + (size % m_vbPageSize ? 1 : 0);

	if(m_resourceArenaInvalidated)
		RestoreResourceArena();

	// Find first free block large enough to accommodate the buffer
	for(INT32 i = 0; i < m_vertexBuffers.Count(); i++)
	{
		RuManagedVertexBuffer *curManagedVB = m_vertexBuffers[i];

		for(INT32 j = 0; j < curManagedVB->m_freeList.Count(); j++)
		{
			if(curManagedVB->m_freeList[j].m_blockSize >= numPagesRequired)
			{
				// Create new vertex buffer interface
				UINT32 blockOffset = curManagedVB->m_freeList[j].m_offset * m_vbPageSize;
				CRuVertexBufferD3D *vertexBuffer = ruNEW CRuVertexBufferD3D(curManagedVB->m_vertexBuffer, blockOffset, size, TRUE);
				vertexBuffer->SetEngine(this);

				// Setup allocation block descriptor
				RuMemoryBlock allocatedBlock;
				allocatedBlock.m_deviceBuffer = vertexBuffer;
				allocatedBlock.m_index = i;
				allocatedBlock.m_offset = curManagedVB->m_freeList[j].m_offset;
				allocatedBlock.m_blockSize = numPagesRequired;

				// Place allocation block descriptor onto hash of allocated regions
				DWORD blockKey = (DWORD) vertexBuffer;
				DWORD blockKeyHash = RuGlobalHashGen().GetHashCode(blockKey);
				m_allocatedVertexBuffers->Add(blockKey, blockKeyHash, allocatedBlock);

				// Remove the allocated region from the free list
				curManagedVB->m_freeList[j].m_offset += numPagesRequired;
				curManagedVB->m_freeList[j].m_blockSize -= numPagesRequired;
				if(curManagedVB->m_freeList[j].m_blockSize == 0)
				{
					curManagedVB->m_freeList.RemoveAt(j);
				}

				// Return allocated vertex buffer
				return vertexBuffer;
			}
		}
	}

	// Cannot satisfy request, return NULL
	return NULL;
}

/*!
	IRuVertexBuffer *CRuEngineD3D::CreateDynamicVertexBuffer(UINT32 size)
	Creates a dynamic vertex buffer
	@param size The size of the vertex buffer
	@return IRuVertexBuffer*
	@retval NULL Could not create the vertex buffer
*/
IRuVertexBuffer *CRuEngineD3D::CreateDynamicVertexBuffer(UINT32 size)
{
	IDirect3DVertexBuffer9 *d3dBuffer = NULL;

	// Create D3D buffer
	if(m_d3dDevice->CreateVertexBuffer(size, D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, 0, D3DPOOL_DEFAULT, &d3dBuffer, NULL) == D3D_OK)
	{
		// Construct buffer object
		CRuVertexBufferD3D *buffer = ruNEW CRuVertexBufferD3D(d3dBuffer, TRUE);
		buffer->SetEngine(this);

		// Release local reference to D3D buffer
		d3dBuffer->Release();

		// Append buffer to list of managed buffers
		m_dynamicVertexBuffers.Add(buffer);

		return buffer;
	}

	// Cannot satisfy request, return NULL
	return NULL;
}

/*!
	IRuIndexBuffer *CRuEngineD3D::CreateIndexBuffer(UINT32 size)
	Creates a static index buffer
	@param size The size of the index buffer
	@return IRuIndexBuffer*
	@retval NULL Could not create the index buffer
*/
IRuIndexBuffer *CRuEngineD3D::CreateIndexBuffer(UINT32 size)
{
	UINT32 numPagesRequired = (size / m_ibPageSize) + (size % m_ibPageSize ? 1 : 0);

	if(m_resourceArenaInvalidated)
		RestoreResourceArena();

	// Find first free block large enough to accommodate the buffer
	for(INT32 i = 0; i < m_indexBuffers.Count(); i++)
	{
		RuManagedIndexBuffer *curManagedIB = m_indexBuffers[i];

		for(INT32 j = 0; j < curManagedIB->m_freeList.Count(); j++)
		{
			if(curManagedIB->m_freeList[j].m_blockSize >= numPagesRequired)
			{
				// Create new index buffer interface
				UINT32 blockOffset = curManagedIB->m_freeList[j].m_offset * m_ibPageSize;
				CRuIndexBufferD3D *indexBuffer = ruNEW CRuIndexBufferD3D(curManagedIB->m_indexBuffer, blockOffset, size, TRUE);
				indexBuffer->SetEngine(this);

				// Setup allocation block descriptor
				RuMemoryBlock allocatedBlock;
				allocatedBlock.m_deviceBuffer = indexBuffer;
				allocatedBlock.m_index = i;
				allocatedBlock.m_offset = curManagedIB->m_freeList[j].m_offset;
				allocatedBlock.m_blockSize = numPagesRequired;

				// Place allocation block descriptor onto hash of allocated regions
				DWORD blockKey = (DWORD) indexBuffer;
				DWORD blockKeyHash = RuGlobalHashGen().GetHashCode(blockKey);
				m_allocatedIndexBuffers->Add(blockKey, blockKeyHash, allocatedBlock);

				// Remove the allocated region from the free list
				curManagedIB->m_freeList[j].m_offset += numPagesRequired;
				curManagedIB->m_freeList[j].m_blockSize -= numPagesRequired;
				if(curManagedIB->m_freeList[j].m_blockSize == 0)
				{
					curManagedIB->m_freeList.RemoveAt(j);
				}

				// Return allocated index buffer
				return indexBuffer;
			}
		}
	}

	// Cannot satisfy request, return NULL
	return NULL;
}

/*!
	IRuIndexBuffer *CRuEngineD3D::CreateDynamicIndexBuffer(UINT32 size)
	Creates a dynamic index buffer
	@param size The size of the index buffer
	@return IRuIndexBuffer*
	@retval NULL Could not create the index buffer
*/
IRuIndexBuffer *CRuEngineD3D::CreateDynamicIndexBuffer(UINT32 size)
{
	IDirect3DIndexBuffer9 *d3dBuffer = NULL;

	// Create D3D buffer
	if(m_d3dDevice->CreateIndexBuffer(size, D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &d3dBuffer, NULL) == D3D_OK)
	{
		// Construct buffer object
		CRuIndexBufferD3D *buffer = ruNEW CRuIndexBufferD3D(d3dBuffer, TRUE);
		buffer->SetEngine(this);

		// Release local reference to D3D buffer
		d3dBuffer->Release();

		// Append buffer to list of managed buffers
		m_dynamicIndexBuffers.Add(buffer);

		return buffer;
	}

	// Cannot satisfy request, return NULL
	return NULL;
}

/*!
	IRuTexture *CRuEngineD3D::CreateTexture(UINT32 width, UINT32 height, UINT32 mipLevels, RuSurfaceFormat surfaceFormat, RuResourcePool resourcePool)
	Creates a 1D or 2D texture
	@param width
	@param height
	@param mipLevels
	@param surfaceFormat
	@param resourcePool
	@return IRuTexture*
	@retval NULL Could not create the texture
*/
IRuTexture *CRuEngineD3D::CreateTexture(UINT32 width, UINT32 height, UINT32 mipLevels, RuSurfaceFormat surfaceFormat, RuResourcePool resourcePool)
{
	IDirect3DTexture9 *d3dTexture = NULL;

	if(m_d3dDevice->CreateTexture(width, height, mipLevels, 0, (D3DFORMAT) surfaceFormat, (D3DPOOL) resourcePool, &d3dTexture, NULL) == D3D_OK)
	{
		CRuTextureD3D *texture = ruNEW CRuTextureD3D(d3dTexture, FALSE);
		texture->SetEngine(this);

		return texture;
	}

	return NULL;
}

/*!
	IRuTexture *CRuEngineD3D::CreateVolumeTexture(UINT32 width, UINT32 height, UINT32 depth, UINT32 mipLevels, RuSurfaceFormat surfaceFormat, RuResourcePool resourcePool)
	Creates a volume texture
	@param width
	@param height
	@param depth
	@param mipLevels
	@param surfaceFormat
	@param resourcePool
	@return IRuTexture*
	@retval NULL Could not create the texture
*/
IRuVolumeTexture *CRuEngineD3D::CreateVolumeTexture(UINT32 width, UINT32 height, UINT32 depth, UINT32 mipLevels, RuSurfaceFormat surfaceFormat, RuResourcePool resourcePool)
{
	IDirect3DVolumeTexture9 *d3dTexture = NULL;

	if(m_d3dDevice->CreateVolumeTexture(width, height, depth, mipLevels, 0, (D3DFORMAT) surfaceFormat, (D3DPOOL) resourcePool, &d3dTexture, NULL) == D3D_OK)
	{
		CRuVolumeTextureD3D *texture = ruNEW CRuVolumeTextureD3D(d3dTexture, FALSE);
		texture->SetEngine(this);

		return texture;
	}

	return NULL;
}

/*!
	IRuTexture *CRuEngineD3D::CreateRenderTarget(UINT32 width, UINT32 height, UINT32 mipLevels, RuSurfaceFormat surfaceFormat)
	Creates a render target
	@param width
	@param height
	@param mipLevels
	@param surfaceFormat
	@return IRuTexture*
	@retval NULL Could not create the render target
*/
IRuTexture *CRuEngineD3D::CreateRenderTarget(UINT32 width, UINT32 height, UINT32 mipLevels, RuSurfaceFormat surfaceFormat)
{
	IDirect3DTexture9 *d3dTexture = NULL;

	if(m_d3dDevice->CreateTexture(width, height, mipLevels, D3DUSAGE_RENDERTARGET, (D3DFORMAT) surfaceFormat, D3DPOOL_DEFAULT, &d3dTexture, NULL) == D3D_OK)
	{
		// Create the texture object
		CRuTextureD3D *texture = ruNEW CRuTextureD3D(d3dTexture, FALSE);
		texture->SetEngine(this);

		// Increment reference count of the texture object, since the engine needs a reference to it
		texture->AddRef();

		// Add to list of render targets
		m_renderTargets.Add(texture);

		return texture;
	}

	return NULL;
}

/*!
	IRuTexture *CRuEngineD3D::CreateMatchedRenderTarget(UINT32 mipLevels, RuSurfaceFormat surfaceFormat)
	Creates a render target matched to the dimensions of the back buffer
	@param mipLevels
	@param surfaceFormat
	@return IRuTexture*
	@retval NULL Could not create the render target
*/
IRuTexture *CRuEngineD3D::CreateMatchedRenderTarget(UINT32 mipLevels, RuSurfaceFormat surfaceFormat)
{
	IDirect3DTexture9 *d3dTexture = NULL;

	// Retrieve information about the back buffer
	D3DSURFACE_DESC bbDesc;
	IDirect3DSurface9 *backBuffer = NULL;
	if(m_d3dDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backBuffer) == D3D_OK)
	{
		backBuffer->GetDesc(&bbDesc);
		backBuffer->Release();
	}

	// Create the D3D texture
	if(m_d3dDevice->CreateTexture(bbDesc.Width, bbDesc.Height, mipLevels, D3DUSAGE_RENDERTARGET, (D3DFORMAT) surfaceFormat, D3DPOOL_DEFAULT, &d3dTexture, NULL) == D3D_OK)
	{
		// Create the texture object
		CRuTextureD3D *texture = ruNEW CRuTextureD3D(d3dTexture, FALSE, TRUE);
		texture->SetEngine(this);

		// Increment reference count of the texture object, since the engine needs a reference to it
		texture->AddRef();

		// Add to list of render targets
		m_renderTargets.Add(texture);

		return texture;
	}

	return NULL;
}

/*!
	BOOL CRuEngineD3D::DestroyVertexDeclaration(IRuVertexDeclaration *vertexDeclaration)
	Technically does nothing, since there is no bookkeeping required for vertex declarations
	@param IRuVertexDeclaration* Pointer to the vertex declaration
	@return BOOL
	@retval TRUE The destruction succeeded
	@retval FALSE The destruction failed
*/
BOOL CRuEngineD3D::DestroyVertexDeclaration(IRuVertexDeclaration *vertexDeclaration)
{
	return TRUE;
}

/*!
	BOOL CRuEngineD3D::DestroyVertexBuffer(IRuVertexBuffer *vertexBuffer)
	Destroys the vertex buffer. If the vertex buffer is managed internally by the engine, resources held
	by the buffer are returned to the allocation area. If the vertex buffer is an independent vertex
	buffer, it is destroyed immediately.
	@param IRuVertexBuffer* Pointer to the vertex buffer to destroy
	@return BOOL
	@retval TRUE The destruction succeeded
	@retval FALSE The destruction failed
*/
BOOL CRuEngineD3D::DestroyVertexBuffer(IRuVertexBuffer *vertexBuffer)
{
	DWORD blockKey = (DWORD) vertexBuffer;
	DWORD blockKeyHash = RuGlobalHashGen().GetHashCode(blockKey);
	RuMemoryBlock deletedBlock;

	// Find the interface allocation block
	if(m_allocatedVertexBuffers->Get(blockKey, blockKeyHash, deletedBlock))
	{
		// Place back onto free list
		RuManagedVertexBuffer *managedVB = m_vertexBuffers[deletedBlock.m_index];

		// Find the position at which to insert the freed block
		INT32 insertIndex, prevIndex;
		for(insertIndex = 0; insertIndex < managedVB->m_freeList.Count(); insertIndex++)
		{
			if(managedVB->m_freeList[insertIndex].m_offset > deletedBlock.m_offset)
			{
				break;
			}
		}
		prevIndex = insertIndex - 1;

		// Try to merge freed block with the block immediately before
		if(insertIndex > 0 && (managedVB->m_freeList[prevIndex].m_offset + managedVB->m_freeList[prevIndex].m_blockSize) == deletedBlock.m_offset)
		{
			managedVB->m_freeList[prevIndex].m_blockSize += deletedBlock.m_blockSize;

			// Try to merge the newly merged block with the block immediately after
			if(insertIndex < managedVB->m_freeList.Count() && (managedVB->m_freeList[prevIndex].m_offset + managedVB->m_freeList[prevIndex].m_blockSize) == managedVB->m_freeList[insertIndex].m_offset)
			{
				managedVB->m_freeList[prevIndex].m_blockSize += managedVB->m_freeList[insertIndex].m_blockSize;
				managedVB->m_freeList.RemoveAt(insertIndex);
			}
		}
		// Try to merge freed block with the block immediately after
		else if(insertIndex < managedVB->m_freeList.Count() && managedVB->m_freeList[insertIndex].m_offset == (deletedBlock.m_offset + deletedBlock.m_blockSize))
		{
			managedVB->m_freeList[insertIndex].m_offset -= deletedBlock.m_blockSize;
			managedVB->m_freeList[insertIndex].m_blockSize += deletedBlock.m_blockSize;
		}
		// The freed block cannot be merged
		else
		{
			RuMemoryBlock freedBlock;
			freedBlock.m_index = deletedBlock.m_index;
			freedBlock.m_offset = deletedBlock.m_offset;
			freedBlock.m_blockSize = deletedBlock.m_blockSize;
			managedVB->m_freeList.Insert(insertIndex, freedBlock);
		}

		// Delete the buffer
		m_allocatedVertexBuffers->Remove(blockKey, blockKeyHash);

		return TRUE;
	}

	// Buffer is not a pooled object, search through the dynamic buffers list
	for(INT32 i = 0; i < m_dynamicVertexBuffers.Count(); ++i)
	{
		if(m_dynamicVertexBuffers[i] == vertexBuffer)
		{
			m_dynamicVertexBuffers.RemoveAt(i);

			return TRUE;
		}
	}

	//	MessageBox(NULL, "UH OH", "", MB_OK);

	return FALSE;
}

/*!
	BOOL CRuEngineD3D::DestroyIndexBuffer(IRuIndexBuffer *indexBuffer)
	Destroys the index buffer. If the index buffer is managed internally by the engine, resources held
	by the buffer are returned to the allocation area. If the index buffer is an independent index
	buffer, it is destroyed immediately.
	@param IRuIndexBuffer* Pointer to the index buffer to destroy
	@return BOOL
	@retval TRUE The destruction succeeded
	@retval FALSE The destruction failed
*/
BOOL CRuEngineD3D::DestroyIndexBuffer(IRuIndexBuffer *indexBuffer)
{
	DWORD blockKey = (DWORD) indexBuffer;
	DWORD blockKeyHash = RuGlobalHashGen().GetHashCode(blockKey);
	RuMemoryBlock deletedBlock;

	// Find the interface allocation block
	if(m_allocatedIndexBuffers->Get(blockKey, blockKeyHash, deletedBlock))
	{
		// Place back onto free list
		RuManagedIndexBuffer *managedIB = m_indexBuffers[deletedBlock.m_index];

		// Find the position at which to insert the freed block
		INT32 insertIndex, prevIndex;
		for(insertIndex = 0; insertIndex < managedIB->m_freeList.Count(); insertIndex++)
		{
			if(managedIB->m_freeList[insertIndex].m_offset > deletedBlock.m_offset)
			{
				break;
			}
		}
		prevIndex = insertIndex - 1;

		// Try to merge freed block with the block immediately before
		if(insertIndex > 0 && (managedIB->m_freeList[prevIndex].m_offset + managedIB->m_freeList[prevIndex].m_blockSize) == deletedBlock.m_offset)
		{
			managedIB->m_freeList[prevIndex].m_blockSize += deletedBlock.m_blockSize;

			// Try to merge the newly merged block with the block immediately after
			if(insertIndex < managedIB->m_freeList.Count() && (managedIB->m_freeList[prevIndex].m_offset + managedIB->m_freeList[prevIndex].m_blockSize) == managedIB->m_freeList[insertIndex].m_offset)
			{
				managedIB->m_freeList[prevIndex].m_blockSize += managedIB->m_freeList[insertIndex].m_blockSize;
				managedIB->m_freeList.RemoveAt(insertIndex);
			}
		}
		// Try to merge freed block with the block immediately after
		else if(insertIndex < managedIB->m_freeList.Count() && managedIB->m_freeList[insertIndex].m_offset == (deletedBlock.m_offset + deletedBlock.m_blockSize))
		{
			managedIB->m_freeList[insertIndex].m_offset -= deletedBlock.m_blockSize;
			managedIB->m_freeList[insertIndex].m_blockSize += deletedBlock.m_blockSize;
		}
		// The freed block cannot be merged
		else
		{
			RuMemoryBlock freedBlock;
			freedBlock.m_index = deletedBlock.m_index;
			freedBlock.m_offset = deletedBlock.m_offset;
			freedBlock.m_blockSize = deletedBlock.m_blockSize;
			managedIB->m_freeList.Insert(insertIndex, freedBlock);
		}

		// Delete the buffer
		m_allocatedIndexBuffers->Remove(blockKey, blockKeyHash);

		return TRUE;
	}

	// Buffer is not a pooled object, search through the dynamic buffers list
	for(INT32 i = 0; i < m_dynamicIndexBuffers.Count(); ++i)
	{
		if(m_dynamicIndexBuffers[i] == indexBuffer)
		{
			m_dynamicIndexBuffers.RemoveAt(i);

			return TRUE;
		}
	}

	//	MessageBox(NULL, "UH OH 2", "", MB_OK);

	return FALSE;
}

/*!
	BOOL CRuEngineD3D::DestroyTexture(IRuTexture *texture)
	Destroys the texture immediately.
	@param IRuTexture* Pointer to the texture to destroy
	@return BOOL
	@retval TRUE The destruction succeeded
	@retval FALSE The destruction failed
*/
BOOL CRuEngineD3D::DestroyTexture(IRuTexture *texture)
{
	return TRUE;
}

IRuSurface *CRuEngineD3D::CreateDepthStencilSurface(UINT32 width, UINT32 height, RuSurfaceFormat surfaceFormat)
{
	// Create depth stencil surface
	IDirect3DSurface9 *depthStencilSurface = NULL;
	HRESULT hr = m_d3dDevice->CreateDepthStencilSurface(width, height, (D3DFORMAT) surfaceFormat, D3DMULTISAMPLE_NONE, 0, TRUE, &depthStencilSurface, NULL);
	if(FAILED(hr))
		return NULL;

	// Create depth stencil surface object
	CRuSurfaceD3D *surfaceObject = ruNEW CRuSurfaceD3D(depthStencilSurface);

	// Increment reference count since the engine needs a reference to the surface
	surfaceObject->AddRef();

	// Add to list of depth stencil surfaces
	m_depthStencilSurfaces.Add(surfaceObject);

	return surfaceObject;
}

BOOL CRuEngineD3D::DestroyDepthStencilSurface(IRuSurface *surface)
{
	return FALSE;
}

BOOL CRuEngineD3D::CopyTexture(IRuEngineResource *srcTexture, IRuEngineResource *destTexture, INT32 surfLevel)
{
	HRESULT retVal = E_FAIL;

	if(srcTexture->GetType() == CRuTextureD3D::Type && destTexture->GetType() == CRuTextureD3D::Type)
	{
		IDirect3DTexture9 *d3dSrcTex = static_cast<CRuTextureD3D *>(srcTexture)->GetIDirect3DTexture();
		IDirect3DTexture9 *d3dDestTex = static_cast<CRuTextureD3D *>(destTexture)->GetIDirect3DTexture();

		if(d3dSrcTex && d3dDestTex)
		{
			IDirect3DSurface9 *srcSurface = NULL;
			IDirect3DSurface9 *destSurface = NULL;

			d3dSrcTex->GetSurfaceLevel(surfLevel, &srcSurface);
			d3dDestTex->GetSurfaceLevel(surfLevel, &destSurface);

			if(srcSurface && destSurface)
			{
				retVal = m_d3dDevice->StretchRect(srcSurface, NULL, destSurface, NULL, D3DTEXF_NONE);
			}

			// Release surfaces
			srcSurface ? srcSurface->Release() : 0;
			destSurface ? destSurface->Release() : 0;
		}
	}
	else if(srcTexture->GetType() == CRuSwapChainD3D::Type && destTexture->GetType() == CRuTextureD3D::Type)
	{
		IDirect3DTexture9 *d3dDestTex = static_cast<CRuTextureD3D *>(destTexture)->GetIDirect3DTexture();

		if(d3dDestTex)
		{
			IDirect3DSurface9 *srcSurface = NULL;
			IDirect3DSurface9 *destSurface = NULL;

			srcSurface = static_cast<CRuSwapChainD3D *>(srcTexture)->GetIDirect3DSurface();
			d3dDestTex->GetSurfaceLevel(surfLevel, &destSurface);

			if(srcSurface && destSurface)
			{
				retVal = m_d3dDevice->StretchRect(srcSurface, NULL, destSurface, NULL, D3DTEXF_NONE);
			}

			// Release surfaces
			destSurface ? destSurface->Release() : 0;
		}
	}

	return retVal == D3D_OK;
}

/*!
	UINT32 CRuEngineD3D::Render(UINT32 numVertices, UINT32 numPrimitives, RuPrimitiveType primType)
	Renders the currently selected buffers.
	@param numVertices
	@param numPrimitives
	@param primType
	@return UINT32 The number of primitives rendered
*/
UINT32 CRuEngineD3D::Render(UINT32 numVertices, UINT32 numPrimitives, RuPrimitiveType primType)
{
	// Flush all state changes to device
	FlushStateChanges();

	switch(primType)
	{
		case ruPRIMTYPE_LINELIST:
			m_d3dDevice->DrawIndexedPrimitive(D3DPT_LINELIST, 0, 0, numVertices, 0, numPrimitives);
			return numPrimitives;

		case ruPRIMTYPE_TRIANGLELIST:
			m_d3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, numVertices, 0, numPrimitives);
			return numPrimitives;

		case ruPRIMTYPE_POINTLIST:
			m_d3dDevice->DrawPrimitive(D3DPT_POINTLIST, 0, numVertices);
			return numVertices;
	}

	return 0;
}

/*!
	IRuSwapChain *CRuEngineD3D::CreateSwapChain(UINT32 width, UINT32 height, HWND focusWindow)
	Creates a new swap chain
	@param width
	@param height
	@param focusWindow Window to which the swap chain is attached
	@return IRuSwapChain*
	@retval NULL Operation failed
*/
IRuSwapChain *CRuEngineD3D::CreateSwapChain(UINT32 width, UINT32 height, HWND deviceWindow)
{
	IDirect3DSwapChain9 *d3dSwapChain = NULL;
	CRuSwapChainD3D *swapChain = NULL;
	D3DPRESENT_PARAMETERS presentParams;
	HRESULT hr;

	// If there are no swap chains, this is the primary swap chain
	if(m_swapChains.Count() == 0)
	{
		RuDeviceInfoD3D *deviceInfo = m_engineSetting.DeviceInfo();

		// Build present parameters
		m_engineSetting.m_isWindowed = TRUE;			// Force windowed mode on creation of the swap chain
		m_engineSetting.m_windowedWidth = width;
		m_engineSetting.m_windowedHeight = height;
		m_engineSetting.BuildPresentParamsFromSettings(deviceWindow, presentParams);

		DWORD behaviorFlags;

		if (m_engineSetting.EngineMode() == ruENGINEMODE_SOFTWARE)
			behaviorFlags = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
		else if (m_engineSetting.EngineMode() == ruENGINEMODE_MIXED)
			behaviorFlags = D3DCREATE_MIXED_VERTEXPROCESSING;
		else if (m_engineSetting.EngineMode() == ruENGINEMODE_HARDWARE)
			behaviorFlags = D3DCREATE_HARDWARE_VERTEXPROCESSING;
		else if (m_engineSetting.EngineMode() == ruENGINEMODE_PURE)
			behaviorFlags = D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_PUREDEVICE;
		else
			behaviorFlags = 0; // TODO: throw exception

		// Force D3D to preserve FPU precision setting
		behaviorFlags |= D3DCREATE_FPU_PRESERVE;

		// Create the device
		for(UINT i = 0; i < m_d3dObject->GetAdapterCount(); ++i)
		{
			D3DADAPTER_IDENTIFIER9 ident;
			m_d3dObject->GetAdapterIdentifier(i,0, &ident);
			if(strcmp(ident.Description, "NVIDIA NVPerfHUD") == 0)
			{
				hr = m_d3dObject->CreateDevice(i, D3DDEVTYPE_REF, m_primaryWindow, behaviorFlags, &presentParams, &m_d3dDevice);
				if(FAILED(hr))
					return NULL;

				break;
			}
		}

		if(m_d3dDevice == NULL)
		{
			hr = m_d3dObject->CreateDevice(m_engineSetting.GetAdapterOrdinal(), deviceInfo->m_deviceType, m_primaryWindow, behaviorFlags, &presentParams, &m_d3dDevice);
			if(FAILED(hr))
				return NULL;
		}

		// Retrieve the device's caps
		if(FAILED(hr = m_d3dDevice->GetDeviceCaps(&m_d3dDeviceCaps)))
			return NULL;

		// Retrieve Direct3D's implicit swap chain
		if(FAILED(hr = m_d3dDevice->GetSwapChain(0, &d3dSwapChain)))
			return NULL;

		// Retrieve Direct3D's implicit depth stencil surface
		if(FAILED(hr = m_d3dDevice->GetDepthStencilSurface(&m_defaultDepthStencilSurface)))
			return NULL;

		// Create swap chain object
		swapChain = ruNEW CRuSwapChainD3D(d3dSwapChain);
		swapChain->SetEngine(this);

		// Increment swap chain reference count by one, since the engine itself needs the reference
		swapChain->AddRef();

		// Add swap chain to the swap chain list
		m_swapChains.Add(swapChain);
	}
	else
	{
		// Build present parameters
		m_engineSetting.BuildPresentParamsForWindowed(deviceWindow, width, height, presentParams);

		// Create additional swap chain
		if(FAILED(hr = m_d3dDevice->CreateAdditionalSwapChain(&presentParams, &d3dSwapChain)))
			return NULL;

		// Create swap chain object
		swapChain = ruNEW CRuSwapChainD3D(d3dSwapChain);
		swapChain->SetEngine(this);

		// Add swap chain to the swap chain list
		m_swapChains.Add(swapChain);
	}

	return swapChain;
}

/*!
	BOOL CRuEngineD3D::DestroySwapChain(IRuSwapChain *swapChain)
	Destroys the given swap chain
	@param swapChain Swap chain to destroy
	@return BOOL
	@retval TRUE Operation succeeded
	@retval FALSE Operation failed
*/
BOOL CRuEngineD3D::DestroySwapChain(IRuSwapChain *swapChain)
{
	// Ensure the swap chain is owned by the engine
	if(m_swapChains.Contains(static_cast<CRuSwapChainD3D *>(swapChain)))
	{
		// Remove the swap chain from the swap chain list
		UINT32 swapChainIndex = m_swapChains.IndexOf(static_cast<CRuSwapChainD3D *>(swapChain));
		m_swapChains.RemoveAt(swapChainIndex);

		return TRUE;
	}

	return FALSE;
}

/*!
	BOOL CRuEngineD3D::SetResolution(IRuSwapChain *swapChain, UINT32 width, UINT32 height, BOOL fullscreen, HWND deviceWindow)
	Sets the resolution for the given swap chain
	@param swapChain Swap chain to set resolution on
	@param width
	@param height
	@param fullscreen Whether to set the swap chain into fullscreen mode, only valid for the primary swap chain
	@param deviceWindow If non-NULL, designates the new device window for the swap chain
	@return BOOL
	@retval TRUE Operation succeeded
	@retval FALSE Operation failed
*/
BOOL CRuEngineD3D::SetResolution(IRuSwapChain *swapChain, UINT32 width, UINT32 height, BOOL fullscreen, HWND deviceWindow)
{
	HRESULT hr;
	IDirect3DSwapChain9 *d3dSwapChain = NULL;

	// Ensure the swap chain is owned by the engine
	if(m_swapChains.Contains(static_cast<CRuSwapChainD3D *>(swapChain)))
	{
		UINT32 swapChainIndex = m_swapChains.IndexOf(static_cast<CRuSwapChainD3D *>(swapChain));

		// Release implicit depth stencil surface
		if(m_defaultDepthStencilSurface)
		{
			m_defaultDepthStencilSurface->Release();
			m_defaultDepthStencilSurface = NULL;
		}

		// Invalidate all swap chains
		for(INT32 i = 0; i < m_swapChains.Count(); i++)
		{
			m_swapChains[i]->Invalidate();
		}

		// Invalidate resource arena
		InvalidateResourceArena();

		m_swapChains[swapChainIndex]->PresentParameters().BackBufferWidth = width;
		m_swapChains[swapChainIndex]->PresentParameters().BackBufferHeight = height;
		m_swapChains[swapChainIndex]->PresentParameters().Windowed = TRUE;

		if(deviceWindow != NULL)
		{
			m_swapChains[swapChainIndex]->PresentParameters().hDeviceWindow = deviceWindow;
		}

		// If fullscreen mode is enabled, set its properties and find the best display mode
		if(swapChainIndex == 0 && fullscreen)
		{
			m_swapChains[swapChainIndex]->PresentParameters().Windowed = FALSE;

			// Setup resolution selection
			m_appMinFullscreenWidth = width;
			m_appMinFullscreenHeight = height;

			m_engineSetting.m_isWindowed = FALSE;
			m_engineSetting.m_preferredFullscreenWidth = m_appMinFullscreenWidth;
			m_engineSetting.m_preferredFullscreenHeight = m_appMinFullscreenHeight;
			m_engineSetting.FindBestFullscreenMode();
		}

		// Reset D3D device
		m_d3dDevice->Reset(&m_swapChains[0]->PresentParameters());

		// Retrieve Direct3D's implicit depth stencil surface
		if(FAILED(hr = m_d3dDevice->GetDepthStencilSurface(&m_defaultDepthStencilSurface)))
			return NULL;

		// Restore primary swap chain
		m_d3dDevice->GetSwapChain(0, &d3dSwapChain);
		m_swapChains[0]->SetD3DSwapChain(d3dSwapChain);

		// Recreate secondary swap chains
		for(INT32 i = 1; i < m_swapChains.Count(); i++)
		{
			// Create D3D swap chain
			hr = m_d3dDevice->CreateAdditionalSwapChain(&m_swapChains[i]->PresentParameters(), &d3dSwapChain);
			if(FAILED(hr))
				return FALSE;

			if(!m_swapChains[i]->SetD3DSwapChain(d3dSwapChain))
				return FALSE;
		}

		// Restore resource arena
		RestoreResourceArena();

		return TRUE;
	}

	return FALSE;
}

/*!
	BOOL CRuEngineD3D::GetRenderTargetSize(UINT32 &widthOut, UINT32 &heightOut)
	Retrieves the current render target's (always retrieves RT0) dimensions.
	@param widthOut Width of the current render target
	@param heightOut Height of the current render target
	@return BOOL
	@retval TRUE Operation succeeded
	@retval FALSE Operation failed
*/
BOOL CRuEngineD3D::GetRenderTargetSize(UINT32 &widthOut, UINT32 &heightOut)
{
	if(m_d3dDevice == NULL)
		return FALSE;

	IDirect3DSurface9 *renderTarget;
	if(m_d3dDevice->GetRenderTarget(0, &renderTarget) == D3D_OK)
	{
		// Get surface description
		D3DSURFACE_DESC surfDesc;
		if(renderTarget->GetDesc(&surfDesc) == D3D_OK)
		{
			// Store dimensions
			widthOut = surfDesc.Width;
			heightOut = surfDesc.Height;

			// Release the render target interface
			renderTarget->Release();

			return TRUE;
		}

		// Release the render target interface
		renderTarget->Release();

		return FALSE;
	}

	return TRUE;
}

INT32 CRuEngineD3D::Metric_GetNumStateChanges()
{
	return m_numStateChanges;
}

void CRuEngineD3D::Metric_OutputAllocationReport()
{
	char outputString[1024];

	RuEngineMetric engineMetric;
	memset(&engineMetric, 0, sizeof(engineMetric));

	// Get metrics for managed buffers
	for(INT32 i = 0; i < m_vertexBuffers.Count(); ++i)
	{
		engineMetric.m_vb_TotalPages += m_vertexBuffers[i]->m_bufferSize;
	}
	engineMetric.m_vb_TotalBytes = engineMetric.m_vb_TotalPages * m_vbPageSize;
	
	for(INT32 i = 0; i < m_indexBuffers.Count(); ++i)
	{
		engineMetric.m_ib_TotalPages += m_indexBuffers[i]->m_bufferSize;
	}
	engineMetric.m_ib_TotalBytes = engineMetric.m_ib_TotalPages * m_ibPageSize;

	// Get metrics for vertex and index buffer allocation
	m_allocatedVertexBuffers->ForAllElements(ResArenaVBCollectMetricCallback, &engineMetric);
	m_allocatedIndexBuffers->ForAllElements(ResArenaIBCollectMetricCallback, &engineMetric);

	engineMetric.m_vb_CommittedBytes = engineMetric.m_vb_CommittedPages * m_vbPageSize;
	engineMetric.m_ib_CommittedBytes = engineMetric.m_ib_CommittedPages * m_ibPageSize;
	engineMetric.m_vb_PercentWaste = 1.0f - (REAL) engineMetric.m_vb_UtilizedBytes / engineMetric.m_vb_CommittedBytes;
	engineMetric.m_ib_PercentWaste = 1.0f - (REAL) engineMetric.m_ib_UtilizedBytes / engineMetric.m_ib_CommittedBytes;


	OutputDebugString("------------------------------------------------------------------------------------------------\n");
	OutputDebugString("RESOURCE ARENA ALLOCATION REPORT\n");
	OutputDebugString("------------------------------------------------------------------------------------------------\n");
	OutputDebugString("                    Total     Total               Committed Committed Utilized         \n");
	OutputDebugString("Pool                Pages     Bytes     Alloc       Pages     Bytes    Bytes    % Waste\n");
	OutputDebugString("------------------------------------------------------------------------------------------------\n");
	sprintf(outputString, "Static VB		   %10d%10d%10d%10d%10d%10d%10f\n", engineMetric.m_vb_TotalPages, engineMetric.m_vb_TotalBytes, engineMetric.m_vb_Allocations, engineMetric.m_vb_CommittedPages, engineMetric.m_vb_CommittedBytes, engineMetric.m_vb_UtilizedBytes, engineMetric.m_vb_PercentWaste);
	OutputDebugString(outputString);
	sprintf(outputString, "Static IB		   %10d%10d%10d%10d%10d%10d%10f\n", engineMetric.m_ib_TotalPages, engineMetric.m_ib_TotalBytes, engineMetric.m_ib_Allocations, engineMetric.m_ib_CommittedPages, engineMetric.m_ib_CommittedBytes, engineMetric.m_ib_UtilizedBytes, engineMetric.m_ib_PercentWaste);
	OutputDebugString(outputString);
	OutputDebugString("------------------------------------------------------------------------------------------------\n");
}

/*!
	BOOL CRuEngineD3D::EnumerateAdapters()
	Enumerates available adapters and their associated display mode combinations
	@return BOOL
	@retval TRUE The enumeration succeeded
	@retval FALSE The enumeration failed
*/
BOOL CRuEngineD3D::EnumerateAdapters()
{
	CRuArrayList<D3DFORMAT> adapterFormatList;

	if(m_d3dObject == NULL)
		return FALSE;

	// Setup list of allowable adapter formats
	m_allowedAdapterFormatList.Add(D3DFMT_X8R8G8B8);
	m_allowedAdapterFormatList.Add(D3DFMT_X1R5G5B5);
	m_allowedAdapterFormatList.Add(D3DFMT_R5G6B5);
	m_allowedAdapterFormatList.Add(D3DFMT_A2R10G10B10);

	// Iterate through every adapter and enumerate each one
	RuAdapterInfoD3D *adapterInfo = NULL;
	UINT32 numAdapters = m_d3dObject->GetAdapterCount();

	for(UINT32 adapterOrdinal = 0; adapterOrdinal < numAdapters; adapterOrdinal++)
	{
		// Allocate new adapter info structure
		adapterInfo = ruNEW RuAdapterInfoD3D();
		if(adapterInfo == NULL)
			return NULL;

		// Setup adapter ordinal and identifier
		adapterInfo->m_adapterOrdinal = adapterOrdinal;
		m_d3dObject->GetAdapterIdentifier(adapterOrdinal, 0, &adapterInfo->m_adapterIdentifier);

		// Enumerate all display modes on this adapter
		adapterFormatList.Clear();
		for(INT32 i = 0; i < m_allowedAdapterFormatList.Count(); i++)
		{
			UINT32 numAdapterModes = m_d3dObject->GetAdapterModeCount(adapterOrdinal, m_allowedAdapterFormatList[i]);
			for(UINT32 mode = 0; mode < numAdapterModes; mode++)
			{
				D3DDISPLAYMODE displayMode;
				m_d3dObject->EnumAdapterModes(adapterOrdinal, m_allowedAdapterFormatList[i], mode, &displayMode);

				// If color channel or display dimensions do not satisfy requirements, skip
				if(	displayMode.Width < m_appMinFullscreenWidth ||
					displayMode.Height < m_appMinFullscreenHeight ||
					ColorChannelBits(displayMode.Format) < m_appMinColorChannelBits)
				{
					continue;
				}

				// Add display mode to the adapter's display mode list
				adapterInfo->m_displayModeList.Add(displayMode);

				// Add display mode's format to the list of all adapter formats
				if(!adapterFormatList.Contains(displayMode.Format))
					adapterFormatList.Add(displayMode.Format);
			}
		}
		// Sort display mode list
		qsort(adapterInfo->m_displayModeList.GetArray(), adapterInfo->m_displayModeList.Count(), sizeof(D3DDISPLAYMODE), SortModesCallback);

		// Get info for each device on this adapter
		if(!EnumerateDevices(adapterInfo, adapterFormatList))
		{
			delete adapterInfo;
			return FALSE;
		}

		// If at least one device on this adapter is available and satisfies requirements, add the adapterInfo to the list
		if(adapterInfo->m_deviceInfoList.Count() == 0)
			delete adapterInfo;
		else
			m_adapterInfoList.Add(adapterInfo);
	}

	return TRUE;
}

/*!
	BOOL CRuEngineD3D::SelectInitialDisplaySettings(BOOL fullscreen)
	Selects suitable initial display settings
	@param fullscreen Whether to operate in fullscreen mode
	@return BOOL
	@retval TRUE Selection succeeded
	@retval FALSE Selection failed
*/
BOOL CRuEngineD3D::SelectInitialDisplaySettings(BOOL fullscreen)
{
	BOOL foundFullscreen = m_engineSetting.FindBestFullscreenMode(m_d3dObject, m_adapterInfoList, FALSE, FALSE);
	BOOL foundWindowed = m_engineSetting.FindBestWindowedMode(m_d3dObject, m_adapterInfoList, FALSE, FALSE);

	if(fullscreen && foundFullscreen)
		m_engineSetting.m_isWindowed = FALSE;
	if(!foundWindowed && foundFullscreen)
		m_engineSetting.m_isWindowed = FALSE;

	if(!foundFullscreen && !foundWindowed)
		return FALSE;

	return TRUE;
}

/*!
	BOOL CRuEngineD3D::EnumerateDevices(RuAdapterInfoD3D *adapterInfo, CRuArrayList<D3DFORMAT> &adapterFormatList)
	Enumerates available devices for the specified adapter and format list
	@return BOOL
	@retval TRUE The enumeration succeeded
	@retval FALSE The enumeration failed
*/
BOOL CRuEngineD3D::EnumerateDevices(RuAdapterInfoD3D *adapterInfo, CRuArrayList<D3DFORMAT> &adapterFormatList)
{
	const D3DDEVTYPE devTypeArray[] = { D3DDEVTYPE_HAL, D3DDEVTYPE_SW, D3DDEVTYPE_REF };
	const UINT32 devTypeArrayCount = sizeof(devTypeArray) / sizeof(devTypeArray[0]);

	RuDeviceInfoD3D *deviceInfo = NULL;
	for(UINT32 i = 0; i < devTypeArrayCount; i++)
	{
		// Allocate new device info structure
		deviceInfo = ruNEW RuDeviceInfoD3D();
		if(deviceInfo == NULL)
			return FALSE;

		// Setup device information and retrieve device caps
		deviceInfo->m_adapterOrdinal = adapterInfo->m_adapterOrdinal;
		deviceInfo->m_deviceType = devTypeArray[i];
		if(FAILED(m_d3dObject->GetDeviceCaps(adapterInfo->m_adapterOrdinal, deviceInfo->m_deviceType, &deviceInfo->m_caps)))
		{
			delete deviceInfo;
			continue;
		}

		// Get info for each device combination on this device
		if(FAILED(EnumerateDeviceCombos(deviceInfo, adapterFormatList)))
		{
			delete deviceInfo;
			return FALSE;
		}

		// If there are no compatible device combinations, skip
		if(deviceInfo->m_deviceComboList.Count() == 0)
		{
			delete deviceInfo;
			continue;
		}

		// Add the device info to the list
		adapterInfo->m_deviceInfoList.Add(deviceInfo);
	}

	return TRUE;
}

/*!
	BOOL CRuEngineD3D::EnumerateDeviceCombos(RuDeviceInfoD3D *deviceInfo, CRuArrayList<D3DFORMAT> &adapterFormatList)
	Enumerates available device combinations for the specified device and format list
	@return BOOL
	@retval TRUE The enumeration succeeded
	@retval FALSE The enumeration failed
*/
BOOL CRuEngineD3D::EnumerateDeviceCombos(RuDeviceInfoD3D *deviceInfo, CRuArrayList<D3DFORMAT> &adapterFormatList)
{
	const D3DFORMAT backBufferFormatArray[] =
	{
		D3DFMT_A8R8G8B8,
		D3DFMT_X8R8G8B8,
		D3DFMT_A2R10G10B10,
		D3DFMT_R5G6B5,
		D3DFMT_A1R5G5B5,
		D3DFMT_X1R5G5B5
	};
	const UINT32 backBufferFormatArrayCount = sizeof(backBufferFormatArray) / sizeof(backBufferFormatArray[0]);
	const BOOL isWindowedArray[] = { FALSE, TRUE };

	// Iterate through each format supported by the adapter
	D3DFORMAT adapterFormat;
	for(INT32 iaf = 0; iaf < adapterFormatList.Count(); iaf++)
	{
		adapterFormat = adapterFormatList[iaf];
		D3DFORMAT backBufferFormat;
		for(INT32 ibbf = 0; ibbf < backBufferFormatArrayCount; ibbf++)
		{
			// Skip if the backbuffer does not support the required number of alpha bits
			backBufferFormat = backBufferFormatArray[ibbf];
			if(AlphaChannelBits(backBufferFormat) < m_appMinAlphaChannelBits)
				continue;

			// Iterate through windowed and fullscreen
			BOOL isWindowed;
			for(UINT32 iiw = 0; iiw < 2; iiw++)
			{
				// Skip if the mode does not match the current windowed mode setting
				isWindowed = isWindowedArray[iiw];
				if(!isWindowed && m_appRequiresWindowed)
					continue;
				if(isWindowed && m_appRequiresFullscreen)
					continue;

				// Ensure the formats are compatible
				if(FAILED(m_d3dObject->CheckDeviceType(deviceInfo->m_adapterOrdinal, deviceInfo->m_deviceType, adapterFormat, backBufferFormat, isWindowed)))
					continue;

				// Allocate new device combo structure
				RuDeviceComboD3D *deviceCombo = NULL;
				deviceCombo = ruNEW RuDeviceComboD3D();
				if(deviceCombo == NULL)
					return FALSE;

				// Setup device combo base information
				deviceCombo->m_adapterOrdinal = deviceInfo->m_adapterOrdinal;
				deviceCombo->m_deviceType = deviceInfo->m_deviceType;
				deviceCombo->m_adapterFormat = adapterFormat;
				deviceCombo->m_backBufferFormat = backBufferFormat;
				deviceCombo->m_isWindowed = isWindowed;

				// Build depth buffer list and verify compatibility
				if(m_appUsesDepthBuffer)
				{
					BuildDepthStencilFormatList(deviceCombo);
					if(deviceCombo->m_depthStencilFormatList.Count() == 0)
					{
						delete deviceCombo;
						continue;
					}
				}

				// Build multisampling type list and its conflicts list
				BuildMultisampleTypeList(deviceCombo);
				if(deviceCombo->m_multisampleTypeList.Count() == 0)
				{
					delete deviceCombo;
					continue;
				}
				BuildMultisampleConflictList(deviceCombo);

				// Build engine mode list
				BuildEngineModeList(deviceInfo, deviceCombo);
				if(deviceCombo->m_engineModeList.Count() == 0)
				{
					delete deviceCombo;
					continue;
				}

				// Build presentation interval list
				BuildPresentIntervalList(deviceInfo, deviceCombo);

				// Add device combo to the list
				deviceInfo->m_deviceComboList.Add(deviceCombo);
			}
		}
	}

	return TRUE;
}

/*!
	void CRuEngineD3D::BuildDepthStencilFormatList(RuDeviceComboD3D *deviceCombo)
	Builds the depth/stencil buffer format list for the specified device combo
	@param deviceCombo Device combo to operate on
	@return void
*/
void CRuEngineD3D::BuildDepthStencilFormatList(RuDeviceComboD3D *deviceCombo)
{
	const D3DFORMAT depthStencilFormatArray[] = 
	{
		D3DFMT_D16,
		D3DFMT_D15S1,
		D3DFMT_D24X8,
		D3DFMT_D24S8,
		D3DFMT_D24X4S4,
		D3DFMT_D32,
	};
	const UINT32 depthStencilFormatArrayCount = sizeof(depthStencilFormatArray) / sizeof(depthStencilFormatArray[0]);

	D3DFORMAT depthStencilFormat;
	for(UINT32 idsf = 0; idsf < depthStencilFormatArrayCount; idsf++)
	{
		// Skip if the depth/stencil buffer is not compatible
		depthStencilFormat = depthStencilFormatArray[idsf];
		if(DepthBits(depthStencilFormat) < m_appMinDepthBits)
			continue;
		if(StencilBits(depthStencilFormat) < m_appMinStencilBits)
			continue;

		// Ensure the format combination is actually supported by the device
		if(SUCCEEDED(m_d3dObject->CheckDeviceFormat(deviceCombo->m_adapterOrdinal, deviceCombo->m_deviceType, deviceCombo->m_adapterFormat, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE, depthStencilFormat)))
		{
			if(SUCCEEDED(m_d3dObject->CheckDepthStencilMatch(deviceCombo->m_adapterOrdinal, deviceCombo->m_deviceType, deviceCombo->m_adapterFormat, deviceCombo->m_backBufferFormat, depthStencilFormat)))
			{
				deviceCombo->m_depthStencilFormatList.Add(depthStencilFormat);
			}
		}
	}
}

/*!
	void CRuEngineD3D::BuildMultisampleTypeList(RuDeviceComboD3D *deviceCombo)
	Builds the multisample type list for the specified device combo
	@param deviceCombo Device combo to operate on
	@return void
*/
void CRuEngineD3D::BuildMultisampleTypeList(RuDeviceComboD3D *deviceCombo)
{
	const D3DMULTISAMPLE_TYPE msTypeArray[] =
	{
		D3DMULTISAMPLE_NONE,
		D3DMULTISAMPLE_NONMASKABLE,
		D3DMULTISAMPLE_2_SAMPLES,
		D3DMULTISAMPLE_3_SAMPLES,
		D3DMULTISAMPLE_4_SAMPLES,
		D3DMULTISAMPLE_5_SAMPLES,
		D3DMULTISAMPLE_6_SAMPLES,
		D3DMULTISAMPLE_7_SAMPLES,
		D3DMULTISAMPLE_8_SAMPLES,
		D3DMULTISAMPLE_9_SAMPLES,
		D3DMULTISAMPLE_10_SAMPLES,
		D3DMULTISAMPLE_11_SAMPLES,
		D3DMULTISAMPLE_12_SAMPLES,
		D3DMULTISAMPLE_13_SAMPLES,
		D3DMULTISAMPLE_14_SAMPLES,
		D3DMULTISAMPLE_15_SAMPLES,
		D3DMULTISAMPLE_16_SAMPLES,
	};
	const UINT32 msTypeArrayCount = sizeof(msTypeArray) / sizeof(msTypeArray[0]);

	D3DMULTISAMPLE_TYPE msType;
	DWORD msQuality;
	for(UINT32 imst = 0; imst < msTypeArrayCount; imst++)
	{
		msType = msTypeArray[imst];
		if(SUCCEEDED(m_d3dObject->CheckDeviceMultiSampleType(deviceCombo->m_adapterOrdinal, deviceCombo->m_deviceType, deviceCombo->m_backBufferFormat, deviceCombo->m_isWindowed, msType, &msQuality)))
		{
			deviceCombo->m_multisampleTypeList.Add(msType);
			deviceCombo->m_multisampleQualityList.Add(msQuality);
		}
	}
}

/*!
	void CRuEngineD3D::BuildDSMSConflictList(RuDeviceComboD3D *deviceCombo)
	Builds the multisample conflict list for the specified device combo
	@param deviceCombo Device combo to operate on
	@return void
*/
void CRuEngineD3D::BuildMultisampleConflictList(RuDeviceComboD3D *deviceCombo)
{
	RuMultisampleConflictD3D msConflict;

	for(INT32 ids = 0; ids < deviceCombo->m_depthStencilFormatList.Count(); ids++)
	{
		D3DFORMAT dsFmt = deviceCombo->m_depthStencilFormatList[ids];
		for(INT32 ims = 0; ims < deviceCombo->m_multisampleTypeList.Count(); ims++)
		{
			D3DMULTISAMPLE_TYPE msType = deviceCombo->m_multisampleTypeList[ims];
			if(FAILED(m_d3dObject->CheckDeviceMultiSampleType(deviceCombo->m_adapterOrdinal, deviceCombo->m_deviceType, dsFmt, deviceCombo->m_isWindowed, msType, NULL)))
			{
				msConflict.m_depthStencilFormat = dsFmt;
				msConflict.m_multisampleType = msType;
				deviceCombo->m_multisampleConflictList.Add(msConflict);
			}
		}
	}
}

/*!
	void CRuEngineD3D::BuildEngineModeList(RuDeviceInfoD3D *deviceInfo, RuDeviceComboD3D *deviceCombo)
	Builds the engine mode list for the specified device combo
	@param deviceCombo Device combo to operate on
	@return void
*/
void CRuEngineD3D::BuildEngineModeList(RuDeviceInfoD3D *deviceInfo, RuDeviceComboD3D *deviceCombo)
{
	deviceCombo->m_engineModeList.Add(ruENGINEMODE_HARDWARE);
}

/*!
	void CRuEngineD3D::BuildPresentIntervalList(RuDeviceInfoD3D *deviceInfo, RuDeviceComboD3D *deviceCombo)
	Builds the presentation interval list for the specified device combo
	@param deviceCombo Device combo to operate on
	@return void
*/
void CRuEngineD3D::BuildPresentIntervalList(RuDeviceInfoD3D *deviceInfo, RuDeviceComboD3D *deviceCombo)
{
	const UINT32 piArray[] =
	{
		D3DPRESENT_INTERVAL_IMMEDIATE,
		D3DPRESENT_INTERVAL_DEFAULT,
		D3DPRESENT_INTERVAL_ONE,
		D3DPRESENT_INTERVAL_TWO,
		D3DPRESENT_INTERVAL_THREE,
		D3DPRESENT_INTERVAL_FOUR,
	};
	const UINT32 piArrayCount = sizeof(piArray) / sizeof(piArray[0]);

	UINT32 pi;
	for(UINT32 ipi = 0; ipi < piArrayCount; ipi++)
	{
		pi = piArray[ipi];
		if(deviceCombo->m_isWindowed)
		{
			// These intervals are not supported in windowed mode.
			if(pi == D3DPRESENT_INTERVAL_TWO || pi == D3DPRESENT_INTERVAL_THREE || pi == D3DPRESENT_INTERVAL_FOUR)
			{
				continue;
			}
		}

		// Note that D3DPRESENT_INTERVAL_DEFAULT is zero, so you can't do a caps check for it -- it is always available.
		if(pi == D3DPRESENT_INTERVAL_DEFAULT || (deviceInfo->m_caps.PresentationIntervals & pi))
		{
			deviceCombo->m_presentIntervalList.Add(pi);
		}
	}
}

void CRuEngineD3D::ClearVertexDeclarationCache()
{
	m_vertexDeclarations->ForAllElements(ClearVertexDeclarationCacheCallback, this);
	m_vertexDeclarations->Clear();
}

/*!
	UINT32 CRuEngineD3D::ColorChannelBits(D3DFORMAT format)
	Determines the number of bits per color channel as specified by D3DFORMAT
	@param format Surface format
	@return UINT32 The number of bits per color channel as specified by D3DFORMAT
*/
UINT32 CRuEngineD3D::ColorChannelBits(D3DFORMAT format)
{
	switch(format)
	{
		case D3DFMT_R8G8B8:
			return 8;
		case D3DFMT_A8R8G8B8:
			return 8;
		case D3DFMT_X8R8G8B8:
			return 8;
		case D3DFMT_R5G6B5:
			return 5;
		case D3DFMT_X1R5G5B5:
			return 5;
		case D3DFMT_A1R5G5B5:
			return 5;
		case D3DFMT_A4R4G4B4:
			return 4;
		case D3DFMT_R3G3B2:
			return 2;
		case D3DFMT_A8R3G3B2:
			return 2;
		case D3DFMT_X4R4G4B4:
			return 4;
		case D3DFMT_A2B10G10R10:
			return 10;
		case D3DFMT_A2R10G10B10:
			return 10;
		default:
			return 0;
	}
}

/*!
	UINT32 CRuEngineD3D::AlphaChannelBits(D3DFORMAT format)
	Determines the number of bits in the alpha channel as specified by D3DFORMAT
	@param format Surface format
	@return UINT32 The number of bits in the alpha channel as specified by D3DFORMAT
*/
UINT32 CRuEngineD3D::AlphaChannelBits(D3DFORMAT format)
{
    switch(format)
    {
        case D3DFMT_R8G8B8:
            return 0;
        case D3DFMT_A8R8G8B8:
            return 8;
        case D3DFMT_X8R8G8B8:
            return 0;
        case D3DFMT_R5G6B5:
            return 0;
        case D3DFMT_X1R5G5B5:
            return 0;
        case D3DFMT_A1R5G5B5:
            return 1;
        case D3DFMT_A4R4G4B4:
            return 4;
        case D3DFMT_R3G3B2:
            return 0;
        case D3DFMT_A8R3G3B2:
            return 8;
        case D3DFMT_X4R4G4B4:
            return 0;
        case D3DFMT_A2B10G10R10:
            return 2;
        case D3DFMT_A2R10G10B10:
            return 2;
        default:
            return 0;
    }
}

/*!
	UINT32 CRuEngineD3D::DepthBits(D3DFORMAT format)
	Determines the number of bits in the depth buffer as specified by D3DFORMAT
	@param format Surface format
	@return UINT32 The number of bits in the depth buffer as specified by D3DFORMAT
*/
UINT32 CRuEngineD3D::DepthBits(D3DFORMAT format)
{
	switch(format)
	{
		case D3DFMT_D16:
			return 16;
		case D3DFMT_D15S1:
			return 15;
		case D3DFMT_D24X8:
			return 24;
		case D3DFMT_D24S8:
			return 24;
		case D3DFMT_D24X4S4:
			return 24;
		case D3DFMT_D32:
			return 32;
		default:
			return 0;
	}
}

/*!
	UINT32 CRuEngineD3D::StencilBits(D3DFORMAT format)
	Determines the number of bits in the stencil buffer as specified by D3DFORMAT
	@param format Surface format
	@return UINT32 The number of bits in the stencil buffer as specified by D3DFORMAT
*/
UINT32 CRuEngineD3D::StencilBits(D3DFORMAT format)
{
	switch(format)
	{
		case D3DFMT_D16:
			return 0;
		case D3DFMT_D15S1:
			return 1;
		case D3DFMT_D24X8:
			return 0;
		case D3DFMT_D24S8:
			return 8;
		case D3DFMT_D24X4S4:
			return 4;
		case D3DFMT_D32:
			return 0;
		default:
			return 0;
	}
}

/*!
	int CRuEngineD3D::SortModesCallback(const void *arg1, const void *arg2)
	Comparison callback used for sorting display modes with qsort()
	@param arg1 Element 1
	@param arg2 Element 2
	@return int Comparison result
*/
int CRuEngineD3D::SortModesCallback(const void *arg1, const void *arg2)
{
	D3DDISPLAYMODE *pdm1 = (D3DDISPLAYMODE *) arg1;
	D3DDISPLAYMODE *pdm2 = (D3DDISPLAYMODE *) arg2;

	if(pdm1->Width > pdm2->Width)
		return 1;

	if(pdm1->Width < pdm2->Width)
		return -1;

	if(pdm1->Height > pdm2->Height)
		return 1;

	if(pdm1->Height < pdm2->Height)
		return -1;

	if(pdm1->Format > pdm2->Format)
		return 1;

	if(pdm1->Format < pdm2->Format)
		return -1;

	if(pdm1->RefreshRate > pdm2->RefreshRate)
		return 1;

	if(pdm1->RefreshRate < pdm2->RefreshRate)
		return -1;

	return 0;
}

BOOL CRuEngineD3D::ResArenaVBCollectMetricCallback(const void *key, const void *data, void *userData)
{
	RuMemoryBlock memoryBlock = *reinterpret_cast<RuMemoryBlock *>(const_cast<void *>(data));
	RuEngineMetric *engineMetric = reinterpret_cast<RuEngineMetric *>(const_cast<void *>(userData));

	IRuVertexBuffer *vb = reinterpret_cast<IRuVertexBuffer *>(memoryBlock.m_deviceBuffer);
	engineMetric->m_vb_Allocations++;
	engineMetric->m_vb_CommittedPages += memoryBlock.m_blockSize;
	engineMetric->m_vb_UtilizedBytes += vb->GetSize();

	return TRUE;
}

BOOL CRuEngineD3D::ResArenaIBCollectMetricCallback(const void *key, const void *data, void *userData)
{
	RuMemoryBlock memoryBlock = *reinterpret_cast<RuMemoryBlock *>(const_cast<void *>(data));
	RuEngineMetric *engineMetric = reinterpret_cast<RuEngineMetric *>(const_cast<void *>(userData));

	IRuIndexBuffer *ib = reinterpret_cast<IRuIndexBuffer *>(memoryBlock.m_deviceBuffer);
	engineMetric->m_ib_Allocations++;
	engineMetric->m_ib_CommittedPages += memoryBlock.m_blockSize;
	engineMetric->m_ib_UtilizedBytes += ib->GetSize();

	return TRUE;
}

BOOL CRuEngineD3D::ResArenaVBInvalidateCallback(const void *key, const void *data, void *userData)
{
	RuMemoryBlock memoryBlock = *((RuMemoryBlock *) data);
	((IRuVertexBuffer *) memoryBlock.m_deviceBuffer)->Invalidate();

	return TRUE;
}

BOOL CRuEngineD3D::ResArenaIBInvalidateCallback(const void *key, const void *data, void *userData)
{
	RuMemoryBlock memoryBlock = *((RuMemoryBlock *) data);
	((IRuIndexBuffer *) memoryBlock.m_deviceBuffer)->Invalidate();

	return TRUE;
}

BOOL CRuEngineD3D::ClearVertexDeclarationCacheCallback(const void *key, const void *data, void *userData)
{
	(*((CRuVertexDeclarationD3D **) data))->Release();

	return TRUE;
}

// ************************************************************************************************************************************************************

CRuEngineD3D*							g_ruEngineD3D = NULL;

// ************************************************************************************************************************************************************

#pragma managed
