/*!
	@file RuRenderer.cpp

	Copyright (c) 2004-2008 All rights reserved
*/

#include "../../Scene/Renderer/RuRenderer.h"

#pragma managed(push, off)

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

ruRTTI_DEFINE_SUBCLASS(CRuSkinCache, IRuObject)
ruHEAP_DEFINE_SUBCLASS(CRuSkinCache, "CRuSkinCache", "IRuObject")
ruCLASSGUID_DEFINE(CRuSkinCache, 0xFE27AC86, 0x517643ae, 0x86F8FDA4, 0x6CC492DE)

ruRTTI_DEFINE_SUBCLASS(CRuClipPlaneSet, IRuObject)
ruHEAP_DEFINE_SUBCLASS(CRuClipPlaneSet, "CRuClipPlaneSet", "IRuObject")
ruCLASSGUID_DEFINE(CRuClipPlaneSet, 0x0954828B, 0x791B41c6, 0xBFE2DD81, 0x87FA3B31)

ruRTTI_DEFINE_SUBCLASS(CRuRendererCommand, IRuObject)
ruHEAP_DEFINE_SUBCLASS(CRuRendererCommand, "CRuRendererCommand", "IRuObject")
ruCLASSGUID_DEFINE(CRuRendererCommand, 0x25EE7DBE, 0x640C4b77, 0x9177ADA1, 0xCB3C7F69)

ruRTTI_DEFINE_SUBCLASS(CRuRendererCommand_CopyTexture, CRuRendererCommand)
ruHEAP_DEFINE_SUBCLASS(CRuRendererCommand_CopyTexture, "CRuRendererCommand_CopyTexture", "CRuRendererCommand")
ruCLASSGUID_DEFINE(CRuRendererCommand_CopyTexture, 0x452EB883, 0xFE9D41b3, 0x82C73941, 0xF2EEF26C)

ruRTTI_DEFINE_SUBCLASS(CRuRenderContext, CRuRendererCommand)
ruHEAP_DEFINE_SUBCLASS(CRuRenderContext, "CRuRenderContext", "CRuRendererCommand")
ruCLASSGUID_DEFINE(CRuRenderContext, 0x92734C98, 0x9AE544d9, 0xA6F1353E, 0x5FF06FDB)

ruRTTI_DEFINE_SUBCLASS(CRuRenderer, IRuObject)
ruHEAP_DEFINE_SUBCLASS(CRuRenderer, "CRuRenderer", "IRuObject")
ruCLASSGUID_DEFINE(CRuRenderer, 0xC14D6BEC, 0xC7704353, 0x87DA4271, 0x8F1A397B)

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

CRuSkinCache::CRuSkinCache()
{
	// Create skinning cache
	m_skinningCache = ruNEW CRuHashMap<CRuGUID, RuCachedSkin>(1024);
}

CRuSkinCache::~CRuSkinCache()
{
	// Flush the skinning cache
	FlushSkinningCache();

	// Release the skinning cache
	delete m_skinningCache;
}

RuCachedSkin CRuSkinCache::GetCachedSkin(IRuMesh *mesh)
{
	RuCachedSkin cachedSkin;

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

	// Locate the cached skin mesh for the current entry
	if(m_skinningCache->Get(meshKey, meshKeyHash, cachedSkin) == FALSE)
	{
		if(mesh->GetType() == CRuMesh::Type)
		{
			// Set skinnable mesh to NULL
			cachedSkin.m_mesh = NULL;

			// Set last update time
			cachedSkin.m_lastUpdateTime = timeGetTime();

			// Subdivide mesh
			cachedSkin.m_subdividedMeshes = ruNEW CRuArrayList<IRuMesh *>();
			static_cast<CRuMesh *>(mesh)->SkinnedMesh_Subdivide(74, cachedSkin.m_subdividedMeshes);

			// Put cached skin entry into list
			m_skinningCache->Add(meshKey, meshKeyHash, cachedSkin);
		}
	}
	else
	{
		// Set last update time
		cachedSkin.m_lastUpdateTime = timeGetTime();

		// Update cache entry
		m_skinningCache->Replace(meshKey, meshKeyHash, cachedSkin);
	}

	return cachedSkin;
}

void CRuSkinCache::FlushSkinningCache()
{
	m_skinningCache->ForAllElements(boost::bind(&CRuSkinCache::FlushSkinningCacheCallback, this, _1, _2));
	m_skinningCache->Clear();
}

BOOL CRuSkinCache::FlushSkinningCacheCallback(const void *key, void *data)
{
	RuCachedSkin &cachedSkin = *reinterpret_cast<RuCachedSkin *>(data);

	// Release the skinnable mesh
	ruSAFE_RELEASE(cachedSkin.m_mesh);

	// Release any subdivided meshes
	if(cachedSkin.m_subdividedMeshes)
	{
		for(INT32 i = 0; i < cachedSkin.m_subdividedMeshes->Count(); ++i)
		{
			ruSAFE_RELEASE(cachedSkin.m_subdividedMeshes->Get(i));
		}

		delete cachedSkin.m_subdividedMeshes;
	}

	return TRUE;
}

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

CRuClipPlaneSet::CRuClipPlaneSet()
{
}

CRuClipPlaneSet::~CRuClipPlaneSet()
{
}

const CRuPlane4 &CRuClipPlaneSet::GetClipPlane(INT32 index)
{
	return m_clipPlanes[index];
}

void CRuClipPlaneSet::SetClipPlane(INT32 index, const CRuPlane4 &clipPlane)
{
	m_clipPlanes[index] = clipPlane;
}

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

CRuRendererCommand::~CRuRendererCommand()
{
}

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

CRuRendererCommand_CopyTexture::CRuRendererCommand_CopyTexture()
:	m_srcTexture(NULL),
	m_tgtTexture(NULL)
{
}

CRuRendererCommand_CopyTexture::~CRuRendererCommand_CopyTexture()
{
	ruSAFE_RELEASE(m_srcTexture);
	ruSAFE_RELEASE(m_tgtTexture);
}

IRuEngineResource *CRuRendererCommand_CopyTexture::GetSourceTexture()
{
	return m_srcTexture;
}

void CRuRendererCommand_CopyTexture::SetSourceTexture(IRuEngineResource *srcTexture)
{
	srcTexture->AddRef();
	ruSAFE_RELEASE(m_srcTexture);
	m_srcTexture = srcTexture;
}

IRuEngineResource *CRuRendererCommand_CopyTexture::GetTargetTexture()
{
	return m_tgtTexture;
}

void CRuRendererCommand_CopyTexture::SetTargetTexture(IRuEngineResource *tgtTexture)
{
	tgtTexture->AddRef();
	ruSAFE_RELEASE(m_tgtTexture);
	m_tgtTexture = tgtTexture;
}

void CRuRendererCommand_CopyTexture::Begin()
{
}

void CRuRendererCommand_CopyTexture::End()
{
	ruSAFE_RELEASE(m_srcTexture);
	ruSAFE_RELEASE(m_tgtTexture);
}

void CRuRendererCommand_CopyTexture::Execute()
{
	if(m_srcTexture && m_tgtTexture)
	{
		g_ruEngine->CopyTexture(m_srcTexture, m_tgtTexture, 0);
	}
}

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

CRuRenderContext::CRuRenderContext(CRuRenderer *owner)
:	m_owner(owner),
	m_renderTarget(NULL),
	m_depthStencilSurface(NULL),
	m_defaultViewport(NULL),
	m_eventListeners(NULL),
	m_renderQueue(256),
	m_sortRenderQueue(TRUE)
{
	// Setup probes
	m_probe_3D_Submit = g_ruProfiler->CreateProbe("3D Submit", "Scene");
	m_probe_3D_BatchCount = g_ruProfiler->CreateProbe("3D Batches", "Scene");
	m_probe_3D_Flush = g_ruProfiler->CreateProbe("3D Flush", "Scene");
	m_probe_3D_Skinning = g_ruProfiler->CreateProbe("Skinning", "Scene");
	m_probe_3D_Morphing = g_ruProfiler->CreateProbe("Morphing", "Scene");
	m_probe_3D_Sorting = g_ruProfiler->CreateProbe("3D Sorting", "Scene");
	m_probe_3D_Parameters = g_ruProfiler->CreateProbe("3D Parameters", "Scene");
	m_probe_3D_Lighting = g_ruProfiler->CreateProbe("3D Lighting", "Scene");
	m_probe_3D_TransformSetup = g_ruProfiler->CreateProbe("Transform Setup", "Scene");
	m_probe_3D_TextureSetup = g_ruProfiler->CreateProbe("Texture Setup", "Scene");
	m_probe_3D_RenderAllPasses = g_ruProfiler->CreateProbe("3D Render Passes", "Scene");
	m_probe_3D_Render = g_ruProfiler->CreateProbe("3D Render", "Scene");
	m_probe_3D_RenderD3D = g_ruProfiler->CreateProbe("3D Render D3D", "Scene");

	m_probe_Engine_InstancingTime = g_ruProfiler->CreateProbe("Engine Instancing Time", "Scene");

	m_probe_Misc_AntiPortalDiscarded = g_ruProfiler->CreateProbe("Anti-Portal Discarded", "Scene");
}

