Browse Source

Refactoring VulkanGpuParams
- It can now be bound to the same command buffer multiple times in a row, with modifications in between binds, by maintaing a list of descriptor sets instead of using just one
- It can now be bound to multiple command buffers at once, as long as it's not written to, or if externally synchronized

BearishSun 9 years ago
parent
commit
fd01c1cde4

+ 33 - 5
Source/BansheeVulkanRenderAPI/Include/BsVulkanCommandBuffer.h

@@ -9,6 +9,7 @@
 
 namespace BansheeEngine
 {
+	class VulkanImage;
 	/** @addtogroup Vulkan
 	 *  @{
 	 */
@@ -131,15 +132,25 @@ namespace BansheeEngine
 
 		/** 
 		 * Lets the command buffer know that the provided resource has been queued on it, and will be used by the
-		 * device when the command buffer is submitted.
+		 * device when the command buffer is submitted. If a resource is an image or a buffer use the more specific
+		 * registerResource() overload.
 		 */
 		void registerResource(VulkanResource* res, VulkanUseFlags flags);
 
 		/** 
-		 * Lets the command buffer know that the provided GPU params object has been bound to it, and its resources
-		 * and descriptors will be used by the device when the command buffer is submitted.
+		 * Lets the command buffer know that the provided image resource has been queued on it, and will be used by the
+		 * device when the command buffer is submitted. If a resource is an image or a buffer use the more specific
+		 * registerResource() overload.
 		 */
-		void registerGpuParams(const SPtr<VulkanGpuParams>& params);
+		void registerResource(VulkanImage* res, VkAccessFlags accessFlags, VkImageLayout layout, 
+			const VkImageSubresourceRange& range, VulkanUseFlags flags);
+
+		/** 
+		 * Lets the command buffer know that the provided image resource has been queued on it, and will be used by the
+		 * device when the command buffer is submitted. If a resource is an image or a buffer use the more specific
+		 * registerResource() overload.
+		 */
+		void registerResource(VulkanBuffer* res, VkAccessFlags accessFlags, VulkanUseFlags flags);
 
 	private:
 		friend class VulkanCmdBufferPool;
@@ -152,6 +163,22 @@ namespace BansheeEngine
 			VulkanUseFlags flags;
 		};
 
+		/** Contains information about a single Vulkan buffer resource bound/used on this command buffer. */
+		struct BufferInfo
+		{
+			VkAccessFlags accessFlags;
+			ResourceUseHandle useHandle;
+		};
+
+		/** Contains information about a single Vulkan image resource bound/used on this command buffer. */
+		struct ImageInfo
+		{
+			VkAccessFlags accessFlags;
+			VkImageLayout layout;
+			VkImageSubresourceRange range;
+			ResourceUseHandle useHandle;
+		};
+
 		UINT32 mId;
 		UINT32 mQueueFamily;
 		State mState;
@@ -163,7 +190,8 @@ namespace BansheeEngine
 		UINT32 mFenceCounter;
 
 		UnorderedMap<VulkanResource*, ResourceUseHandle> mResources;
-		UnorderedSet<SPtr<VulkanGpuParams>> mBoundParams;
+		UnorderedMap<VulkanResource*, ImageInfo> mImages;
+		UnorderedMap<VulkanResource*, BufferInfo> mBuffers;
 
 		VkSemaphore mSemaphoresTemp[BS_MAX_COMMAND_BUFFERS];
 		UnorderedMap<UINT32, TransitionInfo> mTransitionInfoTemp;

+ 9 - 9
Source/BansheeVulkanRenderAPI/Include/BsVulkanGpuParams.h

@@ -37,15 +37,12 @@ namespace BansheeEngine
 		void setLoadStoreSurface(UINT32 set, UINT32 slot, const TextureSurface& surface) override;
 
 		/** 
-		 * Notifies the object that a command buffer containing the object is about to be submitted to a queue. 
-		 *
-		 * @param[in]	buffer			Command buffer on which we're about to submit the GPU params.
-		 * @param[out]	transitionInfo	Contains barriers that transition resources to appropriate queues families
-		 *								and/or transition image layouts.
+		 * Binds the internal descriptor sets to the provided command buffer. Caller must perform external locking if
+		 * some other thread could write to this object while it is being bound. The same applies to any resources
+		 * held by this object.
+		 * 
+		 * @note	Thread safe.
 		 */
-		void prepareForSubmit(VulkanCmdBuffer* buffer, UnorderedMap<UINT32, TransitionInfo>& transitionInfo);
-
-		/** Binds the internal descriptor sets to the provided command buffer. */
 		void bind(VulkanCommandBuffer& buffer);
 
 	protected:
