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

#include "../../Engine/Texture/RuTextureUtility.h"

#pragma managed(push, off)

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

ruRTTI_DEFINE_SUBCLASS(CRuInstanceManagerD3D, IRuInstanceManager)
ruHEAP_DEFINE_SUBCLASS(CRuInstanceManagerD3D, "CRuInstanceManagerD3D", "IRuInstanceManager")
ruCLASSGUID_DEFINE(CRuInstanceManagerD3D, 0x68FE9F08, 0x123F4777, 0x8537BF9F, 0xE4C1362C)

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

CRuInstanceManagerD3D::CRuInstanceManagerD3D()
:	m_vbPoolHash(NULL),
	m_ibHash(NULL),
	m_meshInstantiatorHash(NULL),
	m_metric_NumIBAllocations(0),
	m_metric_NumVBAllocations(0),
	m_metric_NumIBInstanceOps(0),
	m_metric_NumVBInstanceOps(0)
{
	m_currentCycle = 0;

	m_meshInstantiatorHash = ruNEW CRuHashMap<PTRVALUE, IRuMeshInstantiator *>(256);

	m_engine = NULL;

	m_instancedTextures = NULL;
	m_texturePools = NULL;
}

CRuInstanceManagerD3D::~CRuInstanceManagerD3D()
{
	// Unregister event handlers
	m_engine->Event_BeginScene().UnregisterHandler(this->GetGUID());
	m_engine->Event_DeviceReset().UnregisterHandler(this->GetGUID());

	// Clear cache
	ClearCache();

	// Clear mesh instantiators
	ClearMeshInstantiators();

	// Delete instanced vertex buffer pools
	DeleteInstancedVertexBufferPools();

	ruSAFE_DELETE(m_meshInstantiatorHash);

	ruSAFE_DELETE(m_vbPoolHash);
	ruSAFE_DELETE(m_instancedTextures);
	ruSAFE_DELETE(m_texturePools);
}

UINT32 CRuInstanceManagerD3D::GetCacheSize()
{
	return 0;
}

BOOL CRuInstanceManagerD3D::SetEngine(IRuEngine *engine)
{
	// Store pointer to engine
	m_engine = engine;

	// Setup begin scene event handler
	m_engine->Event_BeginScene().RegisterHandler(this->GetGUID(), boost::bind(&CRuInstanceManagerD3D::HandleEvent_BeginScene, this, _1));

	// Setup device reset event handler
	m_engine->Event_DeviceReset().RegisterHandler(this->GetGUID(), boost::bind(&CRuInstanceManagerD3D::HandleEvent_DeviceReset, this, _1));

	// Invalidate and release current cache
	ClearCache();

	// Setup queues according to engine parameters
	ruSAFE_DELETE(m_vbPoolHash);
	m_vbPoolHash = ruNEW CRuHashMap<PTRVALUE, VertexBufferPool *>(128);

	ruSAFE_DELETE(m_ibHash);
	m_ibHash = ruNEW CRuHashMap<CRuGUID, IBDesc>(2048);

	// Setup instanced textures hash
	ruSAFE_DELETE(m_instancedTextures);
	m_instancedTextures = ruNEW CRuHashMap<CRuGUID, TexDesc *>(2048);

	// Setup unused textures hash
	ruSAFE_DELETE(m_texturePools);
	m_texturePools = ruNEW CRuHashMap<CRuGUID, TexPoolDesc *>(256);

	return TRUE;
}

IRuEngine *CRuInstanceManagerD3D::GetEngine()
{
	return m_engine;
}

BOOL CRuInstanceManagerD3D::RegisterMeshInstantiator(IRuMeshInstantiator *meshInstantiator)
{
	if(meshInstantiator)
	{
		// Increment reference on mesh instantiator
		meshInstantiator->AddRef();

		// Get key and key hash for the vertex declaration
		PTRVALUE vertDeclKey = reinterpret_cast<PTRVALUE>(meshInstantiator->GetVertexDeclaration());
		DWORD vertDeclKeyHash = RuGlobalHashGen().GetHashCode(vertDeclKey);

		// If an instantiator already exists, remove and release it
		if(m_meshInstantiatorHash->ContainsKey(vertDeclKey, vertDeclKeyHash))
		{
			// Get the existing instantiator and release it
			IRuMeshInstantiator *existingInstantiator = m_meshInstantiatorHash->Get(vertDeclKey, vertDeclKeyHash);
			ruSAFE_RELEASE(existingInstantiator);

			// Remove the existing instantiator from the hash
			m_meshInstantiatorHash->Remove(vertDeclKey, vertDeclKeyHash);
		}

		// Store reference to mesh instantiator
		m_meshInstantiatorHash->Add(vertDeclKey, vertDeclKeyHash, meshInstantiator);

		return TRUE;
	}

	return FALSE;
}

IRuMeshInstantiator *CRuInstanceManagerD3D::GetMeshInstantiator(const IRuVertexDeclaration *vertexDeclaration)
{
	// Get key and key hash for the vertex declaration
	PTRVALUE vertDeclKey = reinterpret_cast<PTRVALUE>(vertexDeclaration);
	DWORD vertDeclKeyHash = RuGlobalHashGen().GetHashCode(vertDeclKey);

	IRuMeshInstantiator *meshInstantiator = NULL;

	if(m_meshInstantiatorHash->Get(vertDeclKey, vertDeclKeyHash, meshInstantiator))
	{
		return meshInstantiator;
	}

	return NULL;
}

