瀏覽代碼

Refactored Render system context into two different types
Added waitUntilLoaded to Resource

Marko Pintera 13 年之前
父節點
當前提交
616f686c12

+ 11 - 0
CamelotRenderer/Include/CmRenderSystem.h

@@ -1059,6 +1059,17 @@ namespace CamelotEngine
 		 */
 		void submitToGpu(RenderSystemContextPtr context, bool blockUntilComplete);
 
+		/**
+		 * @brief	Creates a new render system context that you can use for rendering on 
+		 * 			a non-render thread. You can have as many of these as you wish, the only limitation
+		 * 			is that you do not use a single instance on more than one thread. Each thread
+		 * 			requires its own context.
+		 * 			
+		 *			Resource context is different from normal rendering context, as it will continously queue commands,
+		 *			while normal "frame" context will discard older batches of commands (i.e. older frames).
+		 */
+		RenderSystemContextPtr createResourceRenderSystemContext();
+
 		/**
 		 * @brief	Gets the currently active render system object.
 		 *

+ 99 - 10
CamelotRenderer/Include/CmRenderSystemContext.h

@@ -18,7 +18,7 @@ namespace CamelotEngine
 	public:
 		CM_THREAD_ID_TYPE getThreadId() const { return mMyThreadId; }
 
-	private:
+	protected:
 		struct RenderSystemCommand
 		{
 			RenderSystemCommand(boost::function<void(AsyncOp&)> _callback, UINT32 _callbackId = 0)
@@ -44,9 +44,6 @@ namespace CamelotEngine
 		// Actively being filled up
 		vector<RenderSystemCommand>::type* mCommands;
 
-		// Finalized and ready for rendering
-		vector<RenderSystemCommand>::type* mReadyCommands;
-
 		bool mIsShutdown;
 		bool mIsExecuting;
 
@@ -73,7 +70,7 @@ namespace CamelotEngine
 		 * 			it completes AsyncOp::isResolved will return true and return data will be valid (if
 		 * 			the callback provided any).
 		 */
-		AsyncOp queueReturnCommand(boost::function<void(AsyncOp&)> commandCallback, UINT32 _callbackId = 0);
+		virtual AsyncOp queueReturnCommand(boost::function<void(AsyncOp&)> commandCallback, UINT32 _callbackId = 0) = 0;
 
 		/**
 		 * @brief	Queue up a new command to execute. Make sure the provided function has all of its
@@ -84,18 +81,18 @@ namespace CamelotEngine
 		 * @param	_callbackId			   	(optional) Identifier for the callback so you can then later find it
 		 * 									if needed.
 		 */
-		void queueCommand(boost::function<void()> commandCallback, UINT32 _callbackId = 0);
+		virtual void queueCommand(boost::function<void()> commandCallback, UINT32 _callbackId = 0) = 0;
 
 		/**
 		 * @brief	Plays all queued commands. Should only be called from the render thread,
 		 * 			and is normally called by the RenderSystem internally.
 		 */
-		void playbackCommands();
+		virtual void playbackCommands() = 0;
 
 		/**
 		 * @brief	Query if this object has any commands ready for rendering.
 		 */
-		bool hasReadyCommands();
+		virtual bool hasReadyCommands() = 0;
 
 		/**
 		 * @brief	Makes all the currently queued commands available to the GPU. They will be executed
@@ -104,7 +101,7 @@ namespace CamelotEngine
 		 * @note	This is expected to be called once per frame. If the previous set of commands hasn't even started rendering
 		 * 			yet, it will be discarded. This is to prevent lag if the simulation executes faster than the render thread.
 		 */
-		void submitToGpu();
+		virtual void submitToGpu() = 0;
 
 		/**
 		 * @brief	Blocks the current thread until all commands in the context are processed.
@@ -112,7 +109,7 @@ namespace CamelotEngine
 		 * @note	Do not call from render thread. Render thread is the thread doing the processing and blocking
 		 * 			it will cause a deadlock since processing will never be completed. 
 		 */