@@ -61,7 +58,8 @@ namespace BansheeEngine
 		struct PerSetData
 		{
 			VulkanDescriptorLayout* layout;
-			VulkanDescriptorSet* set;
+			VulkanDescriptorSet* latestSet;
+			Vector<VulkanDescriptorSet*> sets;
 
 			VkWriteDescriptorSet* writeSetInfos;
 			WriteInfo* writeInfos;
@@ -84,6 +82,8 @@ namespace BansheeEngine
 		GpuDeviceFlags mDeviceMask;
 		UINT8* mData;
 		bool* mSetsDirty;
+
+		Mutex mMutex;
 	};
 
 	/** @} */

+ 1 - 0
Source/BansheeVulkanRenderAPI/Include/BsVulkanPrerequisites.h

@@ -47,6 +47,7 @@ namespace BansheeEngine
 	class VulkanResourceManager;
 	class VulkanGpuParamBlockBufferCore;
 	class VulkanBuffer;
+	class VulkanImage;
 	class VulkanDescriptorPool;
 	class VulkanGpuParams;
 

+ 17 - 14
Source/BansheeVulkanRenderAPI/Include/BsVulkanResource.h

@@ -24,14 +24,6 @@ namespace BansheeEngine
 	typedef Flags<VulkanUseFlag> VulkanUseFlags;
 	BS_FLAGS_OPERATORS(VulkanUseFlag);
 
-	/** Types of VulkanResource. */
-	enum class VulkanResourceType
-	{
-		Generic,
-		Image,
-		Buffer
-	};
-
 	/** 
 	 * Wraps one or multiple native Vulkan objects. Allows the object usage to be tracked in command buffers, handles
 	 * ownership transitions between different queues, and handles delayed object destruction.
@@ -41,12 +33,14 @@ namespace BansheeEngine
 	class VulkanResource
 	{
 	public:
-		VulkanResource(VulkanResourceManager* owner, bool concurrency, VulkanResourceType type = VulkanResourceType::Generic);
+		VulkanResource(VulkanResourceManager* owner, bool concurrency);
 		virtual ~VulkanResource();
 
 		/** 
 		 * Notifies the resource that it is currently bound to a command buffer. Buffer hasn't yet been submitted so the
-		 * resource isn't being used on the GPU yet.
+		 * resource isn't being used on the GPU yet. 
+		 * 
+		 * Must eventually be followed by a notifyUsed() or notifyUnbound().
 		 */
 		void notifyBound();
 
@@ -55,15 +49,28 @@ namespace BansheeEngine
 		 * buffer has actually been submitted to the queue and the resource is used by the GPU.
 		 * 
 		 * A resource can only be used by a single command buffer at a time unless resource concurrency is enabled.
+		 * 
+		 * Must follow a notifyBound(). Must eventually be followed by a notifyDone().
 		 */
 		void notifyUsed(VulkanCmdBuffer* buffer, VulkanUseFlags useFlags);
 
 		/** 
 		 * Notifies the resource that it is no longer used by on the GPU. This makes the resource usable on other command
 		 * buffers again.
+		 * 
+		 * Must follow a notifyUsed().
 		 */
 		void notifyDone();
 
+		/** 
+		 * Notifies the resource that it is no longer queued on the command buffer. This is similar to notifyDone(), but
+		 * should only be called if resource never got submitted to the GPU (e.g. command buffer was destroyed before
+		 * being submitted).
+		 * 
+		 * Must follow a notifyBound() if notifyUsed() wasn't called.
+		 */
+		void notifyUnbound();
+
 		/** 
 		 * Checks is the resource currently used on a device. 
 		 *
@@ -82,9 +89,6 @@ namespace BansheeEngine
 		 */
 		bool isBound() const { Lock(mMutex); return mNumBoundHandles > 0; }
 