IRuVertexBuffer *CRuInstanceManagerD3D::GetInstancedVertexBuffer(IRuMesh *mesh, IRuShader *shader)
{
	// Assert valid mesh
	assert(mesh);

	// Assert valid shader
	assert(shader);

	// Hash the vertex declaration pointer
	PTRVALUE vertDecl = reinterpret_cast<PTRVALUE>(shader->GetVertexDeclaration());
	DWORD vertDeclHash = RuGlobalHashGen().GetHashCode(vertDecl);

	// Get the vertex buffer pool
	VertexBufferPool *vbPool = GetVertexBufferPool(vertDecl, vertDeclHash);
	if(vbPool)
	{
		// Declare empty vertex buffer descriptor
		VBDesc vbDesc = { mesh->GetMeshIteration() - 1, 0, NULL };

		BOOL reinstanceVB = FALSE;
		BOOL vbIsDynamic = mesh->IsMeshDynamic();

		const CRuGUID &meshKey = mesh->GetGUID();
		DWORD meshKeyHash = meshKey.HashCode();

		// Calculate size needed for the vertex buffer
		UINT32 requiredVBSize = shader->GetVertexDeclaration()->GetStreamStride(0) * mesh->GetNumVertices();

		// Locate an existing vertex buffer
		if(vbPool->Get(meshKey, meshKeyHash, vbDesc))
		{
			// If the existing VB is too small, destroy it
			if(requiredVBSize > vbDesc.m_vertexBuffer->GetSize())
			{
				// Destroy vertex buffer
				ruSAFE_RELEASE(vbDesc.m_vertexBuffer);
			}
		}
		else
		{
			// Create a new entry for the current mesh
			vbPool->Add(meshKey, meshKeyHash, vbDesc);
		}

		if(vbDesc.m_vertexBuffer == NULL)
		{
			// Create vertex buffer
			vbDesc.m_vertexBuffer = vbIsDynamic ? m_engine->CreateDynamicVertexBuffer(requiredVBSize) : m_engine->CreateVertexBuffer(requiredVBSize);

			// If vertex buffer creation failed, return
			if(vbDesc.m_vertexBuffer == NULL)
			{
				vbPool->Remove(meshKey, meshKeyHash);
				return NULL;
			}
		}

		// Check to make sure the iteration numbers are the same
		if(vbDesc.m_instanceIteration != mesh->GetMeshIteration())
		{
			// Update iteration number
			vbDesc.m_instanceIteration = mesh->GetMeshIteration();

			// Iteration numbers are different, so re-instance the vertex buffer
			reinstanceVB = TRUE;
		}

		// Update usage cycle
		vbDesc.m_usageCycle = m_currentCycle;

		// Update hash entry
		vbPool->Replace(meshKey, meshKeyHash, vbDesc);

		// Re-instance if necessary
		if(reinstanceVB)
		{
			void *verticesOut = vbDesc.m_vertexBuffer->Lock();

			IRuMeshInstantiator *meshInstantiator = this->GetMeshInstantiator(shader->GetVertexDeclaration());
			if(meshInstantiator)
			{
				meshInstantiator->Instantiate(0, mesh, 0, verticesOut);
			}
			else
			{
				OutputDebugString("No mesh instantiator!\n");
			}

			vbDesc.m_vertexBuffer->Unlock();
		}

		return vbDesc.m_vertexBuffer;
	}

	return NULL;
}

IRuIndexBuffer *CRuInstanceManagerD3D::GetInstancedIndexBuffer(IRuMesh *mesh)
{
	// Assert valid mesh
	assert(mesh);

	// Declare empty index buffer descriptor
	IRuMeshTopology *meshTopology = mesh->GetMeshTopology();
	const CRuGUID &topologyKey = meshTopology->GetGUID();
	DWORD topologyKeyHash = topologyKey.HashCode();

	IBDesc ibDesc = { meshTopology->GetIteration() - 1, 0, NULL };

	BOOL reinstanceIB = FALSE;
	BOOL ibIsDynamic = mesh->IsTopologyDynamic();

	// Calculate size needed for the index buffer
	UINT32 ibSize = sizeof(UINT16) * meshTopology->GetNumIndices();

	// Locate an existing index buffer
	if(m_ibHash->Get(topologyKey, topologyKeyHash, ibDesc))
	{
		// If the existing IB is too small, destroy it
		if(ibSize > ibDesc.m_indexBuffer->GetSize())
		{
			// Destroy index buffer
			ruSAFE_RELEASE(ibDesc.m_indexBuffer);
		}
	}
	else
	{
		// Create a new entry for the current mesh topology
		m_ibHash->Add(topologyKey, topologyKeyHash, ibDesc);
	}

	if(ibDesc.m_indexBuffer == NULL)
	{
		// Create index buffer
		ibDesc.m_indexBuffer = ibIsDynamic ? m_engine->CreateDynamicIndexBuffer(ibSize) : m_engine->CreateIndexBuffer(ibSize);

		// If index buffer creation failed, return
		if(ibDesc.m_indexBuffer == NULL)
		{
			m_ibHash->Remove(topologyKey, topologyKeyHash);
			return NULL;
		}
	}

	// Check to make sure the iteration numbers are the same
	if(ibDesc.m_instanceIteration != meshTopology->GetIteration())
	{
		// Update iteration number
		ibDesc.m_instanceIteration = meshTopology->GetIteration();

		// Iteration numbers are different, so re-instance the index buffer
		reinstanceIB = TRUE;
	}

	// Update usage cycle
	ibDesc.m_usageCycle = m_currentCycle;

	// Update hash entry
	m_ibHash->Replace(topologyKey, topologyKeyHash, ibDesc);

	// Instance indices if necessary
	if(reinstanceIB && meshTopology->GetIndices() && meshTopology->GetNumIndices() > 0)
	{
		UINT16 *indicesOut = reinterpret_cast<UINT16 *>(ibDesc.m_indexBuffer->Lock());
		RuMemcpy(indicesOut, meshTopology->GetIndices(), sizeof(UINT16) * meshTopology->GetNumIndices());
		ibDesc.m_indexBuffer->Unlock();
	}

	return ibDesc.m_indexBuffer;
}