-		void blockUntilExecuted();
+		virtual void blockUntilExecuted() = 0;
 
 		/************************************************************************/
 		/* 								STATES		                     		*/
@@ -131,4 +128,96 @@ namespace CamelotEngine
 		bool fragmentProgramBound;
 		bool invertVertexWinding;
 	};
+
+	/**
+	 * @brief	Render system context where commands are added and meant to be executed once per frame.
+	 * 			Obsolete commands get discarded as new ones are submitted.
+	 */
+	class RenderSystemFrameContext : public RenderSystemContext
+	{
+	protected:
+		/************************************************************************/
+		/* 					CALLABLE ONLY FROM RENDERSYSTEM                     */
+		/************************************************************************/
+		friend class RenderSystem;
+
+		// Finalized and ready for rendering
+		vector<RenderSystemCommand>::type* mReadyCommands;
+
+		RenderSystemFrameContext(CM_THREAD_ID_TYPE threadId);
+
+		/**
+		 * @copydoc RenderSystemContext::queueReturnCommand()
+		 */
+		AsyncOp queueReturnCommand(boost::function<void(AsyncOp&)> commandCallback, UINT32 _callbackId = 0);
+
+		 /**
+		 * @copydoc RenderSystemContext::queueCommand()
+		 */
+		void queueCommand(boost::function<void()> commandCallback, UINT32 _callbackId = 0);
+
+		 /**
+		 * @copydoc RenderSystemContext::playbackCommands()
+		 */
+		void playbackCommands();
+
+		 /**
+		 * @copydoc RenderSystemContext::hasReadyCommands()
+		 */
+		bool hasReadyCommands();
+
+		/**
+		 * @copydoc RenderSystemContext::submitToGpu()
+		 */
+		void submitToGpu();
+
+		/**
+		 * @copydoc RenderSystemContext::blockUntilExecuted()
+		 */
+		void blockUntilExecuted();
+	};
+
+	/**
+	 * @brief	Render system context where commands are added and scheduled for execution immediately.
+	 */
+	class RenderSystemImmediateContext : public RenderSystemContext
+	{
+	protected:
+		/************************************************************************/
+		/* 					CALLABLE ONLY FROM RENDERSYSTEM                     */
+		/************************************************************************/
+		friend class RenderSystem;
+
+		RenderSystemImmediateContext(CM_THREAD_ID_TYPE threadId);
+
+		/**
+		 * @copydoc RenderSystemContext::queueReturnCommand()
+		 */
+		AsyncOp queueReturnCommand(boost::function<void(AsyncOp&)> commandCallback, UINT32 _callbackId = 0);
+
+		 /**
+		 * @copydoc RenderSystemContext::queueCommand()
+		 */
+		void queueCommand(boost::function<void()> commandCallback, UINT32 _callbackId = 0);
+
+		 /**
+		 * @copydoc RenderSystemContext::playbackCommands()
+		 */
+		void playbackCommands();
+
+		 /**
+		 * @copydoc RenderSystemContext::hasReadyCommands()
+		 */
+		bool hasReadyCommands();
+
+		/**
+		 * @copydoc RenderSystemContext::submitToGpu()
+		 */
+		void submitToGpu();
+
+		/**
+		 * @copydoc RenderSystemContext::blockUntilExecuted()
+		 */
+		void blockUntilExecuted();
+	};
 }

+ 8 - 0
CamelotRenderer/Include/CmResource.h

@@ -22,6 +22,11 @@ namespace CamelotEngine
 		 */
 		bool isInitialized() const { return mIsInitialized; }
 
+		/**
+		 * @brief	Blocks the current thread until the resource is fully loaded.
+		 */
+		void waitUntilLoaded();
+
 	protected:
 		friend class Resources;
 		//virtual void unload() = 0;
@@ -47,6 +52,9 @@ namespace CamelotEngine
 		// Transient
 		bool mIsInitialized;
 
+		CM_STATIC_THREAD_SYNCHRONISER(mResourceLoadedCondition)
+		CM_STATIC_MUTEX(mResourceLoadedMutex)
+
 	/************************************************************************/
 	/* 								SERIALIZATION                      		*/
 	/************************************************************************/