CRuRenderContext::~CRuRenderContext()
{
	m_owner = NULL;
	m_renderTarget = NULL;

	// Release depth stencil surface

	// Release default viewport
	ruSAFE_RELEASE(m_defaultViewport);

	// Delete all registered event listener descriptors
	EventListenerDesc *curListener = m_eventListeners;
	while(curListener)
	{
		// Advance to next listener descriptor and delete
		EventListenerDesc *deletedListener = curListener;
		curListener = curListener->m_next;
		delete deletedListener;
	}

	// Clear event listeners
	m_eventListeners = NULL;

	// Release matrix stack
	FlushMatrixStack();
	while(m_unusedMatrixStack.IsEmpty() == FALSE)
	{
		delete m_unusedMatrixStack.Pop();
	}

	// Release parameter cache stack
	FlushParameterCacheStack();
	while(m_unusedParameterCacheStack.IsEmpty() == FALSE)
	{
		delete m_unusedParameterCacheStack.Pop();
	}
}

BOOL CRuRenderContext::SetRenderTarget(IRuObject *renderTarget)
{
	// Store reference to render target
	m_renderTarget = renderTarget;

	// Create default viewport
	ruSAFE_RELEASE(m_defaultViewport);
	m_defaultViewport = g_ruEngine->CreateViewport(0, 0, 32, 32);

	// Update default viewport parameters
	UpdateDefaultViewportParameters();

	// Set default viewport clear parameters
	m_defaultViewport->SetClearColor(0x00000000);
	m_defaultViewport->SetClearFlags(RUCLEAR_TARGET | RUCLEAR_ZBUFFER | RUCLEAR_STENCIL);
	m_defaultViewport->SetClearStencil(0);
	m_defaultViewport->SetClearZ(1.0f);

	return TRUE;
}

BOOL CRuRenderContext::SetDefaultClearParameters(DWORD clearFlags, RUCOLOR color, REAL z, DWORD stencil)
{
	if(m_defaultViewport)
	{
		m_defaultViewport->SetClearFlags(clearFlags);
		m_defaultViewport->SetClearColor(color);
		m_defaultViewport->SetClearZ(z);
		m_defaultViewport->SetClearStencil(stencil);

		return TRUE;
	}

	return FALSE;
}

void CRuRenderContext::EnableRenderQueueSorting(BOOL enable)
{
	m_sortRenderQueue = enable;
}

void CRuRenderContext::UpdateDefaultViewportParameters()
{
	if(m_renderTarget->GetType().IsSubClassOf(IRuSurface::Type))
	{
		IRuSurface *surface = static_cast<IRuSurface *>(m_renderTarget);
		m_defaultViewport->SetViewportParameters(0, 0, surface->GetWidth(), surface->GetHeight(), 0.0f, 1.0f);
	}
	else if(m_renderTarget->GetType().IsSubClassOf(IRuSwapChain::Type))
	{
		IRuSwapChain *swapChain = static_cast<IRuSwapChain *>(m_renderTarget);
		m_defaultViewport->SetViewportParameters(0, 0, swapChain->GetWidth(), swapChain->GetHeight(), 0.0f, 1.0f);
	}
	else if(m_renderTarget->GetType().IsSubClassOf(IRuTexture::Type))
	{
		IRuTexture *texture = static_cast<IRuTexture *>(m_renderTarget);
		m_defaultViewport->SetViewportParameters(0, 0, texture->GetLevelWidth(0), texture->GetLevelHeight(0), 0.0f, 1.0f);
	}
	else
	{
		// Log an error
	}
}

BOOL CRuRenderContext::RegisterListener(IRuEntityEventListener *listener, void *userData)
{
	EventListenerDesc *listenerDesc = ruNEW EventListenerDesc();
	listenerDesc->m_listener = listener;
	listenerDesc->m_userData = userData;
	listenerDesc->m_next = NULL;

	if(m_eventListeners == NULL)
	{
		m_eventListeners = listenerDesc;
	}
	else
	{
		EventListenerDesc *curListener = m_eventListeners;
		while(curListener->m_next)
		{
			// Advance to next listener
			curListener = curListener->m_next;
		}

		curListener->m_next = listenerDesc;
	}

	return TRUE;
}

BOOL CRuRenderContext::UnregisterListener(IRuEntityEventListener *listener)
{
	EventListenerDesc *prevListener = NULL;
	EventListenerDesc *curListener = m_eventListeners;
	while(curListener)
	{
		if(curListener->m_listener == listener)
		{
			if(prevListener)
			{
				prevListener->m_next = curListener->m_next;
			}
			else
			{
				m_eventListeners = curListener->m_next;
			}

			delete curListener;

			return TRUE;
		}

		// Advance to next listener
		prevListener = curListener;
		curListener = curListener->m_next;
	}

	return FALSE;
}

/*!
	Raises an event with the specified source entity, event ID, and event parameter.

	Note that the entity event functions are not reentrant. Listeners must never
	modify the listener list during a signaled event. The only exception is if
	the listener removes itself from the list of listeners. This operation is
	supported to allow listeners to properly unregister during disposal.

	@param entity Entity that raised the event.
	@param eventProxy Proxy object that actually raised the event, or NULL.
	@param eventID Event ID.
	@param eventParameter0 Event parameter 0.
	@param eventParameter1 Event parameter 1.
	@param eventString Event string.
	@param eventDuration Event duration.

	@return None.
*/
void CRuRenderContext::RaiseEvent(CRuEntity *entity, IRuObject *eventProxy, INT32 eventID, PTRVALUE eventParameter0, PTRVALUE eventParameter1, const char *eventString, REAL eventDuration, BOOL propagateEvent)
{
	// Trigger any event listeners registered to this entity
	EventListenerDesc *curListener = m_eventListeners;
	while(curListener)
	{
		IRuEntityEventListener *listener = curListener->m_listener;
		void *userData = curListener->m_userData;

		// Advance to next listener
		curListener = curListener->m_next;

		// If the listener handles the event, return immediately
		if(listener->HandleEvent(entity, eventProxy, eventID, eventParameter0, eventParameter1, eventString, eventDuration, userData))
		{
			if(eventID >= ruENTITYEVENT_BROADCAST_CAMERA)
			{
			}
			else
			{
				return;
			}
		}
	}
}

BOOL CRuRenderContext::Viewport_Push(IRuViewport *viewport)
{
	// Push new viewport onto stack
	m_viewportStack.Push(viewport);

	return TRUE;
}

BOOL CRuRenderContext::Viewport_Pop()
{
	if(m_viewportStack.IsEmpty() == FALSE)
	{
		m_viewportStack.Pop();
		return TRUE;
	}

	return FALSE;
}

BOOL CRuRenderContext::ClipPlaneSet_Push(CRuClipPlaneSet *clipPlaneSet)
{
	// Push new clip plane set onto stack
	m_clipPlaneStack.Push(clipPlaneSet);

	return TRUE;
}

BOOL CRuRenderContext::ClipPlaneSet_Pop()
{
	if(m_clipPlaneStack.IsEmpty() == FALSE)
	{
		m_clipPlaneStack.Pop();
		return TRUE;
	}

	return FALSE;
}

BOOL CRuRenderContext::ParameterBlock_Push(CRuParameterBlock *paramBlock)
{
	m_extraParameterBlocks.Add(paramBlock);

	return TRUE;
}

BOOL CRuRenderContext::ParameterBlock_Pop()
{
	if(m_extraParameterBlocks.Count())
	{
		m_extraParameterBlocks.RemoveAt(m_extraParameterBlocks.Count() - 1);
		return TRUE;
	}

	return FALSE;
}

BOOL CRuRenderContext::Camera_Push(CRuCamera *camera)
{
	m_cameraStack.Push(camera);
	return TRUE;
}

BOOL CRuRenderContext::Camera_Pop()
{
	if(m_cameraStack.IsEmpty() == FALSE)
	{
		m_cameraStack.Pop();
		return TRUE;
	}

	return FALSE;
}

BOOL CRuRenderContext::EventSuppression_Push(RuEntityEvent suppressedEvent)
{
	m_eventSuppressionList.Add(suppressedEvent);

	return TRUE;
}

BOOL CRuRenderContext::EventSuppression_Pop()
{
	if(m_eventSuppressionList.Count() > 0)
	{
		RuEntityEvent poppedEvent = m_eventSuppressionList[m_eventSuppressionList.Count() - 1];

		m_eventSuppressionList.RemoveAt(m_eventSuppressionList.Count() - 1);

		return poppedEvent;
	}

	return ruENTITYEVENT_NONE;
}

BOOL CRuRenderContext::MaterialOverrideCallback_Push(CRuRenderContext::MaterialOverrideCallback overrideCallback)
{
	// Push previous callback onto push stack
	m_materialOverrideCallbackStack.Push(m_materialOverrideCallback);

	// Set new callback
	m_materialOverrideCallback = overrideCallback;

	return TRUE;
}

BOOL CRuRenderContext::MaterialOverrideCallback_Pop()
{
	// Assert on stack empty
	assert(m_materialOverrideCallbackStack.IsEmpty() == FALSE);

	// Pop previous callback from push stack
	m_materialOverrideCallback = m_materialOverrideCallbackStack.Pop();

	return TRUE;
}