BOOL CRuInstanceManagerD3D::TestTextureCompatibility(IRuBaseTexture *texture0, IRuBaseTexture *texture1)
{
	if(texture0->GetType().IsSubClassOf(IRuTexture::Type))
	{
		if(texture1->GetType().IsSubClassOf(IRuTexture::Type))
		{
			IRuTexture *tex0 = static_cast<IRuTexture *>(texture0);
			IRuTexture *tex1 = static_cast<IRuTexture *>(texture1);

			if(	tex0->GetLevelCount() == tex1->GetLevelCount() &&
				tex0->GetLevelWidth(0) == tex1->GetLevelWidth(0) &&
				tex0->GetLevelHeight(0) == tex1->GetLevelHeight(0) &&
				tex0->GetSurfaceFormat() == tex1->GetSurfaceFormat())
			{
				return TRUE;
			}
		}
	}
	else if(texture0->GetType().IsSubClassOf(IRuVolumeTexture::Type))
	{
		if(texture1->GetType().IsSubClassOf(IRuVolumeTexture::Type))
		{
			IRuVolumeTexture *tex0 = static_cast<IRuVolumeTexture *>(texture0);
			IRuVolumeTexture *tex1 = static_cast<IRuVolumeTexture *>(texture1);

			if(	tex0->GetLevelCount() == tex1->GetLevelCount() &&
				tex0->GetLevelWidth(0) == tex1->GetLevelWidth(0) &&
				tex0->GetLevelHeight(0) == tex1->GetLevelHeight(0) &&
				tex0->GetLevelDepth(0) == tex1->GetLevelDepth(0) &&
				tex0->GetSurfaceFormat() == tex1->GetSurfaceFormat())
			{
				return TRUE;
			}
		}
	}

	return FALSE;
}

