ソースを参照

Vulkan queries can now be interrupted by command buffer submission, in order to match the behaviour of DX11 and OpenGL queries and make them more intuitive to use

BearishSun 8 年 前
コミット
dd43fd4a65

+ 14 - 0
Source/BansheeVulkanRenderAPI/Include/BsVulkanCommandBuffer.h

@@ -10,7 +10,10 @@
 
 namespace bs { namespace ct
 {
+	class VulkanOcclusionQuery;
+	class VulkanTimerQuery;
 	class VulkanImage;
+
 	/** @addtogroup Vulkan
 	 *  @{
 	 */
@@ -235,6 +238,12 @@ namespace bs { namespace ct
 		 */
 		void registerResource(VulkanFramebuffer* res, RenderSurfaceMask loadMask, VulkanUseFlags flags);
 
+		/** Notifies the command buffer that the provided query has been queued on it. */
+		void registerQuery(VulkanOcclusionQuery* query) { mOcclusionQueries.insert(query); }
+
+		/** Notifies the command buffer that the provided query has been queued on it. */
+		void registerQuery(VulkanTimerQuery* query) { mTimerQueries.insert(query); }
+
 		/************************************************************************/
 		/* 								COMMANDS	                     		*/
 		/************************************************************************/
@@ -431,6 +440,9 @@ namespace bs { namespace ct
 		/** Finds a subresource info structure containing the specified face and mip level of the provided image. */
 		ImageSubresourceInfo& findSubresourceInfo(VulkanImage* image, UINT32 face, UINT32 mip);
 
+		/** Gets all queries registered on this command buffer that haven't been ended. */
+		void getInProgressQueries(Vector<VulkanTimerQuery*>& timer, Vector<VulkanOcclusionQuery*>& occlusion) const;
+
 		UINT32 mId;
 		UINT32 mQueueFamily;
 		State mState;
@@ -452,6 +464,8 @@ namespace bs { namespace ct
 		UnorderedMap<VulkanResource*, ResourceUseHandle> mResources;
 		UnorderedMap<VulkanResource*, UINT32> mImages;
 		UnorderedMap<VulkanResource*, BufferInfo> mBuffers;
+		UnorderedSet<VulkanOcclusionQuery*> mOcclusionQueries;
+		UnorderedSet<VulkanTimerQuery*> mTimerQueries;
 		Vector<ImageInfo> mImageInfos;
 		Vector<ImageSubresourceInfo> mSubresourceInfos;
 		UINT32 mGlobalQueueIdx;

+ 14 - 1
Source/BansheeVulkanRenderAPI/Include/BsVulkanOcclusionQuery.h

@@ -30,15 +30,28 @@ namespace bs { namespace ct
 		/** @copydoc OcclusionQuery::getNumSamples */
 		UINT32 getNumSamples() override;
 
+		/** Returns true if the query begin() was called, but not end(). */
+		bool _isInProgress() const;
+
+		/**
+		 * Interrupts an in-progress query, allowing the command buffer to submitted. Interrupted queries must be resumed
+		 * using _resume().
+		 */
+		void _interrupt(VulkanCmdBuffer& cb);
+
+		/** Resumes an interrupted query, restoring it back to its original in-progress state. */
+		void _resume(VulkanCmdBuffer& cb);
+
 	private:
 		friend class QueryManager;
 
 		VulkanDevice& mDevice;
-		VulkanQuery* mQuery;
+		Vector<VulkanQuery*> mQueries;
 
 		UINT64 mNumSamples;
 		bool mQueryEndCalled : 1;
 		bool mQueryFinalized : 1;
+		bool mQueryInterrupted : 1;
 	};
 
 	/** @} */

+ 14 - 2
Source/BansheeVulkanRenderAPI/Include/BsVulkanTimerQuery.h

@@ -30,14 +30,26 @@ namespace bs { namespace ct
 		/** @copydoc TimerQuery::getTimeMs */
 		float getTimeMs() override;
 
+		/** Returns true if the query begin() was called, but not end(). */
+		bool _isInProgress() const;
+
+		/**
+		* Interrupts an in-progress query, allowing the command buffer to submitted. Interrupted queries must be resumed
+		* using _resume().
+		*/
+		void _interrupt(VulkanCmdBuffer& cb);
+
+		/** Resumes an interrupted query, restoring it back to its original in-progress state. */
+		void _resume(VulkanCmdBuffer& cb);
+
 	private:
 		VulkanDevice& mDevice;
-		VulkanQuery* mBeginQuery;
-		VulkanQuery* mEndQuery;
+		Vector < std::pair<VulkanQuery*, VulkanQuery*>> mQueries;
 
 		float mTimeDelta;
 		bool mQueryEndCalled : 1;
 		bool mQueryFinalized : 1;
+		bool mQueryInterrupted : 1;
 	};
 
 	/** @} */

+ 42 - 5
Source/BansheeVulkanRenderAPI/Source/BsVulkanCommandBuffer.cpp

@@ -15,6 +15,8 @@
 #include "BsVulkanEventQuery.h"
 #include "BsVulkanQueryManager.h"
 #include "BsVulkanSwapChain.h"
+#include "BsVulkanTimerQuery.h"
+#include "BsVulkanOcclusionQuery.h"
 
 #if BS_PLATFORM == BS_PLATFORM_WIN32
 #include "Win32/BsWin32RenderWindow.h"
@@ -766,6 +768,8 @@ namespace bs { namespace ct
 		mResources.clear();
 		mImages.clear();
 		mBuffers.clear();
+		mOcclusionQueries.clear();
+		mTimerQueries.clear();
 		mImageInfos.clear();
 		mSubresourceInfos.clear();
 	}
@@ -2030,6 +2034,21 @@ namespace bs { namespace ct
 		return subresourceInfos[0];
 	}
 
+	void VulkanCmdBuffer::getInProgressQueries(Vector<VulkanTimerQuery*>& timer, Vector<VulkanOcclusionQuery*>& occlusion) const
+	{
+		for(auto& query : mTimerQueries)
+		{
+			if (query->_isInProgress())
+				timer.push_back(query);
+		}
+
+		for (auto& query : mOcclusionQueries)
+		{
+			if (query->_isInProgress())
+				occlusion.push_back(query);
+		}
+	}
+
 	VulkanCommandBuffer::VulkanCommandBuffer(VulkanDevice& device, GpuQueueType type, UINT32 deviceIdx,
 		UINT32 queueIdx, bool secondary)
 		: CommandBuffer(type, deviceIdx, queueIdx, secondary), mBuffer(nullptr)
@@ -2075,15 +2094,33 @@ namespace bs { namespace ct
 		// Execute any queued layout transitions that weren't already handled by the render pass
 		mBuffer->executeLayoutTransitions();
 
+		// Interrupt any in-progress queries (no in-progress queries allowed during command buffer submit)
+		Vector<VulkanTimerQuery*> timerQueries;
+		Vector<VulkanOcclusionQuery*> occlusionQueries;
+		mBuffer->getInProgressQueries(timerQueries, occlusionQueries);
+
+		for (auto& query : timerQueries)
+			query->_interrupt(*mBuffer);
+
+		for (auto& query : occlusionQueries)
+			query->_interrupt(*mBuffer);
+
 		if (mBuffer->isRecording())
 			mBuffer->end();
 
-		if (!mBuffer->isReadyForSubmit()) // Possibly nothing was recorded in the buffer
-			return;
+		if (mBuffer->isReadyForSubmit()) // Possibly nothing was recorded in the buffer
+		{
+			mBuffer->submit(mQueue, mQueueIdx, syncMask);
+			acquireNewBuffer();
 
-		mBuffer->submit(mQueue, mQueueIdx, syncMask);
-		acquireNewBuffer();
+			gVulkanCBManager().refreshStates(mDeviceIdx);
+		}
+
+		// Resume interrupted queries on the new command buffer
+		for (auto& query : timerQueries)
+			query->_resume(*mBuffer);
 
-		gVulkanCBManager().refreshStates(mDeviceIdx);
+		for (auto& query : occlusionQueries)
+			query->_resume(*mBuffer);
 	}
 }}

+ 67 - 19
Source/BansheeVulkanRenderAPI/Source/BsVulkanOcclusionQuery.cpp

@@ -10,16 +10,18 @@
 namespace bs { namespace ct
 {
 	VulkanOcclusionQuery::VulkanOcclusionQuery(VulkanDevice& device, bool binary)
-		: OcclusionQuery(binary), mDevice(device), mQuery(nullptr), mNumSamples(0), mQueryEndCalled(false)
-		, mQueryFinalized(false)
+		: OcclusionQuery(binary), mDevice(device), mNumSamples(0), mQueryEndCalled(false), mQueryFinalized(false)
+		, mQueryInterrupted(false)
 	{
 		BS_INC_RENDER_STAT_CAT(ResCreated, RenderStatObject_Query);
 	}
 
 	VulkanOcclusionQuery::~VulkanOcclusionQuery()
 	{
-		if (mQuery != nullptr)
-			mDevice.getQueryPool().releaseQuery(mQuery);
+		for(auto& query : mQueries)
+			mDevice.getQueryPool().releaseQuery(query);
+
+		mQueries.clear();
 
 		BS_INC_RENDER_STAT_CAT(ResDestroyed, RenderStatObject_Query);
 	}
@@ -28,12 +30,11 @@ namespace bs { namespace ct
 	{
 		VulkanQueryPool& queryPool = mDevice.getQueryPool();
 
-		// Clear any existing query
-		if (mQuery != nullptr)
-		{
-			queryPool.releaseQuery(mQuery);
-			mQuery = nullptr;
-		}
+		// Clear any existing queries
+		for (auto& query : mQueries)
+			mDevice.getQueryPool().releaseQuery(query);
+
+		mQueries.clear();
 
 		mQueryEndCalled = false;
 		mNumSamples = 0;
@@ -46,14 +47,17 @@ namespace bs { namespace ct
 			vulkanCB = static_cast<VulkanCommandBuffer*>(gVulkanRenderAPI()._getMainCommandBuffer());
 
 		VulkanCmdBuffer* internalCB = vulkanCB->getInternal();
-		mQuery = queryPool.beginOcclusionQuery(internalCB, !mBinary);
+		mQueries.push_back(queryPool.beginOcclusionQuery(internalCB, !mBinary));
+		internalCB->registerQuery(this);
 
 		setActive(true);
 	}
 
 	void VulkanOcclusionQuery::end(const SPtr<CommandBuffer>& cb)
 	{
-		if(mQuery == nullptr)
+		assert(!mQueryInterrupted);
+
+		if(mQueries.size() == 0)
 		{
 			LOGERR("end() called but query was never started.");
 			return;
@@ -70,7 +74,33 @@ namespace bs { namespace ct
 
 		VulkanQueryPool& queryPool = mDevice.getQueryPool();
 		VulkanCmdBuffer* internalCB = vulkanCB->getInternal();
-		queryPool.endOcclusionQuery(mQuery, internalCB);
+		queryPool.endOcclusionQuery(mQueries.back(), internalCB);
+	}
+
+	bool VulkanOcclusionQuery::_isInProgress() const
+	{
+		return !mQueries.empty() && !mQueryEndCalled;
+	}
+
+	void VulkanOcclusionQuery::_interrupt(VulkanCmdBuffer& cb)
+	{
+		assert(mQueries.size() != 0 && !mQueryEndCalled);
+
+		VulkanQueryPool& queryPool = mDevice.getQueryPool();
+		queryPool.endOcclusionQuery(mQueries.back(), &cb);
+
+		mQueryInterrupted = true;
+	}
+
+	void VulkanOcclusionQuery::_resume(VulkanCmdBuffer& cb)
+	{
+		assert(mQueryInterrupted);
+
+		VulkanQueryPool& queryPool = mDevice.getQueryPool();
+		mQueries.push_back(queryPool.beginOcclusionQuery(&cb, !mBinary));
+		cb.registerQuery(this);
+
+		mQueryInterrupted = false;
 	}
 
 	bool VulkanOcclusionQuery::isReady() const
@@ -82,25 +112,43 @@ namespace bs { namespace ct
 			return true;
 
 		UINT64 numSamples;
-		return !mQuery->isBound() && mQuery->getResult(numSamples);
+		bool ready = true;
+		for (auto& query : mQueries)
+			ready &= !query->isBound() && query->getResult(numSamples);
+
+		return ready;
 	}
 
 	UINT32 VulkanOcclusionQuery::getNumSamples()
 	{
 		if(!mQueryFinalized)
 		{
-			UINT64 numSamples;
-			if(!mQuery->isBound() && mQuery->getResult(numSamples))
+			UINT64 totalNumSamples = 0;
+			bool ready = true;
+			for (auto& query : mQueries)
+			{
+				UINT64 numSamples;
+				ready &= !query->isBound() && query->getResult(numSamples);
+
+				totalNumSamples += numSamples;
+			}
+
+			if(ready)
 			{
 				mQueryFinalized = true;
-				mNumSamples = numSamples;
+				mNumSamples = totalNumSamples;
 
 				VulkanQueryPool& queryPool = mDevice.getQueryPool();
-				queryPool.releaseQuery(mQuery);
-				mQuery = nullptr;
+				for (auto& query : mQueries)
+					queryPool.releaseQuery(query);
+
+				mQueries.clear();
 			}
 		}
 
+		if (mBinary)
+			return mNumSamples == 0 ? 0 : 1;
+
 		return (UINT32)mNumSamples;
 	}
 }}

+ 85 - 35
Source/BansheeVulkanRenderAPI/Source/BsVulkanTimerQuery.cpp

@@ -10,19 +10,23 @@
 namespace bs { namespace ct
 {
 	VulkanTimerQuery::VulkanTimerQuery(VulkanDevice& device)
-		: mDevice(device), mBeginQuery(nullptr), mEndQuery(nullptr), mTimeDelta(0.0f), mQueryEndCalled(false)
-		, mQueryFinalized(false)
+		: mDevice(device), mTimeDelta(0.0f), mQueryEndCalled(false), mQueryFinalized(false), mQueryInterrupted(false)
 	{
 		BS_INC_RENDER_STAT_CAT(ResCreated, RenderStatObject_Query);
 	}
 
 	VulkanTimerQuery::~VulkanTimerQuery()
 	{
-		if (mBeginQuery != nullptr)
-			mDevice.getQueryPool().releaseQuery(mBeginQuery);
+		for (auto& query : mQueries)
+		{
+			if(query.first != nullptr)
+				mDevice.getQueryPool().releaseQuery(query.first);
+
+			if (query.second != nullptr)
+				mDevice.getQueryPool().releaseQuery(query.second);
+		}
 
-		if (mEndQuery != nullptr)
-			mDevice.getQueryPool().releaseQuery(mEndQuery);
+		mQueries.clear();
 
 		BS_INC_RENDER_STAT_CAT(ResDestroyed, RenderStatObject_Query);
 	}
@@ -32,18 +36,17 @@ namespace bs { namespace ct
 		VulkanQueryPool& queryPool = mDevice.getQueryPool();
 
 		// Clear any existing queries
-		if (mBeginQuery != nullptr)
+		for (auto& query : mQueries)
 		{
-			queryPool.releaseQuery(mBeginQuery);
-			mBeginQuery = nullptr;
-		}
+			if (query.first != nullptr)
+				queryPool.releaseQuery(query.first);
 
-		if (mEndQuery != nullptr)
-		{
-			queryPool.releaseQuery(mEndQuery);
-			mEndQuery = nullptr;
+			if (query.second != nullptr)
+				queryPool.releaseQuery(query.second);
 		}
 
+		mQueries.clear();
+
 		mQueryEndCalled = false;
 		mTimeDelta = 0.0f;
 
@@ -55,14 +58,19 @@ namespace bs { namespace ct
 			vulkanCB = static_cast<VulkanCommandBuffer*>(gVulkanRenderAPI()._getMainCommandBuffer());
 
 		VulkanCmdBuffer* internalCB = vulkanCB->getInternal();
-		mBeginQuery = queryPool.beginTimerQuery(internalCB);
+		VulkanQuery* beginQuery = queryPool.beginTimerQuery(internalCB);
+		internalCB->registerQuery(this);
+
+		mQueries.push_back(std::make_pair(beginQuery, nullptr));
 
 		setActive(true);
 	}
 
 	void VulkanTimerQuery::end(const SPtr<CommandBuffer>& cb)
 	{
-		if (mBeginQuery == nullptr)
+		assert(!mQueryInterrupted);
+
+		if (mQueries.empty())
 		{
 			LOGERR("end() called but query was never started.");
 			return;
@@ -79,7 +87,39 @@ namespace bs { namespace ct
 
 		VulkanQueryPool& queryPool = mDevice.getQueryPool();
 		VulkanCmdBuffer* internalCB = vulkanCB->getInternal();
-		mEndQuery = queryPool.beginTimerQuery(internalCB);
+		VulkanQuery* endQuery = queryPool.beginTimerQuery(internalCB);
+		internalCB->registerQuery(this);
+
+		mQueries.back().second = endQuery;
+	}
+
+	bool VulkanTimerQuery::_isInProgress() const
+	{
+		return !mQueries.empty() && !mQueryEndCalled;
+	}
+
+	void VulkanTimerQuery::_interrupt(VulkanCmdBuffer& cb)
+	{
+		assert(mQueries.size() != 0 && !mQueryEndCalled);
+
+		VulkanQueryPool& queryPool = mDevice.getQueryPool();
+		VulkanQuery* endQuery = queryPool.beginTimerQuery(&cb);
+		cb.registerQuery(this);
+
+		mQueries.back().second = endQuery;
+		mQueryInterrupted = true;
+	}
+
+	void VulkanTimerQuery::_resume(VulkanCmdBuffer& cb)
+	{
+		assert(mQueryInterrupted);
+
+		VulkanQueryPool& queryPool = mDevice.getQueryPool();
+		VulkanQuery* beginQuery = queryPool.beginTimerQuery(&cb);
+		cb.registerQuery(this);
+
+		mQueries.push_back(std::make_pair(beginQuery, nullptr));
+		mQueryInterrupted = false;
 	}
 
 	bool VulkanTimerQuery::isReady() const
@@ -90,40 +130,50 @@ namespace bs { namespace ct
 		if (mQueryFinalized)
 			return true;
 
-		UINT64 timeBegin;
-		bool beginReady = !mBeginQuery->isBound() && mBeginQuery->getResult(timeBegin);
-
-		UINT64 timeEnd;
-		bool endReady = !mEndQuery->isBound() && mEndQuery->getResult(timeEnd);
+		UINT64 timeBegin, timeEnd;
+		bool ready = true;
+		for (auto& entry : mQueries)
+		{
+			ready &= !entry.first->isBound() && entry.first->getResult(timeBegin);
+			ready &= !entry.second->isBound() && entry.second->getResult(timeEnd);
+		}
 
-		return beginReady && endReady;
+		return ready;
 	}
 
 	float VulkanTimerQuery::getTimeMs()
 	{
 		if (!mQueryFinalized)
 		{
-			UINT64 timeBegin;
-			bool beginReady = !mBeginQuery->isBound() && mBeginQuery->getResult(timeBegin);
+			UINT64 totalTimeDiff = 0;
+			bool ready = true;
+			for (auto& entry : mQueries)
+			{
+				UINT64 timeBegin, timeEnd;
+				ready &= !entry.first->isBound() && entry.first->getResult(timeBegin);
+				ready &= !entry.second->isBound() && entry.second->getResult(timeEnd);
 
-			UINT64 timeEnd;
-			bool endReady = !mEndQuery->isBound() && mEndQuery->getResult(timeEnd);
+				totalTimeDiff += (timeEnd - timeBegin);
+			}
 
-			if (beginReady && endReady)
+			if (ready)
 			{
 				mQueryFinalized = true;
 
-				UINT64 timeDiff = timeEnd - timeBegin;
 				double timestampToMs = (double)mDevice.getDeviceProperties().limits.timestampPeriod / 1e6; // Nano to milli
-
-				mTimeDelta = (float)((double)timeDiff * timestampToMs);
+				mTimeDelta = (float)((double)totalTimeDiff * timestampToMs);
 
 				VulkanQueryPool& queryPool = mDevice.getQueryPool();
-				queryPool.releaseQuery(mBeginQuery);
-				mBeginQuery = nullptr;
+				for (auto& query : mQueries)
+				{
+					if (query.first != nullptr)
+						queryPool.releaseQuery(query.first);
+
+					if (query.second != nullptr)
+						queryPool.releaseQuery(query.second);
+				}
 
-				queryPool.releaseQuery(mEndQuery);
-				mEndQuery = nullptr;
+				mQueries.clear();
 			}
 		}