BOOL CRuRenderContext::Submit(CRuEntity *sceneRoot, BOOL noStatusMod)
{
	RUPROFILE_SAMPLE_BEGIN(m_probe_3D_Submit, 2);
/*
	// If the entity is a valid decal target, put it into the list
	if(sceneRoot->GetOption(ruENTITYOPTION_DECALTARGET))
	{
		m_validDecalTargets.Add(sceneRoot);
	}
*/
	// Assertions
	assert(m_cameraStack.IsEmpty() == FALSE);

	// Shortcut to camera
	CRuCamera *camera = m_cameraStack.Peek();

	// Clear entity stack and then push scene root onto entity stack
	m_entityStack.Clear();
	m_entityStack.Push(sceneRoot);

	BOOL eventSurpressed_SceneSubmit = IsEventSuppressed(ruENTITYEVENT_BROADCAST_SUBMITTED);

	// Enumerate objects for rendering
	while(!m_entityStack.IsEmpty())
	{
		CRuEntity *curEntity = m_entityStack.Pop();

		// Notify attached listeners that an entity is being submitted for rendering
		if(eventSurpressed_SceneSubmit == FALSE)
		{
			this->RaiseEvent(curEntity, NULL, ruENTITYEVENT_BROADCAST_SUBMIT, reinterpret_cast<PTRVALUE>(this), reinterpret_cast<PTRVALUE>(camera), NULL, 0.0f, FALSE);
		}

		// Get basic entity options
		BOOL backdropEntity = curEntity->GetOption(ruENTITYOPTION_BACKDROP);
		BOOL ignoreVisibility = curEntity->GetOption(ruENTITYOPTION_IGNOREVISIBILITY) | curEntity->GetOption(ruENTITYOPTION_BUDISABLE);

		// Check for visibility in frustum
		if(!ignoreVisibility)
		{
			if(!backdropEntity)
			{
				if(IntersectsFrustum(camera->GetFrustumPlanes(), curEntity->GetWorldBounds()) == FALSE)
				{
					if(!noStatusMod)
						curEntity->SetStatusFlag(ruENTITYSTATUS_VISIBLE, FALSE);
					continue;
				}
			}
			else
			{
				if(IntersectsFrustum(camera->GetBackdropFrustumPlanes(), curEntity->GetWorldBounds()) == FALSE)
				{
					if(!noStatusMod)
						curEntity->SetStatusFlag(ruENTITYSTATUS_VISIBLE, FALSE);
					continue;
				}
			}
		}

		// Mark the entity as visible
		if(!noStatusMod)
			curEntity->SetStatusFlag(ruENTITYSTATUS_VISIBLE, TRUE);

		// If the entity is marked hidden, skip
		if(curEntity->GetOption(ruENTITYOPTION_HIDDEN) || curEntity->GetOption(ruENTITYOPTION_HIDDENBYDISTANCE))
		{
			continue;
		}

		// Broadcast scene submit event to all listeners
		if(eventSurpressed_SceneSubmit == FALSE)
		{
/*
			RuBroadcast_Submitted_Args eventArgs = { this, camera, TRUE };

			// Notify entity that it has been submitted for rendering
			curEntity->RaiseEvent(NULL, NULL, ruENTITYEVENT_BROADCAST_SUBMITTED, reinterpret_cast<PTRVALUE>(&eventArgs), NULL, NULL, 0.0f, FALSE);

			// Notify attached listeners that an entity that it has been submitted for rendering
			this->RaiseEvent(curEntity, NULL, ruENTITYEVENT_BROADCAST_SUBMITTED, reinterpret_cast<PTRVALUE>(&eventArgs), NULL, NULL, 0.0f, FALSE);

			// If we are not to continue with this submission, abort
			if(eventArgs.m_continueSubmission == FALSE)
			{
				continue;
			}
*/
		}

		// Push all children onto entity stack
		CRuEntity *curChild = curEntity->GetFirstChild();
		while(curChild)
		{
			// Push child onto work stack
			m_entityStack.Push(curChild);

			// Advance to next child
			curChild = curChild->GetNextSibling();
		}

		// If the entity is a subclass of the type portal, set its view camera (TODO: Move this code somewhere else)
		if(curEntity->GetType().IsSubClassOf(CRuContainer_Portal::Type))
		{
			static_cast<CRuContainer_Portal *>(curEntity)->SetCamera(camera);
		}
/*
		// If the entity is a subclass of the type anti-portal, insert it into the list of anti-portals
		if(curEntity->GetType().IsSubClassOf(CRuEntity_AntiPortal::Type))
		{
			m_antiPortals.Add(static_cast<CRuEntity_AntiPortal *>(curEntity));

			// Broadcast acting camera to the entity
			BOOL eventSurpressed_Broadcast_Camera = IsEventSuppressed(ruENTITYEVENT_BROADCAST_CAMERA);

			if(eventSurpressed_Broadcast_Camera == FALSE)
			{
				curEntity->RaiseEvent(NULL, ruENTITYEVENT_BROADCAST_CAMERA, reinterpret_cast<PTRVALUE>(camera), 0, NULL, 0.0f);
			}
		}
*/
		// If the entity is renderable, process it
		if(curEntity->GetType().IsSubClassOf(IRuEntity_Renderable::Type))
		{
			IRuEntity_Renderable *renderableEntity = static_cast<IRuEntity_Renderable *>(curEntity);

			// Create a render queue entry for every mesh in the renderable entity
			for(INT32 i = 0, numMeshes = renderableEntity->GetNumMeshes(); i < numMeshes; ++i)
			{
				// Get the i-th mesh and material
				IRuMesh *mesh = NULL;
				IRuMaterial *material = NULL;

				renderableEntity->GetMesh(i, &mesh, &material);

				// Submit to queue
				Submit_Internal(renderableEntity, NULL, mesh, material, i == 0);
			}
		}
	}

	RUPROFILE_SAMPLE_END(m_probe_3D_Submit, 2);

	return TRUE;
}

BOOL CRuRenderContext::Submit(CRuEntity *sceneRoot, IRuMesh *mesh, IRuMaterial *material)
{
	RUPROFILE_SAMPLE_BEGIN(m_probe_3D_Submit, 2);
/*
	// If the entity is a valid decal target, put it into the list
	if(sceneRoot->GetOption(ruENTITYOPTION_DECALTARGET))
	{
		m_validDecalTargets.Add(sceneRoot);
	}
*/
	BOOL eventSurpressed_SceneSubmit = IsEventSuppressed(ruENTITYEVENT_BROADCAST_SUBMITTED);
	BOOL abortSubmission = FALSE;

	// Broadcast scene submit event to all listeners
	if(eventSurpressed_SceneSubmit == FALSE)
	{
/*
		RuBroadcast_Submitted_Args eventArgs = { this, m_curCamera, TRUE };

		// Notify entity that it has been submitted for rendering
		sceneRoot->RaiseEvent(NULL, NULL, ruENTITYEVENT_BROADCAST_SUBMITTED, reinterpret_cast<PTRVALUE>(&eventArgs), NULL, NULL, 0.0f, FALSE);

		// Notify attached listeners that an entity that it has been submitted for rendering
		this->RaiseEvent(sceneRoot, NULL, ruENTITYEVENT_BROADCAST_SUBMITTED, reinterpret_cast<PTRVALUE>(&eventArgs), NULL, NULL, 0.0f, FALSE);

		// If we are not to continue with this submission, abort
		if(eventArgs.m_continueSubmission == FALSE)
		{
			abortSubmission = TRUE;
		}
*/
	}

	if(!abortSubmission)
	{
		// Submit to queue
		Submit_Internal(sceneRoot, NULL, mesh, material, TRUE);
	}

	RUPROFILE_SAMPLE_END(m_probe_3D_Submit, 2);

	return TRUE;
}

void CRuRenderContext::Begin()
{
	// Reset statistics
	m_numPrimitivesRendered = 0;
	m_numBatchesIssued = 0;

	// Reset variables
	m_curViewport = NULL;
	m_curClipPlaneSet = NULL;

	// Update default viewport size
	UpdateDefaultViewportParameters();

	// Push default viewport onto viewport stack
	m_viewportStack.Push(m_defaultViewport);

	// Increment iteration
	++m_iteration;
}

void CRuRenderContext::End()
{
	// Clear rendering structures
	m_viewportStack.Clear();
	m_clipPlaneStack.Clear();
	m_cameraStack.Clear();

	// Clear matrix stack
	FlushMatrixStack();

	// Clear parameter cache stack
	FlushParameterCacheStack();

	// Reset variables
	m_curViewport = NULL;
	m_curClipPlaneSet = NULL;
}