IRuBaseTexture *CRuInstanceManagerD3D::GetInstance(IRuBaseTexture *texture)
{
	// If texture is NULL or in an invalidated state, it cannot be instanced
	if(texture == NULL || texture->IsInvalidated())
	{
		return NULL;
	}

	// Check to see if the texture already has an instance attached to it
	TexDesc *texDesc = reinterpret_cast<TexDesc *>(texture->GetInstanceData());
	IRuBaseTexture *instancedTexture = NULL;

	if(texDesc)
	{
		// Set instance texture
		instancedTexture = texDesc->m_instancedTexture;

		// Update usage cycle
		texDesc->m_usageCycle = m_currentCycle;

		return instancedTexture;
	}

	switch(texture->GetPool())
	{
		case ruRESPOOL_DEFAULT:
		case ruRESPOOL_MANAGED:
			{
				instancedTexture = texture;
			}

			break;

		case ruRESPOOL_SYSTEMMEMORY:
		case ruRESPOOL_SCRATCH:
			{
				// Build a 128-bit key based on texture dimensions, level count, and format
				CRuGUID guid = BuildTexturePoolKey(texture);

				// Locate the instantiation pool
				TexPoolDesc *texturePool = NULL;
				m_texturePools->Get(guid, guid.HashCode(), texturePool);

				// If the instantiation pool does not exist, create it now
				if(texturePool == NULL)
				{
					texturePool = ruNEW TexPoolDesc();
					texturePool->m_usedTextures = ruNEW CRuArrayList<IRuBaseTexture *>(256);
					texturePool->m_unusedTextures = ruNEW CRuQueue<TextureGCTuple>(256);
					m_texturePools->Add(guid, guid.HashCode(), texturePool);
				}

				const CRuGUID &texKey = texture->GetGUID();
				BOOL requiresCopy = FALSE;

				if(m_instancedTextures->Get(texKey, texKey.HashCode(), texDesc))
				{
					// If the instanced texture copy does not have the same iteration as the source, a copy is required
					if(texDesc->m_instancedTexture->GetIteration() != texDesc->m_sourceTexture->GetIteration())
						requiresCopy = TRUE;

					// Update usage cycle
					texDesc->m_usageCycle = m_currentCycle;

					// Update texture descriptor
					m_instancedTextures->Replace(texKey, texKey.HashCode(), texDesc);

					// Store output texture pointer
					instancedTexture = texDesc->m_instancedTexture;
				}
				else
				{
					// Copy operation is required
					requiresCopy = TRUE;

					// Either pop a texture off the unused stack, or create a new one
					instancedTexture = texturePool->m_unusedTextures->IsEmpty() ? CreatePooledTexture(texture, reinterpret_cast<PTRVALUE>(texturePool)) : texturePool->m_unusedTextures->DequeueTail().m_texture;

					if(instancedTexture)
					{
						// Create a descriptor
						texDesc = ruNEW TexDesc();
						texDesc->m_texturePool = reinterpret_cast<PTRVALUE>(texturePool);
						texDesc->m_usageCycle = m_currentCycle;
						texDesc->m_sourceTexture = texture;
						texDesc->m_sourceTexture->AddRef();
						texDesc->m_instancedTexture = instancedTexture;
						texDesc->m_instancedTexture->AddRef();
						texturePool->m_usedTextures->Add(texDesc->m_instancedTexture);

						// Set instanced texture descriptor into the source texture
						texDesc->m_sourceTexture->SetInstanceData(reinterpret_cast<PTRVALUE>(texDesc));

						// Update texture descriptor
						m_instancedTextures->Add(texKey, texKey.HashCode(), texDesc);
					}
				}

				if(requiresCopy && instancedTexture)
				{
/*
					DWORD t0, t1, et;
					t0 = timeGetTime();
*/
					if(RuTextureUtility_Copy(texture, instancedTexture))
					{
						// Texture copy successful, re-purpose the source texture such that it is now a proxy to the instanced texture to save memory
						if(texture->GetType() == CRuTexture_Proxy::Type)
						{
							static_cast<CRuTexture_Proxy *>(texture)->Allocate(static_cast<IRuTexture *>(instancedTexture));
						}
						else if(texture->GetType() == CRuVolumeTexture_Proxy::Type)
						{
							static_cast<CRuVolumeTexture_Proxy *>(texture)->Allocate(static_cast<IRuVolumeTexture *>(instancedTexture));
						}

						// Equalize the iteration counter
						texDesc->m_instancedTexture->SetIteration(texDesc->m_sourceTexture->GetIteration());

						// Update texture descriptor
						m_instancedTextures->Replace(texKey, texKey.HashCode(), texDesc);
					}
					else
					{
						// Force copy on the next instantiation
						texDesc->m_instancedTexture->SetIteration(texDesc->m_instancedTexture->GetIteration() - 1);

						// Update texture descriptor
						m_instancedTextures->Replace(texKey, texKey.HashCode(), texDesc);
					}
/*
					t1 = timeGetTime();
					et = t1 - t0;
					char outputStr[256];
					sprintf(outputStr, "Texture instantiation done in %u ms.\n", et);
					OutputDebugString(outputStr);
*/
				}
			}

			break;
	}

	return instancedTexture;
}

BOOL CRuInstanceManagerD3D::ClearCache()
{
	// Clear instanced vertex buffers
	ClearInstancedVertexBufferPools();

	// Clear instanced index buffers
	ClearInstancedIndexBuffers();

	// Clear instanced textures
	ClearInstancedTextures();

	// Clear texture pools
	ClearTexturePool();

	return TRUE;
}

INT32 CRuInstanceManagerD3D::GetNumMetricStrings()
{
	return 1;
}

const char *CRuInstanceManagerD3D::GetMetricString(INT32 stringIndex)
{
	switch(stringIndex)
	{
		case 0:
			sprintf(m_metric_String, "[Instance Manager] [VB Alloc : %d] [IB Alloc : %d] [VB Instance Op : %d] [IB Instance Op : %d]", m_metric_NumVBAllocations, m_metric_NumIBAllocations, m_metric_NumVBInstanceOps, m_metric_NumIBInstanceOps);
			return m_metric_String;
	}

	sprintf(m_metric_String, "[Instance Manager] [VB Alloc : %d] [IB Alloc : %d] [VB Instance Op : %d] [IB Instance Op : %d]", m_metric_NumVBAllocations, m_metric_NumIBAllocations, m_metric_NumVBInstanceOps, m_metric_NumIBInstanceOps);
	return m_metric_String;
}

BOOL CRuInstanceManagerD3D::OutputInstantiationManifest()
{
	return TRUE;
}

BOOL CRuInstanceManagerD3D::HandleEvent_BeginScene(RuEventArgs *eventArgs)
{
	// Increment cycle count
	++m_currentCycle;

	// Run a garbage collection on instanced vertex buffers
	GCInstancedVertexBufferPools();

	// Run a garbage collection on instanced index buffers
	GCInstancedIndexBuffers();

	// Run a clean up on instanced textures
	m_instancedTextures->ForAllElements(boost::bind(&CRuInstanceManagerD3D::InstancedTextureGCCallback, this, _1, _2));

	for(INT32 i = 0; i < m_gcInstancedTextures.Count(); ++i)
	{
		m_instancedTextures->Remove(m_gcInstancedTextures[i], m_gcInstancedTextures[i].HashCode());
	}

	m_gcInstancedTextures.Clear();

	// Run a garbage collection on texture pools
	TexturePoolGC();

	return TRUE;
}