+ 24 - 27
CamelotRenderer/Source/CmRenderSystem.cpp

@@ -86,16 +86,13 @@ namespace CamelotEngine {
 	void RenderSystem::startUp()
 	{
 		mRenderThreadId = CM_THREAD_CURRENT_ID;
-		mResourceContext = createRenderSystemContext();
+		mResourceContext = createResourceRenderSystemContext();
 		mPrimaryContext = createRenderSystemContext();
 		mActiveContext = mPrimaryContext;
 
 		initRenderThread(); // TODO - Move render thread to the outside of the RS
 
-		{
-			CM_LOCK_MUTEX(mResourceContextMutex);
-			mResourceContext->queueCommand(boost::bind(&RenderSystem::startUp_internal, this));
-		}
+		queueResourceCommand(boost::bind(&RenderSystem::startUp_internal, this));
 	}
 	//-----------------------------------------------------------------------
 	void RenderSystem::startUp_internal()
@@ -156,16 +153,12 @@ namespace CamelotEngine {
 		bool fullScreen, const NameValuePairList *miscParams)
 	{
 		AsyncOp op;
-		{
-			CM_LOCK_MUTEX(mResourceContextMutex)
 
-			if(miscParams != nullptr)
-				op = mResourceContext->queueReturnCommand(boost::bind(&RenderSystem::createRenderWindow_internal, this, name, width, height, fullScreen, *miscParams, _1));
-			else
-				op = mResourceContext->queueReturnCommand(boost::bind(&RenderSystem::createRenderWindow_internal, this, name, width, height, fullScreen, NameValuePairList(), _1));
-		}
+		if(miscParams != nullptr)
+			op = queueResourceReturnCommand(boost::bind(&RenderSystem::createRenderWindow_internal, this, name, width, height, fullScreen, *miscParams, _1), true);
+		else
+			op = queueResourceReturnCommand(boost::bind(&RenderSystem::createRenderWindow_internal, this, name, width, height, fullScreen, NameValuePairList(), _1), true);
 
-		submitToGpu(mResourceContext, true);
 		return op.getReturnValue<RenderWindow*>();
 	}
     //---------------------------------------------------------------------------------------------
@@ -962,7 +955,21 @@ namespace CamelotEngine {
 	RenderSystemContextPtr RenderSystem::createRenderSystemContext()
 	{
 		RenderSystemContextPtr newContext = RenderSystemContextPtr(
-			new RenderSystemContext(CM_THREAD_CURRENT_ID)
+			new RenderSystemFrameContext(CM_THREAD_CURRENT_ID)
+			);
+
+		{
+			CM_LOCK_MUTEX(mRSContextMutex);
+			mRenderSystemContexts.push_back(newContext);
+		}
+
+		return newContext;
+	}
+
+	RenderSystemContextPtr RenderSystem::createResourceRenderSystemContext()
+	{
+		RenderSystemContextPtr newContext = RenderSystemContextPtr(
+			new RenderSystemImmediateContext(CM_THREAD_CURRENT_ID)
 			);
 
 		{
@@ -1015,25 +1022,15 @@ namespace CamelotEngine {
 
 	AsyncOp RenderSystem::queueResourceReturnCommand(boost::function<void(AsyncOp&)> commandCallback, bool blockUntilComplete, UINT32 _callbackId)
 	{
-		AsyncOp op;
-		{
-			CM_LOCK_MUTEX(mResourceContextMutex)
-
-			op = mResourceContext->queueReturnCommand(commandCallback);
-		}
-
+		AsyncOp op = mResourceContext->queueReturnCommand(commandCallback);
 		submitToGpu(mResourceContext, blockUntilComplete);
+
 		return op;
 	}
 
 	void RenderSystem::queueResourceCommand(boost::function<void()> commandCallback, bool blockUntilComplete, UINT32 _callbackId)
 	{
-		{
-			CM_LOCK_MUTEX(mResourceContextMutex)
-
-			mResourceContext->queueCommand(commandCallback);
-		}
-
+		mResourceContext->queueCommand(commandCallback);
 		submitToGpu(mResourceContext, blockUntilComplete);
 	}
 

+ 136 - 7
CamelotRenderer/Source/CmRenderSystemContext.cpp

@@ -7,7 +7,7 @@
 namespace CamelotEngine
 {
 	RenderSystemContext::RenderSystemContext(CM_THREAD_ID_TYPE threadId)
-		:mMyThreadId(threadId), mReadyCommands(nullptr), mIsExecuting(false)
+		:mMyThreadId(threadId), mIsExecuting(false)
 		, waitForVerticalBlank(true)
 		, cullingMode(CULL_CLOCKWISE)
 		, vertexProgramBound(false)
@@ -18,7 +18,16 @@ namespace CamelotEngine
 		mCommands = new vector<RenderSystemCommand>::type();
 	}
 
-	AsyncOp RenderSystemContext::queueReturnCommand(boost::function<void(AsyncOp&)> commandCallback, UINT32 _callbackId)
+	/************************************************************************/
+	/* 								FRAME CONTEXT                      		*/
+	/************************************************************************/
+
+	RenderSystemFrameContext::RenderSystemFrameContext(CM_THREAD_ID_TYPE threadId)
+		:RenderSystemContext(threadId), mReadyCommands(nullptr)
+	{
+	}
+
+	AsyncOp RenderSystemFrameContext::queueReturnCommand(boost::function<void(AsyncOp&)> commandCallback, UINT32 _callbackId)
 	{
 #if CM_DEBUG_MODE
 #if CM_THREAD_SUPPORT != 0
@@ -35,7 +44,7 @@ namespace CamelotEngine
 		return newCommand.asyncOp;
 	}
 
-	void RenderSystemContext::queueCommand(boost::function<void()> commandCallback, UINT32 _callbackId)
+	void RenderSystemFrameContext::queueCommand(boost::function<void()> commandCallback, UINT32 _callbackId)
 	{
 #if CM_DEBUG_MODE
 #if CM_THREAD_SUPPORT != 0
@@ -50,7 +59,7 @@ namespace CamelotEngine
 		mCommands->push_back(newCommand);
 	}
 
-	void RenderSystemContext::submitToGpu()
+	void RenderSystemFrameContext::submitToGpu()
 	{
 		{
 			CM_LOCK_MUTEX(mCommandBufferMutex);
@@ -66,7 +75,7 @@ namespace CamelotEngine
 		}
 	}
 
-	void RenderSystemContext::playbackCommands()
+	void RenderSystemFrameContext::playbackCommands()
 	{
 #if CM_DEBUG_MODE
 		RenderSystem* rs = RenderSystemManager::getActive();
@@ -126,7 +135,7 @@ namespace CamelotEngine
 		CM_THREAD_NOTIFY_ALL(mContextPlaybackDoneCondition)
 	}
 
-	bool RenderSystemContext::hasReadyCommands()
+	bool RenderSystemFrameContext::hasReadyCommands()
 	{
 		CM_LOCK_MUTEX(mCommandBufferMutex);
 
@@ -136,7 +145,7 @@ namespace CamelotEngine
 		return false;
 	}
 
-	void RenderSystemContext::blockUntilExecuted()
+	void RenderSystemFrameContext::blockUntilExecuted()
 	{
 #if CM_DEBUG_MODE
 		RenderSystem* rs = RenderSystemManager::getActive();
@@ -153,4 +162,124 @@ namespace CamelotEngine
 			}
 		}
 	}
+
+	/************************************************************************/
+	/* 							IMMEDIATE CONTEXT						*/
+	/************************************************************************/
+
+	RenderSystemImmediateContext::RenderSystemImmediateContext(CM_THREAD_ID_TYPE threadId)
+		:RenderSystemContext(threadId)
+	{
+	}
+
+	AsyncOp RenderSystemImmediateContext::queueReturnCommand(boost::function<void(AsyncOp&)> commandCallback, UINT32 _callbackId)
+	{
+		CM_LOCK_MUTEX(mCommandBufferMutex)
+
+		RenderSystemCommand newCommand(commandCallback, _callbackId);
+		mCommands->push_back(newCommand);
+
+		return newCommand.asyncOp;
+	}
+
+	void RenderSystemImmediateContext::queueCommand(boost::function<void()> commandCallback, UINT32 _callbackId)
+	{
+		CM_LOCK_MUTEX(mCommandBufferMutex)
+
+		RenderSystemCommand newCommand(commandCallback, _callbackId);
+		mCommands->push_back(newCommand);
+	}
+
+	void RenderSystemImmediateContext::submitToGpu()
+	{
+		// Do nothing
+	}
+
+	void RenderSystemImmediateContext::playbackCommands()
+	{
+#if CM_DEBUG_MODE
+		RenderSystem* rs = RenderSystemManager::getActive();
+
+		if(rs->getRenderThreadId() != CM_THREAD_CURRENT_ID)
+			CM_EXCEPT(InternalErrorException, "This method should only be called from the render thread.");
+#endif
+
+		vector<RenderSystemCommand>::type* currentCommands = nullptr;
+		{
+			CM_LOCK_MUTEX(mCommandBufferMutex)
+
+			currentCommands = mCommands;
+			mCommands = new vector<RenderSystemCommand>::type();
+			mIsExecuting = true;
+		}
+
+		if(currentCommands == nullptr)
+		{
+			{
+				CM_LOCK_MUTEX(mCommandBufferMutex);
+				mIsExecuting = false;
+			}
+
+			CM_THREAD_NOTIFY_ALL(mContextPlaybackDoneCondition)
+			return;
+		}
+
+		for(auto iter = currentCommands->begin(); iter != currentCommands->end(); ++iter)
+		{
+			RenderSystemCommand command = (*iter);
+
+			if(command.returnsValue)
+			{
+				command.callbackWithReturnValue(command.asyncOp);
+
+				if(!command.asyncOp.hasCompleted())
+				{
+					LOGDBG("Async operation return value wasn't resolved properly. Resolving automatically to nullptr. " \
+						"Make sure to complete the operation before returning from the command callback method.");
+					command.asyncOp.completeOperation(nullptr);
+				}
+			}
+			else
+			{
+				command.callback();
+			}
+		}
+
+		delete currentCommands;
+
+		{
+			CM_LOCK_MUTEX(mCommandBufferMutex);
+			mIsExecuting = false;
+		}
+
+		CM_THREAD_NOTIFY_ALL(mContextPlaybackDoneCondition)
+	}
+
+	bool RenderSystemImmediateContext::hasReadyCommands()
+	{
+		CM_LOCK_MUTEX(mCommandBufferMutex);
+
+		if(mCommands != nullptr && mCommands->size() > 0)
+			return true;
+
+		return false;
+	}
+
+	void RenderSystemImmediateContext::blockUntilExecuted()
+	{
+#if CM_DEBUG_MODE
+		RenderSystem* rs = RenderSystemManager::getActive();
+
+		if(rs->getRenderThreadId() == CM_THREAD_CURRENT_ID)
+			CM_EXCEPT(InternalErrorException, "This method should never be called from the render thread as it will cause a deadlock.");
+#endif
+
+		{
+			CM_LOCK_MUTEX_NAMED(mCommandBufferMutex, lock);
+			while (mCommands != nullptr && mCommands->size() > 0 || mIsExecuting)
+			{
+				CM_THREAD_WAIT(mContextPlaybackDoneCondition, mCommandBufferMutex, lock)
+			}
+		}
+	}
 }

+ 18 - 0
CamelotRenderer/Source/CmResource.cpp

@@ -1,9 +1,14 @@
 #include "CmResource.h"
 #include "CmResourceRTTI.h"
 #include "CmUUID.h"
+#include "CmRenderSystem.h"
+#include "CmRenderSystemManager.h"
 
 namespace CamelotEngine
 {
+	CM_STATIC_THREAD_SYNCHRONISER_CLASS_INSTANCE(mResourceLoadedCondition, Resource)
+	CM_STATIC_MUTEX_CLASS_INSTANCE(mResourceLoadedMutex, Resource)
+
 	Resource::Resource()
 		:mSize(0), mIsInitialized(false)
 	{
@@ -15,6 +20,19 @@ namespace CamelotEngine
 	void Resource::initialize_internal()
 	{
 		mIsInitialized = true;
+
+		CM_THREAD_NOTIFY_ALL(mResourceLoadedCondition);
+	}
+
+	void Resource::waitUntilLoaded()
+	{
+#if CM_DEBUG_MODE
+		if(CM_THREAD_CURRENT_ID == RenderSystemManager::getActive()->getRenderThreadId())
+			CM_EXCEPT(InternalErrorException, "You cannot call this method on the render thread. It will cause a deadlock!");
+#endif
+
+		CM_LOCK_MUTEX_NAMED(mResourceLoadedMutex, lock);
+		CM_THREAD_WAIT(mResourceLoadedCondition, mResourceLoadedMutex, lock);
 	}
 		
 	RTTITypeBase* Resource::getRTTIStatic()

+ 6 - 0
CamelotUtility/Include/CmThreadDefines.h

@@ -43,6 +43,7 @@ THE SOFTWARE
 #define CM_MUTEX(name) mutable boost::recursive_mutex name;
 #define CM_STATIC_MUTEX(name) static boost::recursive_mutex name;
 #define CM_STATIC_MUTEX_INSTANCE(name) boost::recursive_mutex name;
+#define CM_STATIC_MUTEX_CLASS_INSTANCE(name, classTypeName) boost::recursive_mutex classTypeName##::name;
 #define CM_LOCK_MUTEX(name) boost::recursive_mutex::scoped_lock cmnameLock(name);
 #define CM_LOCK_MUTEX_NAMED(mutexName, lockName) boost::recursive_mutex::scoped_lock lockName(mutexName);
 // like CM_AUTO_MUTEX but mutex held by pointer
@@ -54,6 +55,8 @@ THE SOFTWARE
 #define CM_SET_AUTO_SHARED_MUTEX_NULL CM_AUTO_MUTEX_NAME = 0;
 #define CM_MUTEX_CONDITIONAL(mutex) if (mutex)
 #define CM_THREAD_SYNCHRONISER(sync) boost::condition sync;
+#define CM_STATIC_THREAD_SYNCHRONISER(sync) static boost::condition sync;
+#define CM_STATIC_THREAD_SYNCHRONISER_CLASS_INSTANCE(sync, classTypeName) boost::condition classTypeName##::sync;
 #define CM_THREAD_WAIT(sync, mutex, lock) sync.wait(lock);
 #define CM_THREAD_NOTIFY_ONE(sync) sync.notify_one(); 
 #define CM_THREAD_NOTIFY_ALL(sync) sync.notify_all(); 
@@ -86,6 +89,7 @@ THE SOFTWARE
 #define CM_MUTEX(name)
 #define CM_STATIC_MUTEX(name)
 #define CM_STATIC_MUTEX_INSTANCE(name)
+#define CM_STATIC_MUTEX_CLASS_INSTANCE(name, classTypeName)
 #define CM_LOCK_MUTEX(name)
 #define CM_LOCK_MUTEX_NAMED(mutexName, lockName)
 #define CM_AUTO_SHARED_MUTEX
@@ -99,6 +103,8 @@ THE SOFTWARE
 #define CM_LOCK_RW_MUTEX_READ(name)
 #define CM_LOCK_RW_MUTEX_WRITE(name)
 #define CM_THREAD_SYNCHRONISER(sync) 
+#define CM_STATIC_THREAD_SYNCHRONISER(sync)
+#define CM_STATIC_THREAD_SYNCHRONISER_CLASS_INSTANCE(sync, classTypeName)
 #define CM_THREAD_WAIT(sync, lock) 
 #define CM_THREAD_NOTIFY_ONE(sync) 
 #define CM_THREAD_NOTIFY_ALL(sync)