void CRuRenderContext::Execute()
{
	DWORD param;

	// Error check
	if(!g_ruInstanceManager)
		return;

	// Get local references to system textures
	IRuTexture *nullTexture = g_ruResourceManager->LoadTexture("RUSYSTEM_NULL");
	IRuTexture *clampTexture = g_ruResourceManager->LoadTexture("RUSYSTEM_CLAMP");

	// Begin time sampling for the flush
	RUPROFILE_SAMPLE_BEGIN(m_probe_3D_Flush, 0);

	// Setup render target
	SelectRenderTarget();

	// Sort the rendering queue
	RUPROFILE_SAMPLE_BEGIN(m_probe_3D_Sorting, 0);
	if(m_sortRenderQueue)
	{
		SortRenderQueue();
	}
	RUPROFILE_SAMPLE_END(m_probe_3D_Sorting, 0);

	// Iterate through rendering queue and render each submodel
	UINT32 *sortedIndices = m_radixSort.GetSortedIndices();

	for(INT32 i = 0, numRQEntries = m_renderQueue.Count(); i < numRQEntries; ++i)
	{
		RuRenderQueueEntry &curEntry = m_renderQueue[m_sortRenderQueue ? sortedIndices[i] : i];
/*
		// Check visibility against anti-portal
		for(INT32 i = 0; i < m_antiPortals.Count(); ++i)
		{
			if(curEntry.m_entity && m_antiPortals[i]->IsOccluded(curEntry.m_entity->GetWorldBounds()))
			{
				// Increment probe
				RUPROFILE_SAMPLE_ACCUMULATE(m_probe_Misc_AntiPortalDiscarded, 0, 1);

				// Release mesh and material references
				ruSAFE_RELEASE(curEntry.m_mesh);
				ruSAFE_RELEASE(curEntry.m_material);

				continue;
			}
		}
*/
		// If the render entry has a viewport specified, set it now
		if(m_curViewport != curEntry.m_viewport)
		{
			// Set current viewport index
			m_curViewport = curEntry.m_viewport;

			// Select viewport
			m_curViewport->SelectAsViewport();

			// Clear viewport if necessary
			if(m_curViewport->GetIteration() != m_iteration)
			{
				// Clear viewport according to its clear parameters
				g_ruEngine->Clear(m_curViewport->GetClearFlags(), m_curViewport->GetClearColor(), m_curViewport->GetClearZ(), m_curViewport->GetClearStencil());

				// Update viewport iteration (to mark it as having been cleared)
				m_curViewport->SetIteration(m_iteration);
			}
		}

		// If the render entry has a clip plane set specified, apply it now
		if(m_curClipPlaneSet != curEntry.m_clipPlaneSet)
		{
			// Set current clip plane set
			m_curClipPlaneSet = curEntry.m_clipPlaneSet;

			if(m_curClipPlaneSet)
			{
				for(INT32 cpIdx = 0; cpIdx < 6; ++cpIdx)
				{
					const CRuPlane4 &clipPlane = m_curClipPlaneSet->GetClipPlane(cpIdx);

					if(clipPlane.IsZero() == FALSE)
					{
						g_ruEngine->SetClipPlane(cpIdx, clipPlane);
						g_ruEngine->SetClipPlaneEnable(cpIdx, TRUE);
					}
					else
					{
						g_ruEngine->SetClipPlaneEnable(cpIdx, FALSE);
					}
				}
			}
			else
			{
				for(INT32 cpIdx = 0; cpIdx < 6; ++cpIdx)
				{
					g_ruEngine->SetClipPlaneEnable(cpIdx, FALSE);
				}
			}
		}

		// Ensure the mesh, material, and shader are valid
		if(curEntry.m_mesh && curEntry.m_material && curEntry.m_shader && curEntry.m_mesh->GetNumVertices() > 0 && curEntry.m_mesh->GetNumIndices() > 0)
		{
			IRuShader *shader = curEntry.m_shader;

			// Clear the parameter cache
			m_paramCache0.ClearParameterCache();
			m_paramCache1.ClearParameterCache();

			m_paramCache0.CacheParameterBlock(&m_owner->GetDefaultParameterBlock());

			RUPROFILE_SAMPLE_BEGIN(m_probe_3D_ParameterSetup, 2);

			// Set default parameters into shader
			shader->SetDefaultParameters();

			// Set outer loop parameters into shader
			shader->SetParameters(&m_paramCache0);

			// Set entity parameters into shader
			shader->SetParameters(curEntry.m_paramCache);

			RUPROFILE_SAMPLE_END(m_probe_3D_ParameterSetup, 2);

			RUPROFILE_SAMPLE_BEGIN(m_probe_3D_RenderAllPasses, 4);

			// Iterate through all passes of the shader
			for(INT32 curPass = 0, numPasses = shader->GetNumPasses(); curPass < numPasses; ++curPass)
			{
				RUPROFILE_SAMPLE_BEGIN(m_probe_3D_TextureSetup, 5);

				// Setup texture bindings
				for(INT32 curSampler = 0, numSamplers = shader->GetNumSamplers(curPass); curSampler < numSamplers; ++curSampler)
				{
					RuTextureChannel texChannel = shader->GetTextureChannel(curPass, curSampler);
					IRuBaseTexture *texture = curEntry.m_material->GetTexture(texChannel);

					if(texture)
					{
						IRuBaseTexture *instancedTexture = g_ruInstanceManager->GetInstance(texture);
						instancedTexture->Select(curSampler);
//						texture->Select(curSampler);
					}
					else
					{
						if(texChannel == ruTEXCHANNEL_SYSTEM_CLAMP)
						{
							IRuBaseTexture *instancedTexture = g_ruInstanceManager->GetInstance(clampTexture);
							instancedTexture->Select(curSampler);
//							m_clampTexture->Select(curSampler);
						}
						else
						{
							// Force NULL texture
							IRuBaseTexture *instancedTexture = g_ruInstanceManager->GetInstance(nullTexture);
							instancedTexture->Select(curSampler);
//							m_nullTexture->Select(curSampler);
						}
					}
				}

				RUPROFILE_SAMPLE_END(m_probe_3D_TextureSetup, 5);

				if(curEntry.m_paramCache->GetParameter(ruPARAMTYPE_TWEEN_FACTOR, param))
				{
					RUPROFILE_SAMPLE_BEGIN(m_probe_3D_Morphing, 5);

					REAL tweenFactor = RuCastDWORDToFloat(param);
					curEntry.m_mesh->SetMorphCurveTime(tweenFactor);

					RUPROFILE_SAMPLE_END(m_probe_3D_Morphing, 5);
				}

				// End time sampling for the instancing
				RUPROFILE_SAMPLE_BEGIN(m_probe_Engine_InstancingTime, 5);

				// Instance the mesh
				IRuInstancedMesh *instancedMesh = g_ruInstanceManager->GetInstance(curEntry.m_mesh, shader);

				// End time sampling for the instancing
				RUPROFILE_SAMPLE_END(m_probe_Engine_InstancingTime, 5);

				// Abort if the mesh could not be instanced
				if(instancedMesh == NULL)
					continue;

				// Render each batch
				for(INT32 curBatch = 0; ; ++curBatch)
				{
					INT32 batchOffset, batchNumPrimitives;

					// Get batch settings
					if(curEntry.m_mesh->GetBatch(curBatch, batchOffset, batchNumPrimitives) == FALSE)
						break;

					// Set batch parameters into shader
					const CRuParameterBlock *paramBlock = curEntry.m_mesh->GetBatchParameterBlock(curBatch);
					if(paramBlock)
					{
						m_paramCache1.CacheParameterBlock(paramBlock);
					}
					shader->SetParameters(&m_paramCache1);

					// Render
					RUPROFILE_SAMPLE_BEGIN(m_probe_3D_Render, 5);

					shader->BeginPass(curPass);

					RUPROFILE_SAMPLE_BEGIN(m_probe_3D_RenderD3D, 5);

					m_numPrimitivesRendered += instancedMesh->Render(batchOffset, batchNumPrimitives, 0, -1);

					RUPROFILE_SAMPLE_END(m_probe_3D_RenderD3D, 5);

					++m_numBatchesIssued;
					shader->EndPass(curPass);

					RUPROFILE_SAMPLE_END(m_probe_3D_Render, 5);
				}
			}

			RUPROFILE_SAMPLE_END(m_probe_3D_RenderAllPasses, 4);
		}


		// debugging
//		assert(curEntry.m_mesh->GetRefCount() > 1);
//		assert(curEntry.m_material->GetRefCount() > 1);

		ruSAFE_RELEASE(curEntry.m_mesh);
		ruSAFE_RELEASE(curEntry.m_material);

	}

	// Clear valid decal targets list
//	m_validDecalTargets.Clear();

	// Clear the render queue
	m_renderQueue.Clear();

	// Release local references to textures
	ruSAFE_RELEASE(nullTexture);
	ruSAFE_RELEASE(clampTexture);

	// End time sampling for the flush
	RUPROFILE_SAMPLE_END(m_probe_3D_Flush, 0);

	return;
}

BOOL CRuRenderContext::IsEventSuppressed(RuEntityEvent entityEvent)
{
	for(INT32 i = 0; i < m_eventSuppressionList.Count(); ++i)
	{
		if(m_eventSuppressionList[i] == entityEvent)
		{
			return TRUE;
		}
	}

	return FALSE;
}