BOOL CRuInstanceManagerD3D::HandleEvent_DeviceReset(RuEventArgs *eventArgs)
{
	return ClearCache();
}

void CRuInstanceManagerD3D::ClearMeshInstantiators()
{
	m_meshInstantiatorHash->ForAllElements(boost::bind(&CRuInstanceManagerD3D::ClearMeshInstantiatorsCallback, this, _1, _2));
}

BOOL CRuInstanceManagerD3D::ClearMeshInstantiatorsCallback(const void *key, void *data)
{
	IRuMeshInstantiator *instantiator = *reinterpret_cast<IRuMeshInstantiator **>(data);
	ruSAFE_RELEASE(instantiator);
	return TRUE;
}

CRuInstanceManagerD3D::VertexBufferPool *CRuInstanceManagerD3D::GetVertexBufferPool(PTRVALUE poolKey, DWORD poolKeyHash)
{
	VertexBufferPool *vbPool = NULL;

	// Find an existing vertex buffer pool
	if(m_vbPoolHash->Get(poolKey, poolKeyHash, vbPool) == NULL)
	{
		// Create new vertex buffer pool
		vbPool = ruNEW VertexBufferPool(1024);

		// Add vertex buffer pool into hash
		m_vbPoolHash->Add(poolKey, poolKeyHash, vbPool);
	}

	return vbPool;
}

BOOL CRuInstanceManagerD3D::DeleteInstancedVertexBufferPools()
{
	if(m_vbPoolHash)
	{
		m_vbPoolHash->ForAllElements(boost::bind(&CRuInstanceManagerD3D::DeleteInstancedVertexBufferPoolsCallback, this, _1, _2));
	}

	return TRUE;
}

BOOL CRuInstanceManagerD3D::DeleteInstancedVertexBufferPoolsCallback(const void *key, void *data)
{
	// Cast parameters
	PTRVALUE poolKey = *reinterpret_cast<const PTRVALUE *>(key);
	VertexBufferPool *vbPool = *reinterpret_cast<VertexBufferPool **>(data);

	if(vbPool)
	{
		ClearInstancedVertexBuffers(vbPool);
		ruSAFE_DELETE(vbPool);
		return TRUE;
	}

	return TRUE;
}

BOOL CRuInstanceManagerD3D::ClearInstancedVertexBufferPools()
{
	if(m_vbPoolHash)
	{
		m_vbPoolHash->ForAllElements(boost::bind(&CRuInstanceManagerD3D::ClearInstancedVertexBufferPoolsCallback, this, _1, _2));
	}

	return TRUE;
}

BOOL CRuInstanceManagerD3D::ClearInstancedVertexBufferPoolsCallback(const void *key, void *data)
{
	// Cast parameters
	PTRVALUE poolKey = *reinterpret_cast<const PTRVALUE *>(key);
	VertexBufferPool *vbPool = *reinterpret_cast<VertexBufferPool **>(data);

	if(vbPool)
	{
		return ClearInstancedVertexBuffers(vbPool);
	}

	return TRUE;
}

BOOL CRuInstanceManagerD3D::ClearInstancedVertexBuffers(VertexBufferPool *vbPool)
{
	if(vbPool)
	{
		vbPool->ForAllElements(boost::bind(&CRuInstanceManagerD3D::ClearInstancedVertexBuffersCallback, this, _1, _2));
		vbPool->Clear();
	}

	return TRUE;
}

BOOL CRuInstanceManagerD3D::ClearInstancedVertexBuffersCallback(const void *key, void *data)
{
	// Cast parameters
	const CRuGUID &vbKey = *reinterpret_cast<const CRuGUID *>(key);
	VBDesc &vbDesc= *reinterpret_cast<VBDesc *>(data);

	// Release vertex buffer
	ruSAFE_RELEASE(vbDesc.m_vertexBuffer);

	return TRUE;
}

BOOL CRuInstanceManagerD3D::GCInstancedVertexBufferPools()
{
	if(m_vbPoolHash)
	{
		// Clear the GC queue
		m_gc_Buffers.Clear();

		// Run GC callback on all vertex buffer pools
		m_vbPoolHash->ForAllElements(boost::bind(&CRuInstanceManagerD3D::GCInstancedVertexBufferPoolsCallback, this, _1, _2));

		// Post-process the GC queue
		while(m_gc_Buffers.IsEmpty() == FALSE)
		{
			BufferGCTuple tuple = m_gc_Buffers.Dequeue();

			VertexBufferPool *vbPool = GetVertexBufferPool(tuple.m_poolKey, RuGlobalHashGen().GetHashCode(tuple.m_poolKey));
			if(vbPool == NULL)
			{
				// Critical error
				continue;
			}

			VBDesc vbDesc;
			if(vbPool->Get(tuple.m_bufferKey, tuple.m_bufferKey.HashCode(), vbDesc) == FALSE)
			{
				// Critical error
				continue;
			}

			// Release vertex buffer
			ruSAFE_RELEASE(vbDesc.m_vertexBuffer);

			// Remove from hash
			vbPool->Remove(tuple.m_bufferKey, tuple.m_bufferKey.HashCode());
		}
	}

	return TRUE;
}

