Browse Source

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 years ago
parent
commit
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();
 			}
 		}