void CRuRenderContext::Submit_Internal(CRuEntity *sceneRoot, CRuMatrix4x4 const *worldTransform, IRuMesh *mesh, IRuMaterial *material, BOOL firstEntityInSeries)
{
	INT32 billboardType = ruBILLBOARDTYPE_NONE;
	BOOL isBackdrop = FALSE;

	// Ensure the render target is non-null
	assert(m_renderTarget);

	// Assertions
	assert(m_cameraStack.IsEmpty() == FALSE);

	// Shortcut to camera
	CRuCamera *camera = m_cameraStack.Peek();

	if(worldTransform == NULL)
	{
		if(sceneRoot)
		{
			worldTransform = &sceneRoot->GetWorldTransform();
		}
	else
		{
			worldTransform = &m_identityTransform;
		}
	}

	RuRenderQueueEntry &rqEntry = AllocateRenderQueueEntry();

	rqEntry.m_viewport = m_viewportStack.Peek();
	rqEntry.m_clipPlaneSet = m_clipPlaneStack.IsEmpty() ? NULL : m_clipPlaneStack.Peek();
	rqEntry.m_worldTransform = worldTransform;
	rqEntry.m_paramCache = CreateParameterCache();

	rqEntry.m_mesh = mesh;
	rqEntry.m_material = material;

	// Override material if necessary
	if(m_materialOverrideCallback)
	{
		IRuMaterial *overrideMaterial = m_materialOverrideCallback(rqEntry.m_material);
		if(overrideMaterial)
		{
			rqEntry.m_material = overrideMaterial;
		}
	}

	// Store pointer to shader
	rqEntry.m_shader = rqEntry.m_material->GetShader();

	// Make sure material has a valid shader
	if(rqEntry.m_shader == NULL)
	{
		UndoAllocateRenderQueueEntry();
		return;
	}

	// Increment reference on mesh & material
	rqEntry.m_mesh->AddRef();
	rqEntry.m_material->AddRef();

	// debugging
	rqEntry.m_entity = sceneRoot;

	// Clear parameter block queue
	m_parameterBlocks.Clear();

	// Additional parameter blocks
	for(INT32 i = 0; i < m_extraParameterBlocks.Count(); ++i)
	{
		m_parameterBlocks.Queue(m_extraParameterBlocks[i]);
	}

	// Set material parameters into shader
	const CRuParameterBlock *matParamBlock = rqEntry.m_material->GetParameterBlock();
	if(matParamBlock)
	{
		m_parameterBlocks.Queue(matParamBlock);
	}

	if(sceneRoot)
	{
		// Find all parameter blocks and queue them into the parameter block queue
		CRuEntity *curEntity = sceneRoot;
		while(curEntity != NULL)
		{
			// NOTE: multiple parameter blocks for an entity must be added backwards!
			// otherwise the order of execution is not correct!

			// If the parent entity has any shader parameter blocks, append them to the queue
			m_parameterBlocks.Queue(curEntity->GetParameterBlock());

			// Go up one level in the tree
			curEntity = curEntity->GetParent();
		}

		// Set lighting information into the shader (TODO: Only do this if required by shader)
		RUPROFILE_SAMPLE_BEGIN(m_probe_3D_Lighting, 2);

		RuLightingInfo *lightingInfo = m_owner->BuildLightingInfo(static_cast<IRuEntity_Renderable *>(sceneRoot));

		rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_LIGHT_AMBIENT, (DWORD) &lightingInfo->m_ambientLight);
		rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_LIGHT_OBJ_SH9, (DWORD) lightingInfo->m_shCoefficients);

		// Set specular lighting parameters
		rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_LIGHT_SPECULAR_DIR, (DWORD) &lightingInfo->m_specularDirection);
		rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_LIGHT_SPECULAR_COLOR, (DWORD) lightingInfo->m_specularColor);

		RUPROFILE_SAMPLE_END(m_probe_3D_Lighting, 2);

		// Get the entity's billboard type
		billboardType = sceneRoot->GetOption(ruENTITYOPTION_BILLBOARD);

		// Cache the projection transform
		isBackdrop = sceneRoot->GetOption(ruENTITYOPTION_BACKDROP);

		// Broadcast events
		if(firstEntityInSeries)
		{
			BOOL eventSurpressed_Broadcast_Camera = IsEventSuppressed(ruENTITYEVENT_BROADCAST_CAMERA);
			BOOL eventSurpressed_Broadcast_DecalTarget = IsEventSuppressed(ruENTITYEVENT_BROADCAST_DECALTARGET);

			// Broadcast acting camera to the entity
			if(eventSurpressed_Broadcast_Camera == FALSE)
			{
				sceneRoot->RaiseEvent(NULL, ruENTITYEVENT_BROADCAST_CAMERA, reinterpret_cast<PTRVALUE>(camera), 0, NULL, 0.0f);
			}
/*
			// Decal handling -- broadcast valid decal target array to the entity
			if(eventSurpressed_Broadcast_DecalTarget == FALSE)
			{
				sceneRoot->RaiseEvent(NULL, ruENTITYEVENT_BROADCAST_DECALTARGET, reinterpret_cast<PTRVALUE>(&m_validDecalTargets), 0, NULL, 0.0f);
			}
*/
		}

		// If the mesh is skinned and there is a hierarchy available, skin it
		if(sceneRoot->GetOption(ruENTITYOPTION_SKINNED))
		{
			// Find the frame hierarchy to use for this entity ...
			CRuFrameHierarchy *frameHierarchy = NULL;
			CRuHierarchy_GR2 *hierarchy_GR2 = NULL;
			CRuEntity *parentEntity = sceneRoot->GetParent();
			while(parentEntity != NULL)
			{
				// Use the parent entity as the frame hierarchy if it is one and if a frame hierarchy hasn't already been found
				if(frameHierarchy == NULL && parentEntity->GetType().IsSubClassOf(CRuFrameHierarchy::Type))
				{
					frameHierarchy = static_cast<CRuFrameHierarchy *>(parentEntity);
				}

				if(hierarchy_GR2 == NULL && parentEntity->GetType().IsSubClassOf(CRuHierarchy_GR2::Type))
				{
					hierarchy_GR2 = static_cast<CRuHierarchy_GR2 *>(parentEntity);
				}

				// Go up one level in the tree
				parentEntity = parentEntity->GetParent();
			}

			if(frameHierarchy || hierarchy_GR2)
			{
				// Enable skinning
				rqEntry.m_material->SetSkinned(TRUE);

				// Get cached skin from the skin cache
				RuCachedSkin cachedSkin = m_owner->GetSkinCache()->GetCachedSkin(rqEntry.m_mesh);

				if(cachedSkin.m_subdividedMeshes)
				{
					RuRenderQueueEntry queueEntryCopy = rqEntry;

					// Queue the first subdivided mesh (there is always at least one mesh)
					ruSAFE_RELEASE(rqEntry.m_mesh);
					rqEntry.m_mesh = cachedSkin.m_subdividedMeshes->Get(0);
					rqEntry.m_mesh->AddRef();

					// Queue the remaining subdivided meshes
					for(INT32 k = 1; k < cachedSkin.m_subdividedMeshes->Count(); ++k)
					{
						RuRenderQueueEntry &copiedEntry = AllocateRenderQueueEntry();
						copiedEntry = queueEntryCopy;

						copiedEntry.m_mesh = cachedSkin.m_subdividedMeshes->Get(k);
						copiedEntry.m_mesh->AddRef();
						copiedEntry.m_material->AddRef();
					}
				}

				// Hierarchy stuff
				if(frameHierarchy)
				{
					rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_SKIN_NODECOUNT, static_cast<DWORD>(frameHierarchy->GetNumSubNodes()));
					rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_SKIN_TRANSFORMS, reinterpret_cast<DWORD>(frameHierarchy->GetSubNodeTransforms()));
				}
				else if(hierarchy_GR2)
				{
					rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_SKIN_NODECOUNT, static_cast<DWORD>(hierarchy_GR2->GetNumSubNodes()));
					rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_SKIN_TRANSFORMS, reinterpret_cast<DWORD>(hierarchy_GR2->GetSubNodeTransforms()));
				}
			}
		}
	}

	// Set default scene parameters into shader
	rqEntry.m_paramCache->CacheParameterBlock(&m_owner->GetDefaultParameterBlock());

	// Apply parameter blocks backwards to ensure correct priority
	while(m_parameterBlocks.IsEmpty() == FALSE)
	{
		// Cache all parameters in the parameter block
		rqEntry.m_paramCache->CacheParameterBlock(m_parameterBlocks.DequeueTail());
	}

	RUPROFILE_SAMPLE_BEGIN(m_probe_3D_TransformSetup, 2);

	// Cache projection transform
	if(isBackdrop)
	{
		rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_TRANSFORM_PROJECTION, (DWORD) &camera->GetBackdropProjectionTransform());
	}
	else
	{
		rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_TRANSFORM_PROJECTION, (DWORD) &camera->GetProjectionTransform());
	}

	// Check for billboarding here? And override all of the transforms?
	switch(billboardType)
	{
		// Normal entity, no billboarding
		case ruBILLBOARDTYPE_NONE:
			{
				rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_TRANSFORM_WORLD, (DWORD) rqEntry.m_worldTransform);
				rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_TRANSFORM_VIEW, (DWORD) &camera->GetCameraTransform());

				if(firstEntityInSeries && sceneRoot)
				{
					sceneRoot->RaiseEvent(NULL, ruENTITYEVENT_BROADCAST_WORLD, reinterpret_cast<PTRVALUE>(rqEntry.m_worldTransform), 0, NULL, 0.0f);
					sceneRoot->RaiseEvent(NULL, ruENTITYEVENT_BROADCAST_VIEW, reinterpret_cast<PTRVALUE>(&camera->GetCameraTransform()), 0, NULL, 0.0f);
				}
			}

			break;

		// Unconstrained billboard
		case ruBILLBOARDTYPE_UNCONSTRAINED:
			{
				CRuMatrix4x4 viewBillboardingMatrix;
				viewBillboardingMatrix.SetToBillboard(camera->GetCameraTransform(), CRuVector3(0.0f, 0.0f, -1.0f), CRuVector3(0.0f, 1.0f, 0.0f));

				CRuMatrix4x4 inverseTranslationMatrix;
				inverseTranslationMatrix.SetToTranslation(-(*rqEntry.m_worldTransform)._41, -(*rqEntry.m_worldTransform)._42, -(*rqEntry.m_worldTransform)._43);

				CRuMatrix4x4 translationMatrix;
				translationMatrix.SetToTranslation((*rqEntry.m_worldTransform)._41, (*rqEntry.m_worldTransform)._42, (*rqEntry.m_worldTransform)._43);

				CRuMatrix4x4 *modifiedWorldMatrix = CreateMatrix();
				*modifiedWorldMatrix = (*rqEntry.m_worldTransform) * inverseTranslationMatrix * viewBillboardingMatrix * translationMatrix;

				rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_TRANSFORM_WORLD, (DWORD) modifiedWorldMatrix);
				rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_TRANSFORM_VIEW, (DWORD) &camera->GetCameraTransform());

				if(firstEntityInSeries && sceneRoot)
				{
					sceneRoot->RaiseEvent(NULL, ruENTITYEVENT_BROADCAST_WORLD, reinterpret_cast<PTRVALUE>(modifiedWorldMatrix), 0, NULL, 0.0f);
					sceneRoot->RaiseEvent(NULL, ruENTITYEVENT_BROADCAST_VIEW, reinterpret_cast<PTRVALUE>(&camera->GetCameraTransform()), 0, NULL, 0.0f);
				}
			}

			break;

		case ruBILLBOARDTYPE_AXIAL:
			{
				CRuVector3 billboardAxis(0.0f, 1.0f, 0.0f);
				rqEntry.m_worldTransform->TransformVector(billboardAxis);
				billboardAxis.Normalize();

				CRuVector3 forwardAxis(0.0f, 0.0f, -1.0f);
				rqEntry.m_worldTransform->TransformVector(forwardAxis);
				forwardAxis.Normalize();

				CRuMatrix4x4 viewBillboardingMatrix;
				viewBillboardingMatrix.SetToAxialBillboard(camera->GetCameraTransform(), forwardAxis, billboardAxis);

				CRuMatrix4x4 inverseTranslationMatrix;
				inverseTranslationMatrix.SetToTranslation(-(*rqEntry.m_worldTransform)._41, -(*rqEntry.m_worldTransform)._42, -(*rqEntry.m_worldTransform)._43);

				CRuMatrix4x4 translationMatrix;
				translationMatrix.SetToTranslation((*rqEntry.m_worldTransform)._41, (*rqEntry.m_worldTransform)._42, (*rqEntry.m_worldTransform)._43);

				CRuMatrix4x4 *modifiedWorldMatrix = CreateMatrix();
				*modifiedWorldMatrix = (*rqEntry.m_worldTransform) * inverseTranslationMatrix * viewBillboardingMatrix * translationMatrix;

				rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_TRANSFORM_WORLD, (DWORD) modifiedWorldMatrix);
				rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_TRANSFORM_VIEW, (DWORD) &camera->GetCameraTransform());

				if(firstEntityInSeries && sceneRoot)
				{
					sceneRoot->RaiseEvent(NULL, ruENTITYEVENT_BROADCAST_WORLD, reinterpret_cast<PTRVALUE>(modifiedWorldMatrix), 0, NULL, 0.0f);
					sceneRoot->RaiseEvent(NULL, ruENTITYEVENT_BROADCAST_VIEW, reinterpret_cast<PTRVALUE>(&camera->GetCameraTransform()), 0, NULL, 0.0f);
				}
			}

			break;

		case ruBILLBOARDTYPE_AXIAL_CUSTOM:
			{
				CRuMatrix4x4 combinedWorldViewTransform = (*rqEntry.m_worldTransform) * camera->GetCameraTransform();

				CRuMatrix4x4 *identityMatrix = CreateMatrix();
				identityMatrix->SetToIdentity();

				CRuMatrix4x4 *aggregateMatrix = CreateMatrix();
				aggregateMatrix->SetToAxialBillboard(combinedWorldViewTransform, CRuVector3(0.0f, 0.0f, -1.0f), CRuVector3(0.0f, 1.0f, 0.0f));
				*aggregateMatrix = (*aggregateMatrix) * combinedWorldViewTransform;

				rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_TRANSFORM_WORLD, (DWORD) identityMatrix);
				rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_TRANSFORM_VIEW, (DWORD) aggregateMatrix);
			}

			break;
	}

	RUPROFILE_SAMPLE_END(m_probe_3D_TransformSetup, 2);

	// Retrieve alpha value and store entity priority
	// Retrieve force alpha blend
	DWORD parameter;
	BOOL forceAlphaBlend = FALSE;
	REAL globalAlpha = 1.0f;
	BOOL invertSortOrder = FALSE;
	BOOL ignoreAlphaSort = FALSE;

	if(rqEntry.m_paramCache->GetParameter(ruPARAMTYPE_MATERIAL_TRANSLUCENT, parameter))
	{
		forceAlphaBlend = parameter ? TRUE : FALSE;
	}

	if(rqEntry.m_paramCache->GetParameter(ruPARAMTYPE_MATERIAL_ALPHA, parameter))
	{
		globalAlpha = RuCastDWORDToFloat(parameter) * globalAlpha;
	}

	if(rqEntry.m_paramCache->GetParameter(ruPARAMTYPE_MATERIAL_ALPHAMODIFIER, parameter))
	{
		globalAlpha = RuCastDWORDToFloat(parameter) * globalAlpha;
	}

	if(rqEntry.m_paramCache->GetParameter(ruPARAMTYPE_SYSTEM_INVERTSORTORDER, parameter))
	{
		invertSortOrder = parameter;
	}

	if(rqEntry.m_paramCache->GetParameter(ruPARAMTYPE_SYSTEM_IGNOREALPHASORT, parameter))
	{
		ignoreAlphaSort = parameter;
	}

	// If alpha blending is enabled, set the priority accordingly
	if((globalAlpha < 1.0f || forceAlphaBlend) && sceneRoot && !ignoreAlphaSort)
	{
		CRuVector3 entityPos(sceneRoot->GetWorldBounds().Center());
		camera->GetFrustumTransform().TransformPointProjected(entityPos);

		if(invertSortOrder)
		{
			rqEntry.m_priority = min(0.0f, -entityPos.z) - ruEPSILON;
		}
		else
		{
			rqEntry.m_priority = max(0.0f, 1.0f - entityPos.z) + ruEPSILON;
		}
	}
	else
	{
		rqEntry.m_priority = 0.0f;
	}

	// Set default alpha
	rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_MATERIAL_ALPHAMODE, ruALPHAMODE_NORMAL);
	rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_MATERIAL_ALPHA, RuCastFloatToDWORD(1.0f));

	// Store pointer to camera
	rqEntry.m_paramCache->CacheParameter(ruPARAMTYPE_CAMERA, reinterpret_cast<DWORD>(camera));
}