BOOL CRuInstanceManagerD3D::GCInstancedVertexBufferPoolsCallback(const void *key, void *data)
{
	// Cast parameters
	PTRVALUE poolKey = *reinterpret_cast<const PTRVALUE *>(key);
	VertexBufferPool *vbPool = *reinterpret_cast<VertexBufferPool **>(data);

	if(vbPool)
	{
		// Set current pool key
		m_gc_CurPoolKey = poolKey;

		// Run GC callback on all vertex buffers in the pool
		return GCInstancedVertexBuffers(vbPool);
	}

	return TRUE;
}

BOOL CRuInstanceManagerD3D::GCInstancedVertexBuffers(VertexBufferPool *vbPool)
{
	if(vbPool)
	{
		// Iterate through all vertex buffers in the pool
		vbPool->ForAllElements(boost::bind(&CRuInstanceManagerD3D::GCInstancedVertexBuffersCallback, this, _1, _2));
	}

	return TRUE;
}

BOOL CRuInstanceManagerD3D::GCInstancedVertexBuffersCallback(const void *key, void *data)
{
	// Cast parameters
	const CRuGUID &vbKey = *reinterpret_cast<const CRuGUID *>(key);
	VBDesc &vbDesc= *reinterpret_cast<VBDesc *>(data);

	// Collect the buffer if it has not been used in the given number of cycles
	INT32 cycleDiff = m_currentCycle - vbDesc.m_usageCycle;

	if(cycleDiff > 500)
	{
		// Queue the pool key/buffer key tuple in the GC queue
		BufferGCTuple tuple = { m_gc_CurPoolKey, vbKey };
		m_gc_Buffers.Queue(tuple);
	}

	return TRUE;
}

BOOL CRuInstanceManagerD3D::ClearInstancedIndexBuffers()
{
	if(m_ibHash)
	{
		m_ibHash->ForAllElements(boost::bind(&CRuInstanceManagerD3D::ClearInstancedIndexBuffersCallback, this, _1, _2));
		m_ibHash->Clear();
	}

	return TRUE;
}

BOOL CRuInstanceManagerD3D::ClearInstancedIndexBuffersCallback(const void *key, void *data)
{
	// Cast parameters
	const CRuGUID &ibKey = *reinterpret_cast<const CRuGUID *>(key);
	IBDesc &ibDesc= *reinterpret_cast<IBDesc *>(data);

	// Release index buffer
	ruSAFE_RELEASE(ibDesc.m_indexBuffer);

	return TRUE;
}

BOOL CRuInstanceManagerD3D::GCInstancedIndexBuffers()
{
	if(m_ibHash)
	{
		// Clear garbage collection queue
		m_gc_Buffers.Clear();

		// Run garbage collection callback on all index buffers
		m_ibHash->ForAllElements(boost::bind(&CRuInstanceManagerD3D::GCInstancedIndexBuffersCallback, this, _1, _2));

		// Post-process garbage collection queue
		while(m_gc_Buffers.IsEmpty() == FALSE)
		{
			BufferGCTuple tuple = m_gc_Buffers.Dequeue();

			IBDesc ibDesc;
			if(m_ibHash->Get(tuple.m_bufferKey, tuple.m_bufferKey.HashCode(), ibDesc) == FALSE)
			{
				// Critical error
				continue;
			}

			// Release index buffer
			ruSAFE_RELEASE(ibDesc.m_indexBuffer);

			// Remove from hash
			m_ibHash->Remove(tuple.m_bufferKey, tuple.m_bufferKey.HashCode());
		}
	}

	return TRUE;
}

BOOL CRuInstanceManagerD3D::GCInstancedIndexBuffersCallback(const void *key, void *data)
{
	// Cast parameters
	const CRuGUID &ibKey = *reinterpret_cast<const CRuGUID *>(key);
	IBDesc &ibDesc= *reinterpret_cast<IBDesc *>(data);

	// Collect the buffer if it has not been used in the given number of cycles
	INT32 cycleDiff = m_currentCycle - ibDesc.m_usageCycle;

	if(cycleDiff > 500)
	{
		// Queue the pool key/buffer key tuple in the GC queue
		BufferGCTuple tuple = { 0, ibKey };
		m_gc_Buffers.Queue(tuple);
	}

	return TRUE;
}

CRuGUID CRuInstanceManagerD3D::BuildTexturePoolKey(IRuBaseTexture *texture)
{
	if(texture->GetType().IsSubClassOf(IRuTexture::Type))
	{
		// Build a 128-bit key based on texture dimensions, level count, and format
		IRuTexture *srcTexture = static_cast<IRuTexture *>(texture);
		INT32 key0 = ((srcTexture->GetLevelWidth(0) & 0x0000FFFF) << 16) | (srcTexture->GetLevelHeight(0) & 0x0000FFFF);
		INT32 key1 = srcTexture->GetLevelCount() & 0x0000FFFF;
		INT32 key2 = srcTexture->GetSurfaceFormat();

		return CRuGUID(key0, key1, key2, 0);
	}
	else if(texture->GetType().IsSubClassOf(IRuVolumeTexture::Type))
	{
		// Build a 128-bit key based on texture dimensions, level count, and format
		IRuVolumeTexture *srcTexture = static_cast<IRuVolumeTexture *>(texture);
		INT32 key0 = ((srcTexture->GetLevelWidth(0) & 0x0000FFFF) << 16) | (srcTexture->GetLevelHeight(0) & 0x0000FFFF);
		INT32 key1 = ((srcTexture->GetLevelDepth(0) & 0x0000FFFF) << 16) | (srcTexture->GetLevelCount() & 0x0000FFFF);
		INT32 key2 = srcTexture->GetSurfaceFormat();

		return CRuGUID(key0, key1, key2, 0);
	}

	return CRuGUID::Invalid;
}

IRuBaseTexture *CRuInstanceManagerD3D::CreatePooledTexture(IRuBaseTexture *texture, PTRVALUE texturePool)
{
	IRuBaseTexture *pooledTexture = NULL;

	if(texture->GetType().IsSubClassOf(IRuTexture::Type))
	{
		IRuTexture *srcTexture = static_cast<IRuTexture *>(texture);
		pooledTexture = m_engine->CreateTexture(srcTexture->GetLevelWidth(0), srcTexture->GetLevelHeight(0), srcTexture->GetLevelCount(), srcTexture->GetSurfaceFormat(), ruRESPOOL_MANAGED);
	}
	else if(texture->GetType().IsSubClassOf(IRuVolumeTexture::Type))
	{
		IRuVolumeTexture *srcTexture = static_cast<IRuVolumeTexture *>(texture);
		pooledTexture = m_engine->CreateVolumeTexture(srcTexture->GetLevelWidth(0), srcTexture->GetLevelHeight(0), srcTexture->GetLevelDepth(0), srcTexture->GetLevelCount(), srcTexture->GetSurfaceFormat(), ruRESPOOL_MANAGED);
	}

	return pooledTexture;
}

IRuBaseTexture *CRuInstanceManagerD3D::CreateScratchTexture(IRuBaseTexture *texture)
{
	IRuBaseTexture *scratchTexture = NULL;

	if(texture->GetType().IsSubClassOf(IRuTexture::Type))
	{
		IRuTexture *srcTexture = static_cast<IRuTexture *>(texture);
		scratchTexture = m_engine->CreateTexture(srcTexture->GetLevelWidth(0), srcTexture->GetLevelHeight(0), srcTexture->GetLevelCount(), srcTexture->GetSurfaceFormat(), ruRESPOOL_SYSTEMMEMORY);
	}
	else if(texture->GetType().IsSubClassOf(IRuVolumeTexture::Type))
	{
		IRuVolumeTexture *srcTexture = static_cast<IRuVolumeTexture *>(texture);
		scratchTexture = m_engine->CreateVolumeTexture(srcTexture->GetLevelWidth(0), srcTexture->GetLevelHeight(0), srcTexture->GetLevelDepth(0), srcTexture->GetLevelCount(), srcTexture->GetSurfaceFormat(), ruRESPOOL_SYSTEMMEMORY);
	}

	return scratchTexture;
}

BOOL CRuInstanceManagerD3D::ReleaseTextureInstance(CRuInstanceManagerD3D::TexDesc *texDesc)
{
	BOOL retVal = TRUE;

	// Copy-back data to source texture
	if(texDesc->m_sourceTexture->GetType() == CRuTexture_Proxy::Type)
	{
		// Re-allocate the source texture
		CRuTexture_Proxy *texture = static_cast<CRuTexture_Proxy *>(texDesc->m_sourceTexture);
		texture->Allocate(texture->GetLevelWidth(0), texture->GetLevelHeight(0), texture->GetLevelCount(), texture->GetSurfaceFormat());

		// Copy-back to source texture
		if(RuTextureUtility_Copy(texDesc->m_instancedTexture, texture) == FALSE)
		{
			// Copy-back to source texture failed, discard the texture
			texture->Discard();

			retVal = FALSE;
		}
	}
	else if(texDesc->m_sourceTexture->GetType() == CRuVolumeTexture_Proxy::Type)
	{
		// Re-allocate the source texture
		CRuVolumeTexture_Proxy *texture = static_cast<CRuVolumeTexture_Proxy *>(texDesc->m_sourceTexture);
		texture->Allocate(texture->GetLevelWidth(0), texture->GetLevelHeight(0), texture->GetLevelDepth(0), texture->GetLevelCount(), texture->GetSurfaceFormat());

		// Copy-back to source texture
		if(RuTextureUtility_Copy(texDesc->m_instancedTexture, texture) == FALSE)
		{
			// Copy-back to source texture failed, discard the texture
			texture->Discard();

			retVal = FALSE;
		}
	}

	// Reset instanced texture pointer in source texture
	texDesc->m_sourceTexture->SetInstanceData(NULL);

	// Release reference to source texture
	ruSAFE_RELEASE(texDesc->m_sourceTexture);

	// Release reference to instanced texture
	ruSAFE_RELEASE(texDesc->m_instancedTexture);

	return retVal;
}