-		/** Returns the type of the object wrapped by the resource. */
-		VulkanResourceType getType() const { Lock(mMutex); return mType; }
-
 		/** 
 		 * Returns the queue family the resource is currently owned by. Returns -1 if owned by no queue.
 		 * 
@@ -114,7 +118,6 @@ namespace BansheeEngine
 		VulkanResourceManager* mOwner;
 		UINT32 mQueueFamily;
 		State mState;
-		VulkanResourceType mType;
 		VulkanUseFlags mUseFlags;
 		
 		UINT32 mNumUsedHandles;

+ 179 - 6
Source/BansheeVulkanRenderAPI/Source/BsVulkanCommandBuffer.cpp

@@ -6,6 +6,8 @@
 #include "BsVulkanDevice.h"
 #include "BsVulkanGpuParams.h"
 #include "BsVulkanQueue.h"
+#include "BsVulkanTexture.h"
+#include "BsVulkanHardwareBuffer.h"
 
 namespace BansheeEngine
 {
@@ -138,6 +140,36 @@ namespace BansheeEngine
 
 			if (result == VK_TIMEOUT)
 				LOGWRN("Freeing a command buffer before done executing because fence wait expired!");
+
+			// Resources have been marked as used, make sure to notify them we're done with them
+			refreshFenceStatus();
+		}
+		else if(mState != State::Ready) 
+		{
+			// Notify any resources that they are no longer bound
+			for (auto& entry : mResources)
+			{
+				ResourceUseHandle& useHandle = entry.second;
+				assert(useHandle.used);
+
+				entry.first->notifyUnbound();
+			}
+
+			for (auto& entry : mImages)
+			{
+				ResourceUseHandle& useHandle = entry.second.useHandle;
+				assert(useHandle.used);
+
+				entry.first->notifyUnbound();
+			}
+
+			for (auto& entry : mBuffers)
+			{
+				ResourceUseHandle& useHandle = entry.second.useHandle;
+				assert(useHandle.used);
+
+				entry.first->notifyUnbound();
+			}
 		}
 		
 		vkDestroyFence(device, mFence, gVulkanAllocator);
@@ -222,8 +254,25 @@ namespace BansheeEngine
 					entry.first->notifyDone();
 				}
 
+				for (auto& entry : mImages)
+				{
+					ResourceUseHandle& useHandle = entry.second.useHandle;
+					assert(useHandle.used);
+
+					entry.first->notifyDone();
+				}
+
+				for (auto& entry : mBuffers)
+				{
+					ResourceUseHandle& useHandle = entry.second.useHandle;
+					assert(useHandle.used);
+
+					entry.first->notifyDone();
+				}
+
 				mResources.clear();
-				mBoundParams.clear();
+				mImages.clear();
+				mBuffers.clear();
 			}
 		}
 		else
@@ -250,9 +299,56 @@ namespace BansheeEngine
 		}
 	}
 
-	void VulkanCmdBuffer::registerGpuParams(const SPtr<VulkanGpuParams>& params)
+	void VulkanCmdBuffer::registerResource(VulkanImage* res, VkAccessFlags accessFlags, VkImageLayout layout, 
+		const VkImageSubresourceRange& range, VulkanUseFlags flags)
+	{
+		auto insertResult = mImages.insert(std::make_pair(res, ImageInfo()));
+		if (insertResult.second) // New element
+		{
+			ImageInfo& imageInfo = insertResult.first->second;
+			imageInfo.accessFlags = accessFlags;
+			imageInfo.layout = layout;
+			imageInfo.range = range;
+
+			imageInfo.useHandle.used = false;
+			imageInfo.useHandle.flags = flags;
+
+			res->notifyBound();
+		}
+		else // Existing element
+		{
+			ImageInfo& imageInfo = insertResult.first->second;
+
+			assert(!imageInfo.useHandle.used);
+			imageInfo.useHandle.flags |= flags;
+
+			assert(imageInfo.layout == layout && "Cannot bind the same image with two different layouts on the same command buffer.");
+			imageInfo.accessFlags |= accessFlags;
+			imageInfo.range = range;
+		}
+	}
+
+	void VulkanCmdBuffer::registerResource(VulkanBuffer* res, VkAccessFlags accessFlags, VulkanUseFlags flags)
 	{
-		mBoundParams.insert(params);
+		auto insertResult = mBuffers.insert(std::make_pair(res, BufferInfo()));
+		if (insertResult.second) // New element
+		{
+			BufferInfo& bufferInfo = insertResult.first->second;
+			bufferInfo.accessFlags = accessFlags;
+
+			bufferInfo.useHandle.used = false;
+			bufferInfo.useHandle.flags = flags;
+
+			res->notifyBound();
+		}
+		else // Existing element
+		{
+			BufferInfo& bufferInfo = insertResult.first->second;
+
+			assert(!bufferInfo.useHandle.used);
+			bufferInfo.useHandle.flags |= flags;
+			bufferInfo.accessFlags |= accessFlags;
+		}
 	}
 
 	void VulkanCmdBuffer::submit(VulkanQueue* queue, UINT32 queueIdx, UINT32 syncMask)
@@ -260,12 +356,67 @@ namespace BansheeEngine
 		assert(isReadyForSubmit());
 
 		// Issue pipeline barriers for queue transitions (need to happen on original queue first, then on new queue)
-		for (auto& entry : mBoundParams)
-			entry->prepareForSubmit(this, mTransitionInfoTemp);
+		for(auto& entry : mBuffers)
+		{
+			VulkanBuffer* resource = static_cast<VulkanBuffer*>(entry.first);
+
+			if (!resource->isExclusive())
+				continue;
+
+			UINT32 currentQueueFamily = resource->getQueueFamily();
+			if (currentQueueFamily != mQueueFamily)
+			{
+				Vector<VkBufferMemoryBarrier>& barriers = mTransitionInfoTemp[currentQueueFamily].bufferBarriers;
+
+				barriers.push_back(VkBufferMemoryBarrier());
+				VkBufferMemoryBarrier& barrier = barriers.back();
+				barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
+				barrier.pNext = nullptr;
+				barrier.srcAccessMask = entry.second.accessFlags;
+				barrier.dstAccessMask = entry.second.accessFlags;
+				barrier.srcQueueFamilyIndex = currentQueueFamily;
+				barrier.dstQueueFamilyIndex = mQueueFamily;
+				barrier.buffer = resource->getHandle();
+				barrier.offset = 0;
+				barrier.size = VK_WHOLE_SIZE;
+			}
+		}
+
+		for(auto& entry : mImages)
+		{
+			VulkanImage* resource = static_cast<VulkanImage*>(entry.first);
+
+			UINT32 currentQueueFamily = resource->getQueueFamily();
+			bool queueMismatch = resource->isExclusive() && currentQueueFamily != mQueueFamily;
+
+			if (queueMismatch || resource->getLayout() != entry.second.layout)
+			{
+				Vector<VkImageMemoryBarrier>& barriers = mTransitionInfoTemp[currentQueueFamily].imageBarriers;
+
+				barriers.push_back(VkImageMemoryBarrier());
+				VkImageMemoryBarrier& barrier = barriers.back();
+				barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+				barrier.pNext = nullptr;
+				barrier.srcAccessMask = entry.second.accessFlags;
+				barrier.dstAccessMask = entry.second.accessFlags;
+				barrier.srcQueueFamilyIndex = currentQueueFamily;
+				barrier.dstQueueFamilyIndex = mQueueFamily;
+				barrier.oldLayout = resource->getLayout();
+				barrier.newLayout = entry.second.layout;
+				barrier.image = resource->getHandle();
+				barrier.subresourceRange = entry.second.range;
+
+				resource->setLayout(entry.second.layout);
+			}
+		}
 
 		VulkanDevice& device = queue->getDevice();
 		for (auto& entry : mTransitionInfoTemp)
 		{
+			bool empty = entry.second.imageBarriers.size() == 0 && entry.second.bufferBarriers.size() == 0;
+			if (empty)
+				continue;
+
 			UINT32 entryQueueFamily = entry.first;
 
 			// No queue transition needed for entries on this queue (this entry is most likely an in image layout transition)
@@ -338,6 +489,10 @@ namespace BansheeEngine
 		// Issue second part of transition pipeline barriers (on this queue)
 		for (auto& entry : mTransitionInfoTemp)
 		{
+			bool empty = entry.second.imageBarriers.size() == 0 && entry.second.bufferBarriers.size() == 0;
+			if (empty)
+				continue;
+
 			VulkanCmdBuffer* cmdBuffer = device.getCmdBufferPool().getBuffer(mQueueFamily, false);
 			VkCommandBuffer vkCmdBuffer = cmdBuffer->getHandle();
 
@@ -366,7 +521,25 @@ namespace BansheeEngine
 			assert(!useHandle.used);
 
 			useHandle.used = true;
-			entry.first->notifyUsed();
+			entry.first->notifyUsed(this, useHandle.flags);
+		}
+
+		for (auto& entry : mImages)
+		{
+			ResourceUseHandle& useHandle = entry.second.useHandle;
+			assert(!useHandle.used);
+
+			useHandle.used = true;
+			entry.first->notifyUsed(this, useHandle.flags);
+		}
+
+		for (auto& entry : mBuffers)
+		{
+			ResourceUseHandle& useHandle = entry.second.useHandle;
+			assert(!useHandle.used);
+
+			useHandle.used = true;
+			entry.first->notifyUsed(this, useHandle.flags);
 		}
 
 		cbm.refreshStates(deviceIdx);

+ 95 - 110
Source/BansheeVulkanRenderAPI/Source/BsVulkanGpuParams.cpp

@@ -149,6 +149,8 @@ namespace BansheeEngine
 		mData = (UINT8*)bs_alloc(setsDirtyBytes + (perSetBytes + writeSetInfosBytes + writeInfosBytes) * numDevices);
 		UINT8* dataIter = mData;
 
+		Lock lock(mMutex); // Set write operations need to be thread safe
+
 		mSetsDirty = (bool*)dataIter;
 		memset(mSetsDirty, 1, setsDirtyBytes);
 		dataIter += setsDirtyBytes;
@@ -184,8 +186,9 @@ namespace BansheeEngine
 
 				VkDescriptorSetLayoutBinding* perSetBindings = &bindings[bindingOffset];
 				perSetData.layout = descManager.getLayout(perSetBindings, numBindingsPerSet);
-				perSetData.set = descManager.createSet(perSetData.layout);
 				perSetData.numElements = numBindingsPerSet;
+				perSetData.latestSet = descManager.createSet(perSetData.layout);
+				perSetData.sets.push_back(perSetData.latestSet);
 
 				layouts[j] = perSetData.layout;
 
@@ -255,10 +258,17 @@ namespace BansheeEngine
 
 	VulkanGpuParams::~VulkanGpuParams()
 	{
-		for (UINT32 i = 0; i < BS_MAX_DEVICES; i++)
 		{
-			for (UINT32 j = 0; j < mPerDeviceData[i].numSets; j++)
-				mPerDeviceData[i].perSetData[j].set->destroy();
+			Lock lock(mMutex);
+
+			for (UINT32 i = 0; i < BS_MAX_DEVICES; i++)
+			{
+				for (UINT32 j = 0; j < mPerDeviceData[i].numSets; j++)
+				{
+					for (auto& entry : mPerDeviceData[i].perSetData[j].sets)
+						entry->destroy();
+				}
+			}
 		}
 
 		bs_free(mData); // Everything allocated under a single buffer to a single free is enough
@@ -268,6 +278,8 @@ namespace BansheeEngine
 	{
 		GpuParamsCore::setParamBlockBuffer(set, slot, paramBlockBuffer);
 
+		Lock(mMutex);
+
 		VulkanGpuParamBlockBufferCore* vulkanParamBlockBuffer =
 			static_cast<VulkanGpuParamBlockBufferCore*>(paramBlockBuffer.get());
 
@@ -290,6 +302,8 @@ namespace BansheeEngine
 	{
 		GpuParamsCore::setTexture(set, slot, texture);
 
+		Lock(mMutex);
+
 		VulkanTextureCore* vulkanTexture = static_cast<VulkanTextureCore*>(texture.get());
 		for (UINT32 i = 0; i < BS_MAX_DEVICES; i++)
 		{
@@ -307,6 +321,8 @@ namespace BansheeEngine
 	{
 		GpuParamsCore::setLoadStoreTexture(set, slot, texture, surface);
 
+		Lock(mMutex);
+
 		VulkanTextureCore* vulkanTexture = static_cast<VulkanTextureCore*>(texture.get());
 		for (UINT32 i = 0; i < BS_MAX_DEVICES; i++)
 		{
@@ -323,6 +339,8 @@ namespace BansheeEngine
 	{
 		GpuParamsCore::setBuffer(set, slot, buffer);
 
+		Lock(mMutex);
+
 		VulkanGpuBufferCore* vulkanBuffer = static_cast<VulkanGpuBufferCore*>(buffer.get());
 		for (UINT32 i = 0; i < BS_MAX_DEVICES; i++)
 		{
@@ -343,6 +361,8 @@ namespace BansheeEngine
 	{
 		GpuParamsCore::setSamplerState(set, slot, sampler);
 
+		Lock(mMutex);
+
 		VulkanSamplerStateCore* vulkanSampler = static_cast<VulkanSamplerStateCore*>(sampler.get());
 		for(UINT32 i = 0; i < BS_MAX_DEVICES; i++)
 		{
@@ -367,6 +387,8 @@ namespace BansheeEngine
 		if (texture == nullptr)
 			return;
 
+		Lock(mMutex);
+
 		VulkanTextureCore* vulkanTexture = static_cast<VulkanTextureCore*>(texture.get());
 		for (UINT32 i = 0; i < BS_MAX_DEVICES; i++)
 		{
@@ -379,87 +401,19 @@ namespace BansheeEngine
 		mSetsDirty[set] = true;
 	}
 
-	void VulkanGpuParams::prepareForSubmit(VulkanCmdBuffer* buffer, UnorderedMap<UINT32, TransitionInfo>& transitionInfo)
+	void VulkanGpuParams::bind(VulkanCommandBuffer& buffer)
 	{
-		UINT32 deviceIdx = buffer->getDeviceIdx();
-		UINT32 queueFamily = buffer->getQueueFamily();
+		UINT32 deviceIdx = buffer.getDeviceIdx();
 
-		const PerDeviceData& perDeviceData = mPerDeviceData[deviceIdx];
+		PerDeviceData& perDeviceData = mPerDeviceData[deviceIdx];
 		if (perDeviceData.perSetData == nullptr)
 			return;
 
-		for(UINT32 i = 0; i < perDeviceData.numSets; i++)
-		{
-			if (!mSetsDirty[i])
-				continue;
-
-			// Note: Currently I write to the entire set at once, but it might be beneficial to remember only the exact
-			// entries that were updated, and only write to them individually.
-			const PerSetData& perSetData = perDeviceData.perSetData[i];
-			perSetData.set->write(perSetData.writeSetInfos, perSetData.numElements);
-
-			mSetsDirty[i] = false;
-		}
-
-		auto registerBuffer = [&](VulkanBuffer* resource, VkAccessFlags accessFlags, VulkanUseFlags useFlags)
-		{
-			if (resource == nullptr)
-				return;
-
-			buffer->registerResource(resource, useFlags);
-
-			if (resource->isExclusive())
-			{
-				UINT32 currentQueueFamily = resource->getQueueFamily();
-				if (currentQueueFamily != queueFamily)
-				{
-					Vector<VkBufferMemoryBarrier>& barriers = transitionInfo[currentQueueFamily].bufferBarriers;
-
-					barriers.push_back(VkBufferMemoryBarrier());
-					VkBufferMemoryBarrier& barrier = barriers.back();
-					barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
-					barrier.pNext = nullptr;
-					barrier.srcAccessMask = accessFlags;
-					barrier.dstAccessMask = accessFlags;
-					barrier.srcQueueFamilyIndex = currentQueueFamily;
-					barrier.dstQueueFamilyIndex = queueFamily;
-					barrier.buffer = resource->getHandle();
-					barrier.offset = 0;
-					barrier.size = VK_WHOLE_SIZE;
-				}
-			}
-		};
-
-		auto registerImage = [&](VulkanImage* resource, VkAccessFlags accessFlags, VkImageLayout layout, 
-			const VkImageSubresourceRange& range, VulkanUseFlags useFlags)
-		{
-			buffer->registerResource(resource, useFlags);
-
-			UINT32 currentQueueFamily = resource->getQueueFamily();
-			bool queueMismatch = resource->isExclusive() && currentQueueFamily != queueFamily;
-
-			if (queueMismatch || resource->getLayout() != layout)
-			{
-				Vector<VkImageMemoryBarrier>& barriers = transitionInfo[currentQueueFamily].imageBarriers;
-
-				barriers.push_back(VkImageMemoryBarrier());
-				VkImageMemoryBarrier& barrier = barriers.back();
-				barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
-				barrier.pNext = nullptr;
-				barrier.srcAccessMask = accessFlags;
-				barrier.dstAccessMask = accessFlags;
-				barrier.srcQueueFamilyIndex = currentQueueFamily;
-				barrier.dstQueueFamilyIndex = queueFamily;
-				barrier.oldLayout = resource->getLayout();
-				barrier.newLayout = layout;
-				barrier.image = resource->getHandle();
-				barrier.subresourceRange = range;
-
-				resource->setLayout(layout);
-			}
-		};
-
-		for(UINT32 i = 0; i < mNumElements[(UINT32)ElementType::ParamBlock]; i++)
+		// Registers resources with the command buffer
+		// Note: Makes the assumption that this object (and all of the resources it holds) are externally locked, and will
+		// not be modified on another thread while being bound.
+		VulkanCmdBuffer* internalCB = buffer.getInternal();
+		for (UINT32 i = 0; i < mNumElements[(UINT32)ElementType::ParamBlock]; i++)
 		{
 			if (mParamBlockBuffers[i] == nullptr)
 				continue;
@@ -467,7 +421,7 @@ namespace BansheeEngine
 			VulkanGpuParamBlockBufferCore* element = static_cast<VulkanGpuParamBlockBufferCore*>(mParamBlockBuffers[i].get());
 
 			VulkanBuffer* resource = element->getResource(deviceIdx);
-			registerBuffer(resource, VK_ACCESS_UNIFORM_READ_BIT, VulkanUseFlag::Read);
+			internalCB->registerResource(resource, VK_ACCESS_UNIFORM_READ_BIT, VulkanUseFlag::Read);
 		}
 
 		for (UINT32 i = 0; i < mNumElements[(UINT32)ElementType::Buffer]; i++)
@@ -480,14 +434,14 @@ namespace BansheeEngine
 			VkAccessFlags accessFlags = VK_ACCESS_SHADER_READ_BIT;
 			VulkanUseFlags useFlags = VulkanUseFlag::Read;
 
-			if(element->getProperties().getRandomGpuWrite())
+			if (element->getProperties().getRandomGpuWrite())
 			{
 				accessFlags |= VK_ACCESS_SHADER_WRITE_BIT;
 				useFlags |= VulkanUseFlag::Write;
 			}
 
 			VulkanBuffer* resource = element->getResource(deviceIdx);
-			registerBuffer(resource, accessFlags, useFlags);
+			internalCB->registerResource(resource, accessFlags, useFlags);
 		}
 
 		for (UINT32 i = 0; i < mNumElements[(UINT32)ElementType::SamplerState]; i++)
@@ -501,7 +455,7 @@ namespace BansheeEngine
 			if (resource == nullptr)
 				continue;
 
-			buffer->registerResource(resource, VulkanUseFlag::Read);
+			internalCB->registerResource(resource, VulkanUseFlag::Read);
 		}
 
 		for (UINT32 i = 0; i < mNumElements[(UINT32)ElementType::LoadStoreTexture]; i++)
@@ -519,14 +473,14 @@ namespace BansheeEngine
 			VulkanUseFlags useFlags = VulkanUseFlag::Read | VulkanUseFlag::Write;
 
 			const TextureSurface& surface = mLoadStoreSurfaces[i];
-			VkImageSubresourceRange subresource;
-			subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-			subresource.baseArrayLayer = surface.arraySlice;
-			subresource.layerCount = surface.numArraySlices;
-			subresource.baseMipLevel = surface.mipLevel;
-			subresource.levelCount = surface.numMipLevels;
-
-			registerImage(resource, accessFlags, VK_IMAGE_LAYOUT_GENERAL, subresource, useFlags);
+			VkImageSubresourceRange range;
+			range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			range.baseArrayLayer = surface.arraySlice;
+			range.layerCount = surface.numArraySlices;
+			range.baseMipLevel = surface.mipLevel;
+			range.levelCount = surface.numMipLevels;
+
+			internalCB->registerResource(resource, accessFlags, VK_IMAGE_LAYOUT_GENERAL, range, useFlags);
 		}
 
 		for (UINT32 i = 0; i < mNumElements[(UINT32)ElementType::Texture]; i++)
@@ -544,29 +498,60 @@ namespace BansheeEngine
 
 			bool isDepthStencil = (props.getUsage() & TU_DEPTHSTENCIL) != 0;
 
-			VkImageSubresourceRange subresource;
-			subresource.aspectMask = isDepthStencil ? VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT
-													: VK_IMAGE_ASPECT_COLOR_BIT;
+			VkImageSubresourceRange range;
+			range.aspectMask = isDepthStencil ? VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT
+				: VK_IMAGE_ASPECT_COLOR_BIT;
 
-			subresource.baseArrayLayer = 0;
-			subresource.layerCount = props.getNumFaces();
-			subresource.baseMipLevel = 0;
-			subresource.levelCount = props.getNumMipmaps();
+			range.baseArrayLayer = 0;
+			range.layerCount = props.getNumFaces();
+			range.baseMipLevel = 0;
+			range.levelCount = props.getNumMipmaps();
 
-			registerImage(resource, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subresource, 
-				VulkanUseFlag::Read);
+			internalCB->registerResource(resource, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 
+				range, VulkanUseFlag::Read);
 		}
-	}
 
-	void VulkanGpuParams::bind(VulkanCommandBuffer& buffer)
-	{
-		UINT32 deviceIdx = buffer.getDeviceIdx();
+		// Acquire sets as needed, and updated their contents if dirty
+		VulkanRenderAPI& rapi = static_cast<VulkanRenderAPI&>(RenderAPICore::instance());
+		VulkanDevice& device = *rapi._getDevice(deviceIdx);
+		VulkanDescriptorManager& descManager = device.getDescriptorManager();
 
-		PerDeviceData& perDeviceData = mPerDeviceData[deviceIdx];
-		if (perDeviceData.perSetData == nullptr)
-			return;
+		Lock(mMutex);
+		for (UINT32 i = 0; i < perDeviceData.numSets; i++)
+		{
+			PerSetData& perSetData = perDeviceData.perSetData[i];
 
-		VulkanCmdBuffer* internalCB = buffer.getInternal();
+			if (!mSetsDirty[i]) // Set not dirty, just use the last one we wrote (this is fine even across multiple command buffers)
+				continue;
+
+			// Set is dirty, we need to update
+			//// Use latest unless already used, otherwise try to find an unused one
+			if(perSetData.latestSet->isBound()) // Checking this is okay, because it's only modified below when we call registerResource, which is under the same lock as this
+			{
+				perSetData.latestSet = nullptr;
+
+				for(auto& entry : perSetData.sets)
+				{
+					if(!entry->isBound())
+					{
+						perSetData.latestSet = entry;
+						break;
+					}
+				}
+
+				// Cannot find an empty set, allocate a new one
+				perSetData.latestSet = descManager.createSet(perSetData.layout);
+				perSetData.sets.push_back(perSetData.latestSet);
+			}
+
+			// Note: Currently I write to the entire set at once, but it might be beneficial to remember only the exact
+			// entries that were updated, and only write to them individually.
+			perSetData.latestSet->write(perSetData.writeSetInfos, perSetData.numElements);
+
+			mSetsDirty[i] = false;
+		}
+
+		// Actually bind the sets to the command buffer
 		VkCommandBuffer vkCB = internalCB->getHandle();
 
 		VkPipelineBindPoint bindPoint;
@@ -588,7 +573,7 @@ namespace BansheeEngine
 		VkDescriptorSet* sets = bs_stack_alloc<VkDescriptorSet>(perDeviceData.numSets);
 		for (UINT32 i = 0; i < perDeviceData.numSets; i++)
 		{
-			VulkanDescriptorSet* set = perDeviceData.perSetData[i].set;
+			VulkanDescriptorSet* set = perDeviceData.perSetData[i].latestSet;
 
 			internalCB->registerResource(set, VulkanUseFlag::Read);
 			sets[i] = set->getHandle();

+ 1 - 1
Source/BansheeVulkanRenderAPI/Source/BsVulkanHardwareBuffer.cpp

@@ -9,7 +9,7 @@
 namespace BansheeEngine
 {
 	VulkanBuffer::VulkanBuffer(VulkanResourceManager* owner, VkBuffer buffer, VkBufferView view, VkDeviceMemory memory)
-		:VulkanResource(owner, false, VulkanResourceType::Buffer), mBuffer(buffer), mView(view), mMemory(memory)
+		:VulkanResource(owner, false), mBuffer(buffer), mView(view), mMemory(memory)
 	{
 
 	}

+ 0 - 1
Source/BansheeVulkanRenderAPI/Source/BsVulkanRenderAPI.cpp

@@ -317,7 +317,6 @@ namespace BansheeEngine
 		SPtr<VulkanGpuParams> vulkanGpuParams = std::static_pointer_cast<VulkanGpuParams>(gpuParams);
 
 		vulkanGpuParams->bind(*cb);
-		cb->getInternal()->registerGpuParams(vulkanGpuParams);
 
 		BS_INC_RENDER_STAT(NumGpuParamBinds);
 	}

+ 10 - 2
Source/BansheeVulkanRenderAPI/Source/BsVulkanResource.cpp

@@ -6,14 +6,13 @@
 
 namespace BansheeEngine
 {
-	VulkanResource::VulkanResource(VulkanResourceManager* owner, bool concurrency, VulkanResourceType type)
+	VulkanResource::VulkanResource(VulkanResourceManager* owner, bool concurrency)
 	{
 		Lock lock(mMutex);
 
 		mOwner = owner;
 		mQueueFamily = -1;
 		mState = concurrency ? State::Shared : State::Normal;
-		mType = type;
 		mNumUsedHandles = 0;
 		mNumBoundHandles = 0;
 	}
@@ -66,6 +65,15 @@ namespace BansheeEngine
 			mOwner->destroy(this);
 	}
 
+	void VulkanResource::notifyUnbound()
+	{
+		Lock lock(mMutex);
+		mNumBoundHandles--;
+
+		if (!isBound() && mState == State::Destroyed) // Queued for destruction
+			mOwner->destroy(this);
+	}
+
 	void VulkanResource::destroy()
 	{
 		Lock lock(mMutex);

+ 1 - 1
Source/BansheeVulkanRenderAPI/Source/BsVulkanTexture.cpp

@@ -8,7 +8,7 @@
 namespace BansheeEngine
 {
 	VulkanImage::VulkanImage(VulkanResourceManager* owner, VkImage image, VkDeviceMemory memory, VkImageLayout layout)
-		:VulkanResource(owner, false, VulkanResourceType::Image), mImage(image), mMemory(memory), mLayout(layout)
+		:VulkanResource(owner, false), mImage(image), mMemory(memory), mLayout(layout)
 	{
 		
 	}