BOOL CRuRenderContext::SelectRenderTarget()
{
	// Debug assert
	assert(m_renderTarget);

	// Select the render target
	if(m_renderTarget->GetType().IsSubClassOf(IRuSurface::Type))
	{
		static_cast<IRuSurface *>(m_renderTarget)->SelectAsRenderTarget(0);
	}
	else if(m_renderTarget->GetType().IsSubClassOf(IRuSwapChain::Type))
	{
		static_cast<IRuSwapChain *>(m_renderTarget)->SelectAsRenderTarget(0);
	}
	else if(m_renderTarget->GetType().IsSubClassOf(IRuTexture::Type))
	{
		// NOTE: When we're given a texture instead of a surface, level 0 is always selected as the render target
		static_cast<IRuTexture *>(m_renderTarget)->SelectAsRenderTarget(0, 0);
	}
	else
	{
		// Log an error
		return FALSE;
	}

	// Select the depth stencil surface
	if(m_depthStencilSurface)
	{
		m_depthStencilSurface->SelectDepthStencil();
	}
	else
	{
		g_ruEngine->RestoreDefaultDepthStencil();
	}

	return TRUE;
}

void CRuRenderContext::SortRenderQueue()
{
	for(INT32 i = 0; i < 7; ++i)
	{
		m_sortCriterias[i].SetNumEntries(m_renderQueue.Count());
	}

	for(INT32 i = 0, j = m_renderQueue.Count(); i < j; ++i)
	{
		RuRenderQueueEntry &curEntry = m_renderQueue[i];

		IRuShader *shader = curEntry.m_shader;

		// Use shader class as the third sorting criteria
		m_sortCriterias[0][i] = shader->GetShaderClass();

		// Use entity priority as the sixth sorting criteria
		m_sortCriterias[1][i] = RuCastFloatToDWORD(curEntry.m_priority);

		// Use shader priority as the third sorting criteria
		m_sortCriterias[2][i] = shader->GetShaderPriority();

		// Use shader address as the fourth sorting criteria
		m_sortCriterias[3][i] = reinterpret_cast<UINT32>(shader);

		// Use material priority as the fifth sorting criteria
		m_sortCriterias[4][i] = curEntry.m_material->GetMaterialPriority();

		// Fill the rest of the sorting criteria using textures
		for(INT32 curSampler = 0; curSampler < 2; ++curSampler)
		{
			RuTextureChannel samplerChannel = shader->GetTextureChannel(0, curSampler);
			IRuBaseTexture *samplerTexture = NULL;

			if(samplerChannel != ruTEXCHANNEL_INVALID)
			{
				samplerTexture = curEntry.m_material->GetTexture(samplerChannel);
			}

			m_sortCriterias[curSampler + 5][i] = reinterpret_cast<UINT32>(samplerTexture);
		}
	}

	// Perform radix sort backwards
	for(INT32 i = 6; i >= 0; --i)
	{
		if(i == 3)
			m_radixSort.Sort((REAL *) m_sortCriterias[i].GetArray(), m_renderQueue.Count(), (i == 6) ? FALSE : TRUE);
		else
			m_radixSort.Sort((UINT32 *) m_sortCriterias[i].GetArray(), m_renderQueue.Count(), FALSE, (i == 6) ? FALSE : TRUE);
	}
}