BOOL CRuInstanceManagerD3D::ClearInstancedTextures()
{
	if(m_instancedTextures)
	{
		// Release the entire instanced mesh list
		m_instancedTextures->ForAllElements(boost::bind(&CRuInstanceManagerD3D::ClearInstancedTexturesCallback, this, _1, _2));
		m_instancedTextures->Clear();
	}

	return TRUE;
}

BOOL CRuInstanceManagerD3D::ClearInstancedTexturesCallback(const void *key, void *data)
{
	// Cast parameters
	const CRuGUID &texKey = *reinterpret_cast<const CRuGUID *>(key);
	TexDesc *texDesc= *reinterpret_cast<TexDesc **>(data);

	// Release the texture instance
	ReleaseTextureInstance(texDesc);

	// Delete the texture instance descriptor
	ruSAFE_DELETE(texDesc);

	return TRUE;
}

BOOL CRuInstanceManagerD3D::InstancedTextureGCCallback(const void *key, void *data)
{
	// Cast parameters
	const CRuGUID &texKey = *reinterpret_cast<const CRuGUID *>(key);
	TexDesc *texDesc= *reinterpret_cast<TexDesc **>(data);

	// Collect the texture if it has not been used in the given number of cycles
	INT32 cycleDiff = m_currentCycle - texDesc->m_usageCycle;

	if(cycleDiff > 500)
	{
		// Add texture key to the list of textures to be removed
		m_gcInstancedTextures.Add(texKey);

		// Push instanced texture onto the unused stack
		TexPoolDesc *texturePool = reinterpret_cast<TexPoolDesc *>(texDesc->m_texturePool);

		// Remove texture from used pool
		texturePool->m_usedTextures->Remove(texDesc->m_instancedTexture);

		// Insert texture into unused pool
		TextureGCTuple gcTuple = { texDesc->m_instancedTexture, 0 };
		texturePool->m_unusedTextures->Queue(gcTuple);

		// Release the texture instance
		ReleaseTextureInstance(texDesc);

		// Delete the texture instance descriptor
		ruSAFE_DELETE(texDesc);
	}

	return TRUE;
}

BOOL CRuInstanceManagerD3D::TexturePoolGC()
{
	if(m_texturePools)
	{
		m_texturePools->ForAllElements(boost::bind(&CRuInstanceManagerD3D::TexturePoolGCCallback, this, _1, _2));
	}

	return TRUE;
}

BOOL CRuInstanceManagerD3D::TexturePoolGCCallback(const void *key, void *data)
{
	// Cast parameters
	TexPoolDesc *poolDesc = *reinterpret_cast<TexPoolDesc **>(data);

	for(INT32 i = 0, numTextures = poolDesc->m_unusedTextures->Count(); i < numTextures; ++i)
	{
		// Dequeue texture GC tuple from pool
		TextureGCTuple gcTuple = poolDesc->m_unusedTextures->Dequeue();

		// Increment GC age
		++gcTuple.m_gcAge;

		if(gcTuple.m_gcAge > 30)
		{
			// Release texture
			ruSAFE_RELEASE(gcTuple.m_texture);
		}
		else
		{
			// Queue texture GC tuple back into pool
			poolDesc->m_unusedTextures->Queue(gcTuple);
		}
	}

	return TRUE;
}

BOOL CRuInstanceManagerD3D::ClearTexturePool()
{
	if(m_texturePools)
	{
		// Release the entire texture pool
		m_texturePools->ForAllElements(boost::bind(&CRuInstanceManagerD3D::ClearTexturePoolCallback, this, _1, _2));
		m_texturePools->Clear();
	}

	return TRUE;
}

BOOL CRuInstanceManagerD3D::ClearTexturePoolCallback(const void *key, void *data)
{
	// Cast parameters
	TexPoolDesc *poolDesc = *reinterpret_cast<TexPoolDesc **>(data);

	// Release reference to all used textures
	for(INT32 i = 0; i < poolDesc->m_usedTextures->Count(); ++i)
	{
		IRuBaseTexture *texture = poolDesc->m_usedTextures->Get(i);
		ruSAFE_RELEASE(texture);
	}

	// Release reference to all unused textures
	while(poolDesc->m_unusedTextures->IsEmpty() == FALSE)
	{
		TextureGCTuple gcTuple = poolDesc->m_unusedTextures->Dequeue();
		ruSAFE_RELEASE(gcTuple.m_texture);
	}

	// Delete the texture pool descriptor
	ruSAFE_DELETE(poolDesc->m_usedTextures);
	ruSAFE_DELETE(poolDesc->m_unusedTextures);
	ruSAFE_DELETE(poolDesc);

	return TRUE;
}

BOOL CRuInstanceManagerD3D::TexturePoolManifestCallback(const void *key, void *data)
{
	// Cast parameters
	TexPoolDesc &poolDesc = *reinterpret_cast<TexPoolDesc *>(data);

	return TRUE;
}

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

#pragma managed(pop)