CRuMatrix4x4 *CRuRenderContext::CreateMatrix()
{
	CRuMatrix4x4 *newMatrix = NULL;

	// Create matrix
	if(m_unusedMatrixStack.IsEmpty())
	{
		newMatrix = ruNEW CRuMatrix4x4();
	}
	else
	{
		newMatrix = m_unusedMatrixStack.Pop();
	}

	// Push matrix onto used matrix stack
	m_usedMatrixStack.Push(newMatrix);

	return newMatrix;
}

void CRuRenderContext::FlushMatrixStack()
{
	while(m_usedMatrixStack.IsEmpty() == FALSE)
	{
		m_unusedMatrixStack.Push(m_usedMatrixStack.Pop());
	}
}

CRuParameterCache *CRuRenderContext::CreateParameterCache()
{
	CRuParameterCache *paramCache = NULL;

	// Get a parameter cache object
	if(m_unusedParameterCacheStack.IsEmpty())
	{
		paramCache = ruNEW CRuParameterCache();
	}
	else
	{
		paramCache = m_unusedParameterCacheStack.Pop();
	}

	// Push parameter cache object onto used stack
	m_usedParameterCacheStack.Push(paramCache);

	// Clear the parameter cache
	paramCache->ClearParameterCache();

	return paramCache;
}

void CRuRenderContext::FlushParameterCacheStack()
{
	while(m_usedParameterCacheStack.IsEmpty() == FALSE)
	{
		m_unusedParameterCacheStack.Push(m_usedParameterCacheStack.Pop());
	}
}

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

CRuRenderer::CRuRenderer()
:	m_nullTexture(NULL),
	m_clampTexture(NULL)
{
	// Set default parameters
	m_defaultParamBlock.SetParameter(ruPARAMTYPE_MATERIAL_ALPHA, RuCastFloatToDWORD(1.0f));
	m_defaultParamBlock.SetParameter(ruPARAMTYPE_MATERIAL_DIFFUSE, 0xFFFFFFFF);

	m_defaultParamBlock.SetParameter(ruPARAMTYPE_MATERIAL_SELFILLUMINATION, RuCastFloatToDWORD(0.0f));

	m_defaultParamBlock.SetParameter(ruPARAMTYPE_MATERIAL_2SIDED, FALSE);
	m_defaultParamBlock.SetParameter(ruPARAMTYPE_MATERIAL_FACETED, FALSE);
	m_defaultParamBlock.SetParameter(ruPARAMTYPE_MATERIAL_WIRE, FALSE);
	m_defaultParamBlock.SetParameter(ruPARAMTYPE_MATERIAL_ALPHATESTMODE, TRUE);
	m_defaultParamBlock.SetParameter(ruPARAMTYPE_MATERIAL_ALPHATESTVALUE, 150);

	m_defaultParamBlock.SetParameter(ruPARAMTYPE_MATERIAL_ALPHAMODE, ruALPHAMODE_NORMAL);

	// Set default fog
	REAL fogStart = 100.0f, fogEnd = 1500.0f;
	RUCOLOR fogColor = RUCOLOR_ARGB(255, 100, 100, 100);
	m_defaultParamBlock.SetParameter(ruPARAMTYPE_FOG_START, RuCastFloatToDWORD(fogStart));
	m_defaultParamBlock.SetParameter(ruPARAMTYPE_FOG_END, RuCastFloatToDWORD(fogEnd));
//	m_defaultParamBlock.SetParameter(ruPARAMTYPE_FOG_COLOR, (DWORD) fogColor);

	// Reverse cull disabled by default
	m_defaultParamBlock.SetParameter(ruPARAMTYPE_FX_REVERSECULL, FALSE);

	// Create skin cache
	m_skinCache = ruNEW CRuSkinCache();

	// Create the light tree
	m_lightTree = ruNEW CRuAABBTree();

/*
	SH9 Constants
	-------------

	normalisation = PI * 16 / 17
	const1 = 0.282095^2 * normalisation * 1
	const2 = 0.488603^2 * normalisation * (2 / 3)
	const3 = 1.092548^2 * normalisation * (1 / 4)
	const4 = 0.315392^2 * normalisation * (1 / 4)
	const5 = 0.546274^2 * normalisation * (1 / 4)
*/

	// Pre-calculate 3rd-order spherical harmonic constants
	REAL normalization = PI * 16.0f / 17.0f;
	m_sh9_Const1 = 0.282095f * 0.282095f * normalization * 1.0f;
	m_sh9_Const2 = 0.488603f * 0.488603f * normalization * (2.0f /3.0f);
	m_sh9_Const3 = 1.092548f * 1.092548f * normalization * (1.0f /4.0f);
	m_sh9_Const4 = 0.315392f * 0.315392f * normalization * (1.0f /4.0f);
	m_sh9_Const5 = 0.546274f * 0.546274f * normalization * (1.0f /4.0f);
}

CRuRenderer::~CRuRenderer()
{
	// Free lighting tree
	ruSAFE_RELEASE(m_lightTree);

	// Free skin cache
	ruSAFE_RELEASE(m_skinCache);

	// Release all allocated renderer commands
	for(INT32 i = 0; i < m_allocatedRendererCommands.Count(); ++i)
	{
		ruSAFE_RELEASE(m_allocatedRendererCommands[i]);
	}
	m_allocatedRendererCommands.Clear();

	// Free unused render contexts
	while(m_unusedRenderContexts.IsEmpty() == FALSE)
	{
		CRuRenderContext *renderContext = m_unusedRenderContexts.Pop();
		ruSAFE_RELEASE(renderContext);
	}
}

CRuRenderContext *CRuRenderer::CreateRenderContext()
{
	CRuRenderContext *renderContext = m_unusedRenderContexts.IsEmpty() ? ruNEW CRuRenderContext(this) : m_unusedRenderContexts.Pop();
	m_allocatedRendererCommands.Add(renderContext);
	return renderContext;
}

BOOL CRuRenderer::DestroyRenderContext(CRuRenderContext *renderContext)
{
	m_allocatedRendererCommands.Remove(renderContext);
	m_unusedRenderContexts.Push(renderContext);
	return TRUE;
}

void CRuRenderer::Begin()
{
	// Initialize the NULL texture
	if(m_nullTexture == NULL)
	{
		// If even this fails, nothing should have worked anyway
		m_nullTexture = g_ruResourceManager->CreateTexture("RUSYSTEM_NULL", 1, 1, ruSURFACEFMT_A8R8G8B8);
		assert(m_nullTexture);

		DWORD *pixels;
		INT32 pitch;

		if(m_nullTexture->Lock(0, (void **) &pixels, &pitch))
		{
			pixels[0] = 0xFFFFFFFF;

			m_nullTexture->Unlock(0);
		}
	}

	// Initialize the CLAMP texture
	if(m_clampTexture == NULL)
	{
		m_clampTexture = g_ruResourceManager->CreateTexture("RUSYSTEM_CLAMP", 128, 128, 1, ruSURFACEFMT_A8R8G8B8);
		assert(m_clampTexture);

		DWORD *pixels;
		INT32 pitch;

		if(m_clampTexture->Lock(0, (void **) &pixels, &pitch))
		{
			for(INT32 z = 0; z < 128; ++z)
			{
				for(INT32 x = 0; x < 128; ++x)
				{
					if(z == 0 || x == 0 || z == 127 || x == 127)
						pixels[x + z * pitch / 4] = 0x00000000;
					else
						pixels[x + z * pitch / 4] = 0xFFFFFFFF;
				}
			}

			m_clampTexture->Unlock(0);
		}
	}

	// Call begin on all renderer commands
	for(INT32 i = 0; i < m_allocatedRendererCommands.Count(); ++i)
	{
		m_allocatedRendererCommands[i]->Begin();
	}
}

void CRuRenderer::End()
{
	// Call execute on all renderer commands -- This needs to execute in dependency order
	for(INT32 i = 0; i < m_allocatedRendererCommands.Count(); ++i)
	{
		m_allocatedRendererCommands[i]->Execute();
	}

	// Call end on all renderer commands
	for(INT32 i = 0; i < m_allocatedRendererCommands.Count(); ++i)
	{
		m_allocatedRendererCommands[i]->Begin();
	}
}

void CRuRenderer::SetLightingProp(CRuEntity *lightingProp)
{
	INT32 lightCount = 0;

	// Clear old lighting tree
	m_lightTree->Clear();

	// Parse the lighting prop and extract lights
	m_entityStack.Clear();
	m_entityStack.Push(lightingProp);

	// Enumerate lights
	while(!m_entityStack.IsEmpty())
	{
		CRuEntity *curEntity = m_entityStack.Pop();

		if(curEntity->GetType().IsSubClassOf(IRuEntity_Container::Type))
		{
			// Push all entities held by the frame onto the entity stack
			IRuEntity_Container *frame = (IRuEntity_Container *) curEntity;
			for(INT32 i = 0; i < frame->GetNumEntities(); ++i)
			{
				// Push entity onto entity stack
				m_entityStack.Push(frame->GetEntity(i));
			}
		}
		else if(curEntity->GetType().IsSubClassOf(CRuEntity_Light::Type))
		{
			CRuEntity_Light *light;

			// Try to recycle a light entity from our existing list
			if(lightCount < m_recycledLightEntities.Count())
			{
				light = m_recycledLightEntities[lightCount];
			}
			// Otherwise create a new light entity
			else
			{
				light = ruNEW CRuEntity_Light();
				m_recycledLightEntities.Add(light);
			}

			// Set light parameters
			light->SetLight(((CRuEntity_Light *) curEntity)->GetLight());

			// Append light to tree and increment light count
			m_lightTree->AddEntity(light);
			++lightCount;
		}
	}
}

void CRuRenderer::SetSpecularColor(DWORD specularColor)
{
	m_specularColor = specularColor;
}

void CRuRenderer::SetSpecularDirection(const CRuVector3 &specularDir)
{
	m_specularDirection = specularDir;
}

void CRuRenderer::AddtoSH9(CRuColor4 *shCoefficients, CRuColor4 &lightColor, CRuVector3 &dirN)
{
	shCoefficients[0] += lightColor * m_sh9_Const1;
	shCoefficients[1] += lightColor * (m_sh9_Const2 * dirN.x);
	shCoefficients[2] += lightColor * (m_sh9_Const2 * dirN.y);
	shCoefficients[3] += lightColor * (m_sh9_Const2 * dirN.z);
	shCoefficients[4] += lightColor * m_sh9_Const3 * (dirN.x * dirN.z);
	shCoefficients[5] += lightColor * m_sh9_Const3 * (dirN.z * dirN.y);
	shCoefficients[6] += lightColor * m_sh9_Const3 * (dirN.y * dirN.x);
	shCoefficients[7] += lightColor * m_sh9_Const4 * (3.0f * dirN.z * dirN.z - 1.0f);
	shCoefficients[8] += lightColor * m_sh9_Const5 * (dirN.x * dirN.x - dirN.y * dirN.y);
}

RuLightingInfo *CRuRenderer::BuildLightingInfo(IRuEntity_Renderable *renderableEntity)
{
	// Retrieve the renderable entity's tag
	RuLightingInfo *lightInfo = static_cast<RuLightingInfo *>(renderableEntity->GetTag(ruENTITYTAG_SYSTEM));

	if(lightInfo == NULL)
	{
		// Create a new lighting info structure and set it as the renderable entity's tag
		lightInfo = ruNEW RuLightingInfo();
		lightInfo->m_entityIteration = 0;
		lightInfo->m_lightingIteration = m_iterationNumber - 1;
		renderableEntity->SetTag(ruENTITYTAG_SYSTEM, lightInfo);

		// Add lighting info to the active lighting info list
		m_activeLightingInfo.Add(lightInfo);
	}

	// If the lighting info is not at the current iteration, rebuild it
//	if(lightInfo->m_entityIteration != renderableEntity->GetIteration() || lightInfo->m_lightingIteration != m_iterationNumber)
	{
		// Set default ambient level
		lightInfo->m_ambientLight.Set(1.0f, 0.1f, 0.1f, 0.1f);

		// Reset lighting coefficients
		for(INT32 i = 0; i < 9; ++i)
		{
			lightInfo->m_shCoefficients[i].Set(0.0f, 0.0f, 0.0f, 0.0f);
		}

		// Update iteration number
//		lightInfo->m_entityIteration = renderableEntity->GetIteration();
//		lightInfo->m_lightingIteration = m_iterationNumber;
//		lightInfo->m_lightingIteration = m_iterationNumber - 1;

		// Get the entity's inverse world transform, used for transforming light into object space
		// NOTE: Except for spot lights, it is enough to simply invert the translation. For now, we hack around this. Will need to be changed later.
		CRuMatrix4x4 invWT;
		invWT.Rotate(renderableEntity->GetRotation(ruFRAMETYPE_WORLD));
		invWT.Translate(renderableEntity->GetTranslation(ruFRAMETYPE_WORLD));
		invWT = invWT.GetInverse();

		// Get the bounds of the entity in world space
		CRuSphere entityBounds(renderableEntity->GetWorldBounds());

		// Transform specular position from world space into object space
		CRuVector3 localSpecularDirection;
		invWT.TransformVector(m_specularDirection, localSpecularDirection);
		lightInfo->m_specularDirection = localSpecularDirection;
//		lightInfo->m_specularDirection = localSpecularDirection * entityBounds.Radius();

		// Query lighting tree for nearby lights
		m_enumeratedLights.Clear();
		m_enumeratedLightDistances.Clear();
		m_lightTree->GetEntities(entityBounds, m_enumeratedLights);

		// Sort enumerated lights
		for(INT32 i = 0; i < m_enumeratedLights.Count(); ++i)
		{
			const CRuLight &light = static_cast<CRuEntity_Light *>(m_enumeratedLights[i])->GetLight();

			switch(light.m_type)
			{
				default:
					{
						m_enumeratedLightDistances.Add(0.0f);
					}

					break;

				case ruLIGHTTYPE_POINT:
					{
						m_enumeratedLightDistances.Add((entityBounds.Center() - light.m_position).Magnitude());
					}

					break;
			}
		}

		m_radixSort2.Sort(m_enumeratedLightDistances.GetArray(), m_enumeratedLightDistances.Count());

		CRuVector3 objSpaceDirection;

		// Sort lights into directional, ambient, point, and spot bins
		UINT32 *sortedIndices = m_radixSort2.GetSortedIndices();
		for(INT32 i = 0; i < m_enumeratedLights.Count(); ++i)
		{
			const CRuLight &light = static_cast<CRuEntity_Light *>(m_enumeratedLights[sortedIndices[i]])->GetLight();

			switch(light.m_type)
			{
				case ruLIGHTTYPE_AMBIENT:
					{
						lightInfo->m_ambientLight.Set(light.m_diffuse.GetARGB());
					}

					break;

				case ruLIGHTTYPE_DIRECTIONAL:
					{
						// Transform light direction into object space
						invWT.TransformVector(light.m_direction, objSpaceDirection);

						// Represent directional lights in the SH term
						REAL d = m_enumeratedLightDistances[sortedIndices[i]];
						REAL attenuation = 1.0f - min(1.0f, d / light.m_range);
						AddtoSH9(lightInfo->m_shCoefficients, light.m_diffuse * 1.0f, objSpaceDirection * -1.0f);
					}

					break;

				case ruLIGHTTYPE_POINT:
					{
/*
						if(lightInfo->m_numPointLights < 2)
						{
							lightInfo->m_pointLights[lightInfo->m_numPointLights] = light;

							invWT.TransformPoint(lightInfo->m_pointLights[lightInfo->m_numPointLights].m_position);

							++lightInfo->m_numPointLights;
						}
*/
						// Set one specular light

						// Approximate point lights in the SH term
						REAL d = m_enumeratedLightDistances[sortedIndices[i]];
						REAL attenuation = 1.0f - min(1.0f, d / light.m_range);
						AddtoSH9(lightInfo->m_shCoefficients, light.m_diffuse * attenuation, ((entityBounds.Center() - light.m_position) / d) * -1.0f);
					}

					break;
			}
		}
	}

	return lightInfo;
}

CRuParameterBlock &CRuRenderer::GetDefaultParameterBlock()
{
	return m_defaultParamBlock;
}

CRuSkinCache *CRuRenderer::GetSkinCache()
{
	return m_skinCache;
}

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

#pragma managed(pop)
