Browse Source

Vulkan: Now tracking image layouts on a per-resource basis. This way individual faces & mip-levels can be bound for rendering, particularily useful for cubemaps and texture arrays in general.

BearishSun 9 years ago
parent
commit
2e94779fc6

+ 1 - 1
Source/BansheeEngine/Source/BsLight.cpp

@@ -97,7 +97,7 @@ namespace bs
 			break;
 		case LightType::Spot:
 		{
-			Degree angle = Math::clamp(mSpotAngle, Degree(-90), Degree(90));
+			Degree angle = Math::clamp(mSpotAngle, Degree(-89), Degree(89));
 			float coneRadius = Math::tan(angle) * mRange;
 
 			float radius;

+ 4 - 0
Source/BansheeUtility/Include/BsGlobalFrameAlloc.h

@@ -143,6 +143,10 @@ namespace bs
 	template <typename T, typename A = StdAlloc<T, FrameAlloc>>
 	using FrameStack = std::stack < T, std::deque<T, A> > ;
 
+	/** Queue allocated with a frame allocator. */
+	template <typename T, typename A = StdAlloc<T, FrameAlloc>>
+	using FrameQueue = std::queue<T, std::deque<T, A>>;
+
 	/** Set allocated with a frame allocator. */
 	template <typename T, typename P = std::less<T>, typename A = StdAlloc<T, FrameAlloc>>
 	using FrameSet = std::set < T, P, A > ;

+ 24 - 4
Source/BansheeVulkanRenderAPI/Include/BsVulkanCommandBuffer.h

@@ -199,6 +199,7 @@ namespace bs { namespace ct
 		 * updates the externally visible image layout field to @p finalLayout (once submitted).
 		 * 
 		 * @param[in]	res						Image to register with the command buffer.
+		 * @param[in]	range					Range of sub-resources to register.
 		 * @param[in]	newLayout				Layout the image needs to be transitioned in before use. Set to undefined
 		 *										layout if no transition is required.
 		 * @param[in]	finalLayout				Determines what value the externally visible image layout will be set after
@@ -208,15 +209,15 @@ namespace bs { namespace ct
 		 * @param[in]	isFBAttachment			Determines if the image is being used as a framebuffer attachment (if true),
 		 *										or just as regular shader input (if false).
 		 */
-		void registerResource(VulkanImage* res, VkImageLayout newLayout, VkImageLayout finalLayout, VulkanUseFlags flags, 
-			bool isFBAttachment = false);
+		void registerResource(VulkanImage* res, const VkImageSubresourceRange& range, VkImageLayout newLayout, 
+							  VkImageLayout finalLayout, VulkanUseFlags flags, bool isFBAttachment = false);
 
 		/** 
 		 * 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. Performs no layout transitions on the image, they must be performed
 		 * by the caller, or not required at all.
 		 */
-		void registerResource(VulkanImage* res, VulkanUseFlags flags);
+		void registerResource(VulkanImage* res, 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
@@ -322,9 +323,17 @@ namespace bs { namespace ct
 		/** Contains information about a single Vulkan image resource bound/used on this command buffer. */
 		struct ImageInfo
 		{
-			VkImageSubresourceRange range;
 			ResourceUseHandle useHandle;
 
+			UINT32 subresourceInfoIdx;
+			UINT32 numSubresourceInfos;
+		};
+
+		/** Contains information about a range of Vulkan image sub-resources bound/used on this command buffer. */
+		struct ImageSubresourceInfo
+		{
+			VkImageSubresourceRange range;
+
 			// Only relevant for layout transitions
 			VkImageLayout initialLayout;
 			VkImageLayout currentLayout;
@@ -373,6 +382,16 @@ namespace bs { namespace ct
 		 */
 		void updateFinalLayouts();
 
+		/** 
+		 * Updates an existing sub-resource info range with new layout, use flags and framebuffer flag. Returns true if
+		 * the bound sub-resource is a read-only framebuffer attachment.
+		 */
+		bool updateSubresourceInfo(VulkanImage* image, UINT32 imageInfoIdx, ImageSubresourceInfo& subresourceInfo, 
+			VkImageLayout newLayout, VkImageLayout finalLayout, VulkanUseFlags flags, bool isFBAttachment);
+
+		/** Finds a subresource info structure containing the specified face and mip level of the provided image. */
+		ImageSubresourceInfo& findSubresourceInfo(VulkanImage* image, UINT32 face, UINT32 mip);
+
 		UINT32 mId;
 		UINT32 mQueueFamily;
 		State mState;
@@ -395,6 +414,7 @@ namespace bs { namespace ct
 		UnorderedMap<VulkanResource*, UINT32> mImages;
 		UnorderedMap<VulkanResource*, BufferInfo> mBuffers;
 		Vector<ImageInfo> mImageInfos;
+		Vector<ImageSubresourceInfo> mSubresourceInfos;
 		UINT32 mGlobalQueueIdx;
 
 		SPtr<VulkanGraphicsPipelineState> mGraphicsPipeline;

+ 10 - 0
Source/BansheeVulkanRenderAPI/Include/BsVulkanCommandBufferManager.h

@@ -44,6 +44,14 @@ namespace bs { namespace ct
 		void setLayout(VkImage image, VkAccessFlags srcAccessFlags, VkAccessFlags dstAccessFlags, 
 			VkImageLayout oldLayout, VkImageLayout newLayout, const VkImageSubresourceRange& range);
 
+		/**
+		 * Issues one or multiple pipeline barrier on the provided image, changing the layout of its subresources. 
+		 * Automatically determines original layout for individual sub-resources, groups the pipeline barriers and issues
+		 * them.
+		 */
+		void setLayout(VulkanImage* image, const VkImageSubresourceRange& range, VkAccessFlags newAccessMask, 
+					   VkImageLayout newLayout);
+
 		/** 
 		 * Submits the command buffer on the queue. 
 		 * 
@@ -68,6 +76,8 @@ namespace bs { namespace ct
 
 		VulkanCmdBuffer* mCB;
 		UINT32 mSyncMask;
+
+		Vector<VkImageMemoryBarrier> mBarriersTemp;
 	};
 
 	/** 

+ 9 - 2
Source/BansheeVulkanRenderAPI/Include/BsVulkanFramebuffer.h

@@ -17,8 +17,8 @@ namespace bs { namespace ct
 		/** Image to attach or null if none. */
 		VulkanImage* image = nullptr;
 
-		/** View of the image to attach or VK_NULL_HANDLE if none. */
-		VkImageView view = VK_NULL_HANDLE;
+		/** Surface representing the sub-resource of the image to use as an attachment. */
+		TextureSurface surface;
 
 		/** Format of the attached image. */
 		VkFormat format = VK_FORMAT_UNDEFINED;
@@ -56,6 +56,7 @@ namespace bs { namespace ct
 	struct VulkanFramebufferAttachment
 	{
 		VulkanImage* image = nullptr;
+		TextureSurface surface;
 		UINT32 baseLayer = 0;
 		VkImageLayout finalLayout = VK_IMAGE_LAYOUT_UNDEFINED;
 		UINT32 index = 0;
@@ -121,6 +122,12 @@ namespace bs { namespace ct
 
 		/** Returns sample flags that determine if the framebuffer supports multi-sampling, and for how many samples. */
 		VkSampleCountFlagBits getSampleFlags() const { return mSampleFlags; }
+
+		/** 
+		 * Returns the maximum required number of clear entries to provide in a render pass start structure. This depends on
+		 * the clear mask and the attachments on the framebuffer. 
+		 */
+		UINT32 getNumClearEntries(ClearMask clearMask) const;
 	private:
 		/** Information about a single frame-buffer variant. */
 		struct Variant

+ 0 - 8
Source/BansheeVulkanRenderAPI/Include/BsVulkanSwapChain.h

@@ -15,7 +15,6 @@ namespace bs { namespace ct
 	struct SwapChainSurface
 	{
 		VulkanImage* image;
-		VkImageView view;
 		VulkanSemaphore* sync;
 		bool acquired;
 		bool needsWait;
@@ -74,12 +73,6 @@ namespace bs { namespace ct
 		/** Returns the number of available color surfaces. */
 		UINT32 getNumColorSurfaces() const { return (UINT32)mSurfaces.size(); }
 
-		/** Returns an image view representing the color surface at the specified index. */
-		VkImageView getColorView(UINT32 index) const { return mSurfaces[index].view; }
-
-		/** Returns an image view representing the depth-stencil buffer, if any. */
-		VkImageView getDepthStencilView() const { return mDepthStencilView; }
-
 		/** Returns the internal swap chain handle. */
 		VkSwapchainKHR getHandle() const { return mSwapChain; }
 	private:
@@ -94,7 +87,6 @@ namespace bs { namespace ct
 		Vector<SwapChainSurface> mSurfaces;
 
 		VulkanImage* mDepthStencilImage = nullptr;
-		VkImageView mDepthStencilView = VK_NULL_HANDLE;
 
 		UINT32 mCurrentSemaphoreIdx = 0;
 		UINT32 mCurrentBackBufferIdx = 0;

+ 24 - 12
Source/BansheeVulkanRenderAPI/Include/BsVulkanTexture.h

@@ -68,16 +68,6 @@ namespace bs { namespace ct
 		/** Returns the preferred (not necessarily current) layout of the image. */
 		VkImageLayout getOptimalLayout() const;
 
-		/** 
-		 * Returns the layout the image is currently in. Note that this is only used to communicate layouts between 
-		 * different command buffers, and will only be updated only after command buffer submit() call. In short this means
-		 * you should only care about this value on the core thread.
-		 */
-		VkImageLayout getLayout() const { return mLayout; }
-
-		/** Notifies the resource that the current image layout has changed. */
-		void setLayout(VkImageLayout layout) { mLayout = layout; }
-
 		/** 
 		 * Returns an image view that covers all faces and mip maps of the texture. 
 		 * 
@@ -101,6 +91,9 @@ namespace bs { namespace ct
 		/** Retrieves a subresource range covering all the sub-resources of the image. */
 		VkImageSubresourceRange getRange() const;
 
+		/** Retrieves a subresource range covering all the specified sub-resource range of the image. */
+		VkImageSubresourceRange getRange(const TextureSurface& surface) const;
+
 		/** 
 		 * Retrieves a separate resource for a specific image face & mip level. This allows the caller to track subresource
 		 * usage individually, instead for the entire image. 
@@ -145,6 +138,13 @@ namespace bs { namespace ct
 		 */
 		VkAccessFlags getAccessFlags(VkImageLayout layout, bool readOnly = false);
 
+		/** 
+		 * Generates a set of image barriers that are grouped depending on the current layout of individual sub-resources
+		 * in the specified range. The method will try to reduce the number of generated barriers by grouping as many
+		 * sub-resources as possibly.
+		 */
+		void getBarriers(const VkImageSubresourceRange& range, Vector<VkImageMemoryBarrier>& barriers);
+
 	private:
 		/** Creates a new view of the provided part (or entirety) of surface. */
 		VkImageView createView(const TextureSurface& surface, VkImageAspectFlags aspectMask) const;
@@ -159,7 +159,6 @@ namespace bs { namespace ct
 
 		VkImage mImage;
 		VkDeviceMemory mMemory;
-		VkImageLayout mLayout;
 		VkImageView mMainView;
 		VkImageView mFramebufferMainView;
 		VulkanImageUsage mUsage;
@@ -177,7 +176,20 @@ namespace bs { namespace ct
 	class VulkanImageSubresource : public VulkanResource
 	{
 	public:
-		VulkanImageSubresource(VulkanResourceManager* owner);
+		VulkanImageSubresource(VulkanResourceManager* owner, VkImageLayout layout);
+
+		/** 
+		 * Returns the layout the subresource is currently in. Note that this is only used to communicate layouts between 
+		 * different command buffers, and will only be updated only after command buffer submit() call. In short this means
+		 * you should only care about this value on the core thread.
+		 */
+		VkImageLayout getLayout() const { return mLayout; }
+
+		/** Notifies the resource that the current subresource layout has changed. */
+		void setLayout(VkImageLayout layout) { mLayout = layout; }
+
+	private:
+		VkImageLayout mLayout;
 	};
 
 	/**	Vulkan implementation of a texture. */

+ 15 - 0
Source/BansheeVulkanRenderAPI/Include/BsVulkanUtility.h

@@ -76,6 +76,21 @@ namespace bs { namespace ct
 
 		/** Checks is a flag for a particular device enabled. */
 		static bool isDeviceIdxSet(const VulkanRenderAPI& rapi, UINT32 idx, GpuDeviceFlags flags);
+
+		/** 
+		 * Subdivides an image subresource range by cutting it with another range. If the ranges don't overlap, or the
+		 * @p cutWith range completely overs the @p toCut range, the original @p toCut range is output. 
+		 * 
+		 * @param[in]	toCut		Range to cut.
+		 * @param[in]	cutWith		Range to cut with.
+		 * @param[out]	output		Pieces of the range that was cut.
+		 * @param[out]	numAreas	Number of pieces in the @p output array.
+		 */
+		static void cutRange(const VkImageSubresourceRange& toCut, const VkImageSubresourceRange& cutWith,
+							 std::array<VkImageSubresourceRange, 5>& output, UINT32& numAreas);
+
+		/** Checks if the two image subresource ranges have any overlapping subresources. */
+		static bool rangeOverlaps(const VkImageSubresourceRange& a, const VkImageSubresourceRange& b);
 	};
 
 	/** @} */

+ 377 - 190
Source/BansheeVulkanRenderAPI/Source/BsVulkanCommandBuffer.cpp

@@ -285,12 +285,11 @@ namespace bs { namespace ct
 		UINT32 numColorAttachments = mFramebuffer->getNumColorAttachments();
 		for(UINT32 i = 0; i < numColorAttachments; i++)
 		{
-			VulkanImage* image = mFramebuffer->getColorAttachment(i).image;
+			const VulkanFramebufferAttachment& fbAttachment = mFramebuffer->getColorAttachment(i);
+			ImageSubresourceInfo& subresourceInfo = findSubresourceInfo(fbAttachment.image, fbAttachment.surface.arraySlice,
+																		fbAttachment.surface.mipLevel);
 
-			UINT32 imageInfoIdx = mImages[image];
-			ImageInfo& imageInfo = mImageInfos[imageInfoIdx];
-
-			bool readOnly = imageInfo.isShaderInput;
+			bool readOnly = subresourceInfo.isShaderInput;
 
 			if(readOnly)
 				readMask.set((RenderSurfaceMaskBits)(1 << i));
@@ -298,25 +297,21 @@ namespace bs { namespace ct
 
 		if(mFramebuffer->hasDepthAttachment())
 		{
-			VulkanImage* image = mFramebuffer->getDepthStencilAttachment().image;
+			const VulkanFramebufferAttachment& fbAttachment = mFramebuffer->getDepthStencilAttachment();
+			ImageSubresourceInfo& subresourceInfo = findSubresourceInfo(fbAttachment.image, fbAttachment.surface.arraySlice,
+																		fbAttachment.surface.mipLevel);
 
-			UINT32 imageInfoIdx = mImages[image];
-			ImageInfo& imageInfo = mImageInfos[imageInfoIdx];
-
-			bool readOnly = imageInfo.isShaderInput;
+			bool readOnly = subresourceInfo.isShaderInput;
 
 			if (readOnly)
 				readMask.set(RT_DEPTH);
 		}
 
 		// Reset flags that signal image usage (since those only matter for the render-pass' purposes)
-		for (auto& entry : mImages)
+		for (auto& entry : mSubresourceInfos)
 		{
-			UINT32 imageInfoIdx = entry.second;
-			ImageInfo& imageInfo = mImageInfos[imageInfoIdx];
-
-			imageInfo.isFBAttachment = false;
-			imageInfo.isShaderInput = false;
+			entry.isFBAttachment = false;
+			entry.isShaderInput = false;
 		}
 
 		VkRenderPassBeginInfo renderPassBeginInfo;
@@ -328,7 +323,7 @@ namespace bs { namespace ct
 		renderPassBeginInfo.renderArea.offset.y = 0;
 		renderPassBeginInfo.renderArea.extent.width = mRenderTargetWidth;
 		renderPassBeginInfo.renderArea.extent.height = mRenderTargetHeight;
-		renderPassBeginInfo.clearValueCount = mFramebuffer->getNumAttachments();
+		renderPassBeginInfo.clearValueCount = mFramebuffer->getNumClearEntries(mClearMask);
 		renderPassBeginInfo.pClearValues = mClearValues.data();
 
 		vkCmdBeginRenderPass(mCmdBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
@@ -353,14 +348,11 @@ namespace bs { namespace ct
 		// and reset read-only state.
 		// Note: It's okay reset these even those they might still be bound on the GPU, because these values only matter
 		// for state transitions.
-		for (auto& entry : mImages)
+		for (auto& entry : mSubresourceInfos)
 		{
-			UINT32 imageInfoIdx = entry.second;
-			ImageInfo& imageInfo = mImageInfos[imageInfoIdx];
-
-			imageInfo.isFBAttachment = false;
-			imageInfo.isShaderInput = false;
-			imageInfo.isReadOnly = true;
+			entry.isFBAttachment = false;
+			entry.isShaderInput = false;
+			entry.isReadOnly = true;
 		}
 
 		updateFinalLayouts();
@@ -448,6 +440,7 @@ namespace bs { namespace ct
 		}
 
 		// For images issue queue transitions, as above. Also issue layout transitions to their inital layouts.
+		Vector<VkImageMemoryBarrier>& localBarriers = mTransitionInfoTemp[mQueueFamily].imageBarriers;
 		for (auto& entry : mImages)
 		{
 			VulkanImage* resource = static_cast<VulkanImage*>(entry.first);
@@ -456,48 +449,82 @@ namespace bs { namespace ct
 			UINT32 currentQueueFamily = resource->getQueueFamily();
 			bool queueMismatch = resource->isExclusive() && currentQueueFamily != -1 && currentQueueFamily != mQueueFamily;
 
-			VkImageLayout currentLayout = resource->getLayout();
+			ImageSubresourceInfo* subresourceInfos = &mSubresourceInfos[imageInfo.subresourceInfoIdx];
 			if (queueMismatch)
 			{
 				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 = resource->getAccessFlags(currentLayout);
-				barrier.dstAccessMask = resource->getAccessFlags(currentLayout);
-				barrier.oldLayout = currentLayout;
-				barrier.newLayout = currentLayout;
-				barrier.image = resource->getHandle();
-				barrier.subresourceRange = imageInfo.range;
-				barrier.srcQueueFamilyIndex = currentQueueFamily;
-				barrier.dstQueueFamilyIndex = mQueueFamily;
+				for (UINT32 i = 0; i < imageInfo.numSubresourceInfos; i++)
+				{
+					ImageSubresourceInfo& subresourceInfo = subresourceInfos[i];
+
+					UINT32 startIdx = (UINT32)barriers.size();
+					resource->getBarriers(subresourceInfo.range, barriers);
+
+					for(UINT32 j = startIdx; j < (UINT32)barriers.size(); j++)
+					{
+						VkImageMemoryBarrier& barrier = barriers[j];
+
+						barrier.dstAccessMask = resource->getAccessFlags(barrier.oldLayout);
+						barrier.newLayout = barrier.oldLayout;
+						barrier.srcQueueFamilyIndex = currentQueueFamily;
+						barrier.dstQueueFamilyIndex = mQueueFamily;
+					}
+				}
 			}
 
-			VkImageLayout initialLayout = imageInfo.initialLayout;
-			if(currentLayout != initialLayout && initialLayout != VK_IMAGE_LAYOUT_UNDEFINED)
+			for (UINT32 i = 0; i < imageInfo.numSubresourceInfos; i++)
 			{
-				Vector<VkImageMemoryBarrier>& barriers = mTransitionInfoTemp[mQueueFamily].imageBarriers;
+				ImageSubresourceInfo& subresourceInfo = subresourceInfos[i];
 
+				VkImageLayout initialLayout = subresourceInfo.initialLayout;
 				if (initialLayout == VK_IMAGE_LAYOUT_UNDEFINED)
-					initialLayout = currentLayout;
+					continue;
 
-				barriers.push_back(VkImageMemoryBarrier());
-				VkImageMemoryBarrier& barrier = barriers.back();
-				barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
-				barrier.pNext = nullptr;
-				barrier.srcAccessMask = resource->getAccessFlags(currentLayout);
-				barrier.dstAccessMask = resource->getAccessFlags(initialLayout, imageInfo.isInitialReadOnly);
-				barrier.oldLayout = currentLayout;
-				barrier.newLayout = initialLayout;
-				barrier.image = resource->getHandle();
-				barrier.subresourceRange = imageInfo.range;
-				barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-				barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-			}
+				const VkImageSubresourceRange& range = subresourceInfo.range;
+				UINT32 mipEnd = range.baseMipLevel + range.levelCount;
+				UINT32 faceEnd = range.baseArrayLayer + range.layerCount;
+
+				bool layoutMismatch = false;
+				for (UINT32 mip = range.baseMipLevel; mip < mipEnd; mip++)
+				{
+					for (UINT32 face = range.baseArrayLayer; face < faceEnd; face++)
+					{
+						VulkanImageSubresource* subresource = resource->getSubresource(face, mip);
+						if(subresource->getLayout() != initialLayout)
+						{
+							layoutMismatch = true;
+							break;
+						}
+					}
 
-			resource->setLayout(imageInfo.finalLayout);
+					if (layoutMismatch)
+						break;
+				}
+
+				if(layoutMismatch)
+				{
+					UINT32 startIdx = (UINT32)localBarriers.size();
+					resource->getBarriers(subresourceInfo.range, localBarriers);
+
+					for (UINT32 j = startIdx; j < (UINT32)localBarriers.size(); j++)
+					{
+						VkImageMemoryBarrier& barrier = localBarriers[j];
+
+						barrier.dstAccessMask = resource->getAccessFlags(initialLayout, subresourceInfo.isInitialReadOnly);
+						barrier.newLayout = initialLayout;
+					}
+				}
+
+				for (UINT32 mip = range.baseMipLevel; mip < mipEnd; mip++)
+				{
+					for (UINT32 face = range.baseArrayLayer; face < faceEnd; face++)
+					{
+						VulkanImageSubresource* subresource = resource->getSubresource(face, mip);
+						subresource->setLayout(subresourceInfo.finalLayout);
+					}
+				}
+			}
 		}
 
 		for (auto& entry : mTransitionInfoTemp)
@@ -735,6 +762,7 @@ namespace bs { namespace ct
 		mImages.clear();
 		mBuffers.clear();
 		mImageInfos.clear();
+		mSubresourceInfos.clear();
 	}
 
 	void VulkanCmdBuffer::setRenderTarget(const SPtr<RenderTarget>& rt, bool readOnlyDepthStencil, 
@@ -793,13 +821,8 @@ namespace bs { namespace ct
 		}
 
 		// Reset flags that signal image usage
-		for (auto& entry : mImages)
-		{
-			UINT32 imageInfoIdx = entry.second;
-			ImageInfo& imageInfo = mImageInfos[imageInfoIdx];
-
-			imageInfo.isFBAttachment = false;
-		}
+		for (auto& entry : mSubresourceInfos)
+			entry.isFBAttachment = false;
 
 		setGpuParams(nullptr);
 
@@ -1147,12 +1170,11 @@ namespace bs { namespace ct
 		UINT32 numColorAttachments = mFramebuffer->getNumColorAttachments();
 		for (UINT32 i = 0; i < numColorAttachments; i++)
 		{
-			VulkanImage* image = mFramebuffer->getColorAttachment(i).image;
+			const VulkanFramebufferAttachment& fbAttachment = mFramebuffer->getColorAttachment(i);
+			ImageSubresourceInfo& subresourceInfo = findSubresourceInfo(fbAttachment.image, fbAttachment.surface.arraySlice,
+																		fbAttachment.surface.mipLevel);
 
-			UINT32 imageInfoIdx = mImages[image];
-			ImageInfo& imageInfo = mImageInfos[imageInfoIdx];
-
-			if (imageInfo.isShaderInput && !pipeline->isColorReadOnly(i))
+			if (subresourceInfo.isShaderInput && !pipeline->isColorReadOnly(i))
 			{
 				LOGWRN("Framebuffer attachment also used as a shader input, but color writes aren't disabled. This will"
 					" result in undefined behavior.");
@@ -1161,12 +1183,11 @@ namespace bs { namespace ct
 
 		if (mFramebuffer->hasDepthAttachment())
 		{
-			VulkanImage* image = mFramebuffer->getDepthStencilAttachment().image;
+			const VulkanFramebufferAttachment& fbAttachment = mFramebuffer->getDepthStencilAttachment();
+			ImageSubresourceInfo& subresourceInfo = findSubresourceInfo(fbAttachment.image, fbAttachment.surface.arraySlice,
+																		fbAttachment.surface.mipLevel);
 
-			UINT32 imageInfoIdx = mImages[image];
-			ImageInfo& imageInfo = mImageInfos[imageInfoIdx];
-
-			if (imageInfo.isShaderInput && !pipeline->isDepthStencilReadOnly())
+			if (subresourceInfo.isShaderInput && !pipeline->isDepthStencilReadOnly())
 			{
 				LOGWRN("Framebuffer attachment also used as a shader input, but depth/stencil writes aren't disabled. "
 					"This will result in undefined behavior.");
@@ -1253,22 +1274,31 @@ namespace bs { namespace ct
 	{
 		auto createLayoutTransitionBarrier = [&](VulkanImage* image, ImageInfo& imageInfo)
 		{
-			mLayoutTransitionBarriersTemp.push_back(VkImageMemoryBarrier());
-			VkImageMemoryBarrier& barrier = mLayoutTransitionBarriersTemp.back();
-			barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
-			barrier.pNext = nullptr;
-			barrier.srcAccessMask = image->getAccessFlags(imageInfo.currentLayout);
-			barrier.dstAccessMask = image->getAccessFlags(imageInfo.requiredLayout, imageInfo.isReadOnly);
-			barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-			barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-			barrier.oldLayout = imageInfo.currentLayout;
-			barrier.newLayout = imageInfo.requiredLayout;
-			barrier.image = image->getHandle();
-			barrier.subresourceRange = imageInfo.range;
-
-			imageInfo.currentLayout = imageInfo.requiredLayout;
-			imageInfo.isReadOnly = true;
-			imageInfo.hasTransitioned = true;
+			ImageSubresourceInfo* subresourceInfos = &mSubresourceInfos[imageInfo.subresourceInfoIdx];
+			for (UINT32 i = 0; i < imageInfo.numSubresourceInfos; i++)
+			{
+				ImageSubresourceInfo& subresourceInfo = subresourceInfos[i];
+
+				if (!subresourceInfo.hasTransitioned || subresourceInfo.currentLayout == subresourceInfo.requiredLayout)
+					continue;
+
+				mLayoutTransitionBarriersTemp.push_back(VkImageMemoryBarrier());
+				VkImageMemoryBarrier& barrier = mLayoutTransitionBarriersTemp.back();
+				barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+				barrier.pNext = nullptr;
+				barrier.srcAccessMask = image->getAccessFlags(subresourceInfo.currentLayout);
+				barrier.dstAccessMask = image->getAccessFlags(subresourceInfo.requiredLayout, subresourceInfo.isReadOnly);
+				barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+				barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+				barrier.oldLayout = subresourceInfo.currentLayout;
+				barrier.newLayout = subresourceInfo.requiredLayout;
+				barrier.image = image->getHandle();
+				barrier.subresourceRange = subresourceInfo.range;
+
+				subresourceInfo.currentLayout = subresourceInfo.requiredLayout;
+				subresourceInfo.isReadOnly = true;
+				subresourceInfo.hasTransitioned = true;
+			}
 		};
 
 		// Note: These layout transitions will contain transitions for offscreen framebuffer attachments (while they 
@@ -1301,26 +1331,24 @@ namespace bs { namespace ct
 		UINT32 numColorAttachments = mFramebuffer->getNumColorAttachments();
 		for (UINT32 i = 0; i < numColorAttachments; i++)
 		{
-			const VulkanFramebufferAttachment& attachment = mFramebuffer->getColorAttachment(i);
-
-			UINT32 imageInfoIdx = mImages[attachment.image];
-			ImageInfo& imageInfo = mImageInfos[imageInfoIdx];
+			const VulkanFramebufferAttachment& fbAttachment = mFramebuffer->getColorAttachment(i);
+			ImageSubresourceInfo& subresourceInfo = findSubresourceInfo(fbAttachment.image, fbAttachment.surface.arraySlice,
+																		fbAttachment.surface.mipLevel);
 
-			imageInfo.currentLayout = imageInfo.finalLayout;
-			imageInfo.requiredLayout = imageInfo.finalLayout;
-			imageInfo.hasTransitioned = true;
+			subresourceInfo.currentLayout = subresourceInfo.finalLayout;
+			subresourceInfo.requiredLayout = subresourceInfo.finalLayout;
+			subresourceInfo.hasTransitioned = true;
 		}
 
 		if (mFramebuffer->hasDepthAttachment())
 		{
-			const VulkanFramebufferAttachment& attachment = mFramebuffer->getDepthStencilAttachment();
+			const VulkanFramebufferAttachment& fbAttachment = mFramebuffer->getDepthStencilAttachment();
+			ImageSubresourceInfo& subresourceInfo = findSubresourceInfo(fbAttachment.image, fbAttachment.surface.arraySlice,
+																		fbAttachment.surface.mipLevel);
 
-			UINT32 imageInfoIdx = mImages[attachment.image];
-			ImageInfo& imageInfo = mImageInfos[imageInfoIdx];
-
-			imageInfo.currentLayout = imageInfo.finalLayout;
-			imageInfo.requiredLayout = imageInfo.finalLayout;
-			imageInfo.hasTransitioned = true;
+			subresourceInfo.currentLayout = subresourceInfo.finalLayout;
+			subresourceInfo.requiredLayout = subresourceInfo.finalLayout;
+			subresourceInfo.hasTransitioned = true;
 		}
 	}
 
@@ -1339,7 +1367,7 @@ namespace bs { namespace ct
 		renderPassBeginInfo.renderArea.offset.y = mClearArea.y;
 		renderPassBeginInfo.renderArea.extent.width = mClearArea.width;
 		renderPassBeginInfo.renderArea.extent.height = mClearArea.height;
-		renderPassBeginInfo.clearValueCount = mFramebuffer->getNumAttachments();
+		renderPassBeginInfo.clearValueCount = mFramebuffer->getNumClearEntries(mClearMask);
 		renderPassBeginInfo.pClearValues = mClearValues.data();
 
 		vkCmdBeginRenderPass(mCmdBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
@@ -1495,25 +1523,34 @@ namespace bs { namespace ct
 		}
 	}
 
-	void VulkanCmdBuffer::registerResource(VulkanImage* res, VulkanUseFlags flags)
+	void VulkanCmdBuffer::registerResource(VulkanImage* res, const VkImageSubresourceRange& range, VulkanUseFlags flags)
 	{
 		VkImageLayout layout = res->getOptimalLayout();
 
-		registerResource(res, VK_IMAGE_LAYOUT_UNDEFINED, layout, flags, false);
+		registerResource(res, range, VK_IMAGE_LAYOUT_UNDEFINED, layout, flags, false);
 	}
 
-	void VulkanCmdBuffer::registerResource(VulkanImage* res, VkImageLayout newLayout, VkImageLayout finalLayout, 
-		VulkanUseFlags flags, bool isFBAttachment)
+	void VulkanCmdBuffer::registerResource(VulkanImage* res, const VkImageSubresourceRange& range, VkImageLayout newLayout, 
+										   VkImageLayout finalLayout, VulkanUseFlags flags, bool isFBAttachment)
 	{
-		// Note: I currently always perform pipeline barriers (layout transitions and similar), over the entire image.
-		//       In the case of render and storage images, the case is often that only a specific subresource requires
-		//       it. However this makes grouping and tracking of current image layouts much more difficult.
-		//       If this is ever requires we'll need to track image layout per-subresource instead per-image, and we
-		//       might also need a smart way to group layout transitions for multiple sub-resources on the same image.
-
-		VkImageSubresourceRange range = res->getRange();
 		UINT32 nextImageInfoIdx = (UINT32)mImageInfos.size();
 
+		auto registerSubresourceInfo = [&](const VkImageSubresourceRange& subresourceRange)
+		{
+			mSubresourceInfos.push_back(ImageSubresourceInfo());
+			ImageSubresourceInfo& subresourceInfo = mSubresourceInfos.back();
+			subresourceInfo.currentLayout = newLayout;
+			subresourceInfo.initialLayout = newLayout;
+			subresourceInfo.requiredLayout = newLayout;
+			subresourceInfo.finalLayout = finalLayout;
+			subresourceInfo.range = subresourceRange;
+			subresourceInfo.isFBAttachment = isFBAttachment;
+			subresourceInfo.isShaderInput = !isFBAttachment;
+			subresourceInfo.hasTransitioned = false;
+			subresourceInfo.isReadOnly = !flags.isSet(VulkanUseFlag::Write);
+			subresourceInfo.isInitialReadOnly = subresourceInfo.isReadOnly;
+		};
+
 		auto insertResult = mImages.insert(std::make_pair(res, nextImageInfoIdx));
 		if (insertResult.second) // New element
 		{
@@ -1521,20 +1558,14 @@ namespace bs { namespace ct
 			mImageInfos.push_back(ImageInfo());
 
 			ImageInfo& imageInfo = mImageInfos[imageInfoIdx];
-			imageInfo.currentLayout = newLayout;
-			imageInfo.initialLayout = newLayout;
-			imageInfo.requiredLayout = newLayout;
-			imageInfo.finalLayout = finalLayout;
-			imageInfo.range = range;
-			imageInfo.isFBAttachment = isFBAttachment;
-			imageInfo.isShaderInput = !isFBAttachment;
-			imageInfo.hasTransitioned = false;
-			imageInfo.isReadOnly = !flags.isSet(VulkanUseFlag::Write);
-			imageInfo.isInitialReadOnly = imageInfo.isReadOnly;
+			imageInfo.subresourceInfoIdx = (UINT32)mSubresourceInfos.size();
+			imageInfo.numSubresourceInfos = 1;
 
 			imageInfo.useHandle.used = false;
 			imageInfo.useHandle.flags = flags;
 
+			registerSubresourceInfo(range);
+
 			res->notifyBound();
 		}
 		else // Existing element
@@ -1545,81 +1576,132 @@ namespace bs { namespace ct
 			assert(!imageInfo.useHandle.used);
 			imageInfo.useHandle.flags |= flags;
 
-			imageInfo.isReadOnly &= !flags.isSet(VulkanUseFlag::Write);
-
-			// New layout is valid, check for transitions (UNDEFINED signifies the caller doesn't want a layout transition)
-			if (newLayout != VK_IMAGE_LAYOUT_UNDEFINED)
+			// See if there is an overlap between existing ranges and the new range. And if so break them up accordingly.
+			//// First test for the simplest and most common case (same range or no overlap) to avoid more complex
+			//// computations.
+			ImageSubresourceInfo* subresources = &mSubresourceInfos[imageInfo.subresourceInfoIdx];
+			
+			bool foundRange = false;
+			bool foundOverlap = false;
+			for(UINT32 i = 0; i < imageInfo.numSubresourceInfos; i++)
 			{
-				// If layout transition was requested by framebuffer bind, respect it because render-pass will only accept a
-				// specific layout (in certain cases), and we have no choice.
-				// In the case when a FB attachment is also bound for shader reads, this will override the layout required for
-				// shader read (GENERAL or DEPTH_READ_ONLY), but that is fine because those transitions are handled
-				// automatically by render-pass layout transitions.
-				// Any other texture (non FB attachment) will only even be bound in a single layout and we can keep the one it
-				// was originally registered with.
-				if (isFBAttachment)
-					imageInfo.requiredLayout = newLayout;
-				else if(!imageInfo.isFBAttachment) // Layout transition is not being done on a FB image
+				if(VulkanUtility::rangeOverlaps(subresources[i].range, range))
 				{
-					// Check if the image had a layout previously assigned, and if so check if multiple different layouts
-					// were requested. In that case we wish to transfer the image to GENERAL layout.
-
-					bool firstUseInRenderPass = !imageInfo.isShaderInput && !imageInfo.isFBAttachment;
-					if (firstUseInRenderPass || imageInfo.requiredLayout == VK_IMAGE_LAYOUT_UNDEFINED)
-						imageInfo.requiredLayout = newLayout;
-					else if (imageInfo.requiredLayout != newLayout)
-						imageInfo.requiredLayout = VK_IMAGE_LAYOUT_GENERAL;
-				}
-			}
+					if (subresources[i].range.layerCount == range.layerCount &&
+						subresources[i].range.levelCount == range.levelCount &&
+						subresources[i].range.baseArrayLayer == range.baseArrayLayer &&
+						subresources[i].range.baseMipLevel == range.baseMipLevel)
+					{
+						// Just update existing range
+						bool requiresReadOnlyFB = updateSubresourceInfo(res, imageInfoIdx, subresources[0], newLayout,
+																		finalLayout, flags, isFBAttachment);
 
-			// If attached to FB, then the final layout is set by the FB (provided as layout param here), otherwise its
-			// the same as required layout
-			if(!isFBAttachment && !imageInfo.isFBAttachment)
-				imageInfo.finalLayout = imageInfo.requiredLayout;
-			else
-			{
-				if (isFBAttachment)
-					imageInfo.finalLayout = finalLayout;
-			}
+						// If we need to switch frame-buffers, end current render pass
+						if (requiresReadOnlyFB && isInRenderPass())
+							endRenderPass();
 
-			// If we haven't done a layout transition yet, we can just overwrite the previously written values, and the
-			// transition will be handled as the first thing in submit(), otherwise we queue a non-initial transition
-			// below.
-			if (!imageInfo.hasTransitioned)
-			{
-				imageInfo.initialLayout = imageInfo.requiredLayout;
-				imageInfo.currentLayout = imageInfo.requiredLayout;
-				imageInfo.isInitialReadOnly = imageInfo.isReadOnly;
-			}
-			else
-			{
-				if (imageInfo.currentLayout != imageInfo.requiredLayout)
-					mQueuedLayoutTransitions[res] = imageInfoIdx;
-			}
+						foundRange = true;
+						break;
+					}
 
-			// If a FB attachment was just bound as a shader input, we might need to restart the render pass with a FB
-			// attachment that supports read-only attachments using the GENERAL layout
-			bool requiresReadOnlyFB = false;
-			if (isFBAttachment)
-			{
-				if (!imageInfo.isFBAttachment)
-				{
-					imageInfo.isFBAttachment = true;
-					requiresReadOnlyFB = imageInfo.isShaderInput;
+					foundOverlap = true;
+					break;
 				}
 			}
-			else
+
+			//// We'll need to update subresource ranges or add new ones. The hope is that this code is trigger VERY rarely
+			//// (for just a few specific textures per frame).
+			if (!foundRange)
 			{
-				if (!imageInfo.isShaderInput)
+				std::array<VkImageSubresourceRange, 5> tempCutRanges;
+
+				bs_frame_mark();
 				{
-					imageInfo.isShaderInput = true;
-					requiresReadOnlyFB = imageInfo.isFBAttachment;
+					// We orphan previously allocated memory (we reset it after submit() anyway)
+					UINT32 newSubresourceIdx = (UINT32)mSubresourceInfos.size();
+
+					FrameVector<UINT32> cutOverlappingRanges;
+					for (UINT32 i = 0; i < imageInfo.numSubresourceInfos; i++)
+					{
+						UINT32 subresourceIdx = imageInfo.subresourceInfoIdx + i;
+						ImageSubresourceInfo& subresource = mSubresourceInfos[subresourceIdx];
+
+						if (!VulkanUtility::rangeOverlaps(subresource.range, range))
+						{
+							// Just copy as is
+							mSubresourceInfos.push_back(subresource);
+						}
+						else // Need to cut
+						{
+							UINT32 numCutRanges;
+							VulkanUtility::cutRange(subresource.range, range, tempCutRanges, numCutRanges);
+
+							for(UINT32 j = 0; j < numCutRanges; j++)
+							{
+								// Create a copy of the original subresource with the new range
+								ImageSubresourceInfo newInfo = subresource;
+								newInfo.range = tempCutRanges[j];
+
+								if(VulkanUtility::rangeOverlaps(tempCutRanges[j], range))
+								{
+									// Update overlapping sub-resource range with new data from this range
+									updateSubresourceInfo(res, imageInfoIdx, newInfo, newLayout, finalLayout, flags, 
+														  isFBAttachment);
+
+									// Keep track of the overlapping ranges for later
+									cutOverlappingRanges.push_back((UINT32)mSubresourceInfos.size());
+								}
+
+								mSubresourceInfos.push_back(newInfo);
+							}
+						}
+					}
+
+					// Our range doesn't overlap with any existing ranges, so just add it
+					if(cutOverlappingRanges.size() == 0)
+					{
+						registerSubresourceInfo(range);
+					}
+					else // Search if overlapping ranges fully cover the requested range, and insert non-covered regions
+					{
+						FrameQueue<VkImageSubresourceRange> sourceRanges;
+						sourceRanges.push(range);
+
+						for(auto& entry : cutOverlappingRanges)
+						{
+							VkImageSubresourceRange& overlappingRange = mSubresourceInfos[entry].range;
+
+							UINT32 numSourceRanges = (UINT32)sourceRanges.size();
+							for(UINT32 i = 0; i < numSourceRanges; i++)
+							{
+								VkImageSubresourceRange sourceRange = sourceRanges.front();
+								sourceRanges.pop();
+
+								UINT32 numCutRanges;
+								VulkanUtility::cutRange(sourceRange, overlappingRange, tempCutRanges, numCutRanges);
+
+								for(UINT32 j = 0; j < numCutRanges; j++)
+								{
+									// We only care about ranges outside of the ones we already covered
+									if(!VulkanUtility::rangeOverlaps(tempCutRanges[j], overlappingRange))
+										sourceRanges.push(tempCutRanges[j]);
+								}
+							}
+						}
+
+						// Any remaining range hasn't been covered yet
+						while(!sourceRanges.empty())
+						{
+							registerSubresourceInfo(sourceRanges.front());
+							sourceRanges.pop();
+						}
+					}
+
+					imageInfo.subresourceInfoIdx = newSubresourceIdx;
+					imageInfo.numSubresourceInfos = (UINT32)mSubresourceInfos.size() - newSubresourceIdx;
 				}
+				bs_frame_clear();
 			}
-
-			// If we need to switch frame-buffers, end current render pass
-			if (requiresReadOnlyFB && isInRenderPass())
-				endRenderPass();
 		}
 
 		// Register any sub-resources
@@ -1690,7 +1772,8 @@ namespace bs { namespace ct
 			else
 				layout = VK_IMAGE_LAYOUT_UNDEFINED;
 
-			registerResource(attachment.image, layout, attachment.finalLayout, VulkanUseFlag::Write, true);
+			VkImageSubresourceRange range = attachment.image->getRange(attachment.surface);
+			registerResource(attachment.image, range, layout, attachment.finalLayout, VulkanUseFlag::Write, true);
 		}
 
 		if(res->hasDepthAttachment())
@@ -1704,10 +1787,114 @@ namespace bs { namespace ct
 			else
 				layout = VK_IMAGE_LAYOUT_UNDEFINED;
 
-			registerResource(attachment.image, layout, attachment.finalLayout, VulkanUseFlag::Write, true);
+			VkImageSubresourceRange range = attachment.image->getRange(attachment.surface);
+			registerResource(attachment.image, range, layout, attachment.finalLayout, VulkanUseFlag::Write, true);
 		}
 	}
 
+	bool VulkanCmdBuffer::updateSubresourceInfo(VulkanImage* image, UINT32 imageInfoIdx, 
+			ImageSubresourceInfo& subresourceInfo, VkImageLayout newLayout, VkImageLayout finalLayout, VulkanUseFlags flags, 
+			bool isFBAttachment)
+	{
+		subresourceInfo.isReadOnly &= !flags.isSet(VulkanUseFlag::Write);
+
+		// New layout is valid, check for transitions (UNDEFINED signifies the caller doesn't want a layout transition)
+		if (newLayout != VK_IMAGE_LAYOUT_UNDEFINED)
+		{
+			// If layout transition was requested by framebuffer bind, respect it because render-pass will only accept a
+			// specific layout (in certain cases), and we have no choice.
+			// In the case when a FB attachment is also bound for shader reads, this will override the layout required for
+			// shader read (GENERAL or DEPTH_READ_ONLY), but that is fine because those transitions are handled
+			// automatically by render-pass layout transitions.
+			// Any other texture (non FB attachment) will only even be bound in a single layout and we can keep the one it
+			// was originally registered with.
+			if (isFBAttachment)
+				subresourceInfo.requiredLayout = newLayout;
+			else if (!subresourceInfo.isFBAttachment) // Layout transition is not being done on a FB image
+			{
+				// Check if the image had a layout previously assigned, and if so check if multiple different layouts
+				// were requested. In that case we wish to transfer the image to GENERAL layout.
+
+				bool firstUseInRenderPass = !subresourceInfo.isShaderInput && !subresourceInfo.isFBAttachment;
+				if (firstUseInRenderPass || subresourceInfo.requiredLayout == VK_IMAGE_LAYOUT_UNDEFINED)
+					subresourceInfo.requiredLayout = newLayout;
+				else if (subresourceInfo.requiredLayout != newLayout)
+					subresourceInfo.requiredLayout = VK_IMAGE_LAYOUT_GENERAL;
+			}
+		}
+
+		// If attached to FB, then the final layout is set by the FB (provided as layout param here), otherwise its
+		// the same as required layout
+		if (!isFBAttachment && !subresourceInfo.isFBAttachment)
+			subresourceInfo.finalLayout = subresourceInfo.requiredLayout;
+		else
+		{
+			if (isFBAttachment)
+				subresourceInfo.finalLayout = finalLayout;
+		}
+
+		// If we haven't done a layout transition yet, we can just overwrite the previously written values, and the
+		// transition will be handled as the first thing in submit(), otherwise we queue a non-initial transition
+		// below.
+		if (!subresourceInfo.hasTransitioned)
+		{
+			subresourceInfo.initialLayout = subresourceInfo.requiredLayout;
+			subresourceInfo.currentLayout = subresourceInfo.requiredLayout;
+			subresourceInfo.isInitialReadOnly = subresourceInfo.isReadOnly;
+		}
+		else
+		{
+			if (subresourceInfo.currentLayout != subresourceInfo.requiredLayout)
+				mQueuedLayoutTransitions[image] = imageInfoIdx;
+		}
+
+		// If a FB attachment was just bound as a shader input, we might need to restart the render pass with a FB
+		// attachment that supports read-only attachments using the GENERAL layout
+		bool requiresReadOnlyFB = false;
+		if (isFBAttachment)
+		{
+			if (!subresourceInfo.isFBAttachment)
+			{
+				subresourceInfo.isFBAttachment = true;
+				requiresReadOnlyFB = subresourceInfo.isShaderInput;
+			}
+		}
+		else
+		{
+			if (!subresourceInfo.isShaderInput)
+			{
+				subresourceInfo.isShaderInput = true;
+				requiresReadOnlyFB = subresourceInfo.isFBAttachment;
+			}
+		}
+
+		// If we need to switch frame-buffers, end current render pass
+		if (requiresReadOnlyFB && isInRenderPass())
+			endRenderPass();
+
+		return requiresReadOnlyFB;
+	}
+
+	VulkanCmdBuffer::ImageSubresourceInfo& VulkanCmdBuffer::findSubresourceInfo(VulkanImage* image, UINT32 face, UINT32 mip)
+	{
+		UINT32 imageInfoIdx = mImages[image];
+		ImageInfo& imageInfo = mImageInfos[imageInfoIdx];
+
+		ImageSubresourceInfo* subresourceInfos = &mSubresourceInfos[imageInfo.subresourceInfoIdx];
+		for(UINT32 i = 0; i < imageInfo.numSubresourceInfos; i++)
+		{
+			ImageSubresourceInfo& entry = subresourceInfos[i];
+			if(face >= entry.range.baseArrayLayer && face < (entry.range.baseArrayLayer + entry.range.layerCount) &&
+			   mip >= entry.range.baseMipLevel && mip < (entry.range.baseMipLevel + entry.range.levelCount))
+			{
+				return entry;
+			}
+		}
+
+		assert(false); // Caller should ensure the subresource actually exists, so this shouldn't happen
+		return subresourceInfos[0];
+	}
+
 	VulkanCommandBuffer::VulkanCommandBuffer(VulkanDevice& device, GpuQueueType type, UINT32 deviceIdx,
 		UINT32 queueIdx, bool secondary)
 		: CommandBuffer(type, deviceIdx, queueIdx, secondary), mBuffer(nullptr)

+ 42 - 0
Source/BansheeVulkanRenderAPI/Source/BsVulkanCommandBufferManager.cpp

@@ -5,6 +5,7 @@
 #include "BsVulkanRenderAPI.h"
 #include "BsVulkanDevice.h"
 #include "BsVulkanQueue.h"
+#include "BsVulkanTexture.h"
 
 namespace bs { namespace ct
 {
@@ -87,6 +88,47 @@ namespace bs { namespace ct
 							 1, &barrier);
 	}
 
+	void VulkanTransferBuffer::setLayout(VulkanImage* image, const VkImageSubresourceRange& range, 
+										 VkAccessFlags newAccessMask, VkImageLayout newLayout)
+	{
+		image->getBarriers(range, mBarriersTemp);
+
+		if (mBarriersTemp.size() == 0)
+			return;
+
+		INT32 count = (INT32)mBarriersTemp.size();
+		for(INT32 i = 0; i < count; i++)
+		{
+			VkImageMemoryBarrier& barrier = mBarriersTemp[i];
+
+			// Remove barriers that don't signify a layout change
+			if(barrier.oldLayout == newLayout)
+			{
+				if(i < (count - 1))
+					std::swap(mBarriersTemp[i], mBarriersTemp[count - 1]);
+
+				mBarriersTemp.erase(mBarriersTemp.begin() + count - 1);
+				count--;
+				i--;
+			}
+		}
+
+		for(auto& entry : mBarriersTemp)
+		{
+			entry.dstAccessMask = newAccessMask;
+			entry.newLayout = newLayout;
+		}
+
+		vkCmdPipelineBarrier(mCB->getHandle(),
+							 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+							 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+							 0, 0, nullptr,
+							 0, nullptr,
+							 (UINT32)mBarriersTemp.size(), mBarriersTemp.data());
+
+		mBarriersTemp.clear();		
+	}
+
 	void VulkanTransferBuffer::flush(bool wait)
 	{
 		if (mCB == nullptr)

+ 35 - 3
Source/BansheeVulkanRenderAPI/Source/BsVulkanFramebuffer.cpp

@@ -64,17 +64,22 @@ namespace bs { namespace ct
 			mColorAttachments[attachmentIdx].image = desc.color[i].image;
 			mColorAttachments[attachmentIdx].finalLayout = attachmentDesc.finalLayout;
 			mColorAttachments[attachmentIdx].index = i;
+			mColorAttachments[attachmentIdx].surface = desc.color[i].surface;
 
 			VkAttachmentReference& ref = mColorReferences[attachmentIdx];
 			ref.attachment = attachmentIdx;
 			ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
 
-			mAttachmentViews[attachmentIdx] = desc.color[i].view;
+			if (desc.color[i].surface.numMipLevels == 0)
+				mAttachmentViews[attachmentIdx] = desc.color[i].image->getView(true);
+			else
+				mAttachmentViews[attachmentIdx] = desc.color[i].image->getView(desc.color[i].surface, true);
+
 			attachmentIdx++;
 		}
 
 		mNumColorAttachments = attachmentIdx;
-		mHasDepth = desc.depth.view != VK_NULL_HANDLE;
+		mHasDepth = desc.depth.image != nullptr;
 
 		if (mHasDepth)
 		{
@@ -93,12 +98,17 @@ namespace bs { namespace ct
 			mDepthStencilAttachment.image = desc.depth.image;
 			mDepthStencilAttachment.finalLayout = attachmentDesc.finalLayout;
 			mDepthStencilAttachment.index = 0;
+			mDepthStencilAttachment.surface = desc.depth.surface;
 
 			VkAttachmentReference& ref = mDepthReference;
 			ref.attachment = attachmentIdx;
 			ref.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
 
-			mAttachmentViews[attachmentIdx] = desc.depth.view;
+			if (desc.depth.surface.numMipLevels == 0)
+				mAttachmentViews[attachmentIdx] = desc.depth.image->getView(true);
+			else
+				mAttachmentViews[attachmentIdx] = desc.depth.image->getView(desc.depth.surface, true);
+
 			attachmentIdx++;
 		}
 
@@ -292,4 +302,26 @@ namespace bs { namespace ct
 
 		return newVariant.framebuffer;
 	}
+
+	UINT32 VulkanFramebuffer::getNumClearEntries(ClearMask clearMask) const
+	{
+		if (clearMask == CLEAR_NONE)
+			return 0;
+		else if (clearMask == CLEAR_ALL)
+			return getNumAttachments();
+		else if (((UINT32)clearMask & (UINT32)(CLEAR_DEPTH | CLEAR_STENCIL)) != 0 && hasDepthAttachment())
+			return getNumAttachments();
+
+		UINT32 numAttachments = 0;
+		for(INT32 i = BS_MAX_MULTIPLE_RENDER_TARGETS - 1; i >= 0; i--)
+		{
+			if(((1 << i) & (UINT32)clearMask) != 0)
+			{
+				numAttachments = i + 1;
+				break;
+			}
+		}
+
+		return std::min(numAttachments, getNumColorAttachments());
+	}
 }}

+ 8 - 6
Source/BansheeVulkanRenderAPI/Source/BsVulkanGpuParams.cpp

@@ -643,9 +643,12 @@ namespace bs { namespace ct
 			if (resource == nullptr)
 				continue;
 
+			const TextureSurface& surface = mLoadStoreTextureData[i].surface;
+			VkImageSubresourceRange range = resource->getRange(surface);
+
 			// Register with command buffer
 			VulkanUseFlags useFlags = VulkanUseFlag::Read | VulkanUseFlag::Write;
-			buffer.registerResource(resource, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, useFlags);
+			buffer.registerResource(resource, range, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, useFlags);
 
 			// Check if internal resource changed from what was previously bound in the descriptor set
 			assert(perDeviceData.storageImages[i] != VK_NULL_HANDLE);
@@ -659,8 +662,6 @@ namespace bs { namespace ct
 				mParamInfo->getSetSlot(GpuPipelineParamInfo::ParamType::LoadStoreTexture, i, set, slot);
 
 				UINT32 bindingIdx = vkParamInfo.getBindingIdx(set, slot);
-
-				const TextureSurface& surface = mLoadStoreTextureData[i].surface;
 				perDeviceData.perSetData[set].writeInfos[bindingIdx].image.imageView = resource->getView(surface, false);;
 
 				mSetsDirty[set] = true;
@@ -688,7 +689,10 @@ namespace bs { namespace ct
 			else
 				layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
 
-			buffer.registerResource(resource, layout, layout, VulkanUseFlag::Read);
+			const TextureSurface& surface = mSampledTextureData[i].surface;
+			VkImageSubresourceRange range = resource->getRange(surface);
+
+			buffer.registerResource(resource, range, layout, layout, VulkanUseFlag::Read);
 
 			// Check if internal resource changed from what was previously bound in the descriptor set
 			assert(perDeviceData.sampledImages[i] != VK_NULL_HANDLE);
@@ -702,8 +706,6 @@ namespace bs { namespace ct
 				mParamInfo->getSetSlot(GpuPipelineParamInfo::ParamType::Texture, i, set, slot);
 
 				UINT32 bindingIdx = vkParamInfo.getBindingIdx(set, slot);
-
-				const TextureSurface& surface = mSampledTextureData[i].surface;
 				perDeviceData.perSetData[set].writeInfos[bindingIdx].image.imageView = resource->getView(surface, false);
 
 				mSetsDirty[set] = true;

+ 2 - 2
Source/BansheeVulkanRenderAPI/Source/BsVulkanRenderTexture.cpp

@@ -78,7 +78,7 @@ namespace bs
 			}
 
 			fbDesc.color[i].image = image;
-			fbDesc.color[i].view = image->getView(surface, true);
+			fbDesc.color[i].surface = surface;
 			fbDesc.color[i].format = VulkanUtility::getPixelFormat(texture->getProperties().getFormat(),
 																   texture->getProperties().isHardwareGammaEnabled());
 		}
@@ -118,7 +118,7 @@ namespace bs
 				}
 
 				fbDesc.depth.image = image;
-				fbDesc.depth.view = image->getView(surface, true);
+				fbDesc.depth.surface = surface;
 				fbDesc.depth.format = VulkanUtility::getPixelFormat(texture->getProperties().getFormat(),
 																	texture->getProperties().isHardwareGammaEnabled());
 				fbDesc.depth.baseLayer = view->getFirstArraySlice();

+ 2 - 7
Source/BansheeVulkanRenderAPI/Source/BsVulkanSwapChain.cpp

@@ -145,7 +145,6 @@ namespace bs { namespace ct
 			mSurfaces[i].acquired = false;
 			mSurfaces[i].needsWait = false;
 			mSurfaces[i].image = resManager.create<VulkanImage>(imageDesc, false);
-			mSurfaces[i].view = mSurfaces[i].image->getView(true);
 			mSurfaces[i].sync = resManager.create<VulkanSemaphore>();
 		}
 
@@ -181,13 +180,9 @@ namespace bs { namespace ct
 			imageDesc.memory = mDevice->allocateMemory(depthStencilImage, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
 
 			mDepthStencilImage = resManager.create<VulkanImage>(imageDesc, true);
-			mDepthStencilView = mDepthStencilImage->getView(true);
 		}
 		else
-		{
 			mDepthStencilImage = nullptr;
-			mDepthStencilView = VK_NULL_HANDLE;
-		}
 
 		// Create a framebuffer for each swap chain buffer
 		UINT32 numFramebuffers = (UINT32)mSurfaces.size();
@@ -201,11 +196,11 @@ namespace bs { namespace ct
 			desc.offscreen = false;
 			desc.color[0].format = colorFormat;
 			desc.color[0].image = mSurfaces[i].image;
-			desc.color[0].view = mSurfaces[i].view;
+			desc.color[0].surface = TextureSurface::COMPLETE;
 			desc.color[0].baseLayer = 0;
 			desc.depth.format = depthFormat;
 			desc.depth.image = mDepthStencilImage;
-			desc.depth.view = mDepthStencilView;
+			desc.depth.surface = TextureSurface::COMPLETE;
 			desc.depth.baseLayer = 0;
 
 			mSurfaces[i].framebuffer = resManager.create<VulkanFramebuffer>(desc);

+ 191 - 35
Source/BansheeVulkanRenderAPI/Source/BsVulkanTexture.cpp

@@ -44,9 +44,8 @@ namespace bs { namespace ct
 	{ }
 
 	VulkanImage::VulkanImage(VulkanResourceManager* owner, const VULKAN_IMAGE_DESC& desc, bool ownsImage)
-		: VulkanResource(owner, false), mImage(desc.image), mMemory(desc.memory), mLayout(desc.layout)
-		, mFramebufferMainView(VK_NULL_HANDLE), mOwnsImage(ownsImage), mNumFaces(desc.numFaces)
-		, mNumMipLevels(desc.numMipLevels), mUsage(desc.usage)
+		: VulkanResource(owner, false), mImage(desc.image), mMemory(desc.memory), mFramebufferMainView(VK_NULL_HANDLE)
+		, mOwnsImage(ownsImage), mNumFaces(desc.numFaces), mNumMipLevels(desc.numMipLevels), mUsage(desc.usage)
 	{
 		mImageViewCI.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
 		mImageViewCI.pNext = nullptr;
@@ -106,7 +105,7 @@ namespace bs { namespace ct
 		UINT32 numSubresources = mNumFaces * mNumMipLevels;
 		mSubresources = (VulkanImageSubresource**)bs_alloc(sizeof(VulkanImageSubresource*) * numSubresources);
 		for (UINT32 i = 0; i < numSubresources; i++)
-			mSubresources[i] = owner->create<VulkanImageSubresource>();
+			mSubresources[i] = owner->create<VulkanImageSubresource>(desc.layout);
 	}
 
 	VulkanImage::~VulkanImage()
@@ -267,9 +266,21 @@ namespace bs { namespace ct
 		return range;
 	}
 
+	VkImageSubresourceRange VulkanImage::getRange(const TextureSurface& surface) const
+	{
+		VkImageSubresourceRange range;
+		range.baseArrayLayer = surface.arraySlice;
+		range.layerCount = surface.numArraySlices == 0 ? mNumFaces : surface.numArraySlices;
+		range.baseMipLevel = surface.mipLevel;
+		range.levelCount = surface.numMipLevels == 0 ? mNumMipLevels : surface.numMipLevels;
+		range.aspectMask = getAspectFlags();
+
+		return range;
+	}
+
 	VulkanImageSubresource* VulkanImage::getSubresource(UINT32 face, UINT32 mipLevel)
 	{
-		return mSubresources[face * mNumMipLevels + mipLevel];
+		return mSubresources[mipLevel * mNumFaces + face];
 	}
 
 	void VulkanImage::map(UINT32 face, UINT32 mipLevel, PixelData& output) const
@@ -390,8 +401,160 @@ namespace bs { namespace ct
 		return accessFlags;
 	}
 
-	VulkanImageSubresource::VulkanImageSubresource(VulkanResourceManager* owner)
-		:VulkanResource(owner, false)
+	void VulkanImage::getBarriers(const VkImageSubresourceRange& range, Vector<VkImageMemoryBarrier>& barriers)
+	{
+		UINT32 numSubresources = range.levelCount * range.layerCount;
+
+		// Nothing to do
+		if (numSubresources == 0)
+			return;
+
+		UINT32 mip = range.baseMipLevel;
+		UINT32 face = range.baseArrayLayer;
+		UINT32 lastMip = range.baseMipLevel + range.levelCount - 1;
+		UINT32 lastFace = range.baseArrayLayer + range.layerCount - 1;
+
+		VkImageMemoryBarrier defaultBarrier;
+		defaultBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+		defaultBarrier.pNext = nullptr;
+		defaultBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+		defaultBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+		defaultBarrier.image = getHandle();
+		defaultBarrier.subresourceRange.aspectMask = range.aspectMask;
+		defaultBarrier.subresourceRange.layerCount = 1;
+		defaultBarrier.subresourceRange.levelCount = 1;
+		defaultBarrier.subresourceRange.baseArrayLayer = 0;
+		defaultBarrier.subresourceRange.baseMipLevel = 0;
+
+		auto addNewBarrier = [&](VulkanImageSubresource* subresource, UINT32 face, UINT32 mip)
+		{
+			barriers.push_back(defaultBarrier);
+			VkImageMemoryBarrier* barrier = &barriers.back();
+
+			barrier->subresourceRange.baseArrayLayer = face;
+			barrier->subresourceRange.baseMipLevel = mip;
+			barrier->srcAccessMask = getAccessFlags(subresource->getLayout());
+			barrier->oldLayout = subresource->getLayout();
+
+			return barrier;
+		};
+
+		bs_frame_mark();
+		{
+			FrameVector<bool> processed(numSubresources, false);
+
+			// Add first subresource
+			VulkanImageSubresource* subresource = getSubresource(face, mip);
+			addNewBarrier(subresource, face, mip);
+			numSubresources--;
+			processed[0] = true;
+
+			while (numSubresources > 0)
+			{
+				// Try to expand the barrier as much as possible
+				VkImageMemoryBarrier* barrier = &barriers.back();
+
+				while (true)
+				{
+					// Expand by one in the X direction
+					bool expandedFace = true;
+					if (face < lastFace)
+					{
+						for (UINT32 i = 0; i < barrier->subresourceRange.levelCount; i++)
+						{
+							UINT32 curMip = barrier->subresourceRange.baseMipLevel + i;
+							VulkanImageSubresource* subresource = getSubresource(face + 1, curMip);
+							if (barrier->oldLayout != subresource->getLayout())
+							{
+								expandedFace = false;
+								break;
+							}
+						}
+
+						if (expandedFace)
+						{
+							barrier->subresourceRange.layerCount++;
+							numSubresources -= barrier->subresourceRange.levelCount;
+							face++;
+
+							for (UINT32 i = 0; i < barrier->subresourceRange.levelCount; i++)
+							{
+								UINT32 idx = i * range.layerCount + (face - range.baseArrayLayer);
+								processed[idx] = true;
+							}
+						}
+					}
+					else
+						expandedFace = false;
+
+					// Expand by one in the Y direction
+					bool expandedMip = true;
+					if (mip < lastMip)
+					{
+						for (UINT32 i = 0; i < barrier->subresourceRange.layerCount; i++)
+						{
+							UINT32 curFace = barrier->subresourceRange.baseArrayLayer + i;
+							VulkanImageSubresource* subresource = getSubresource(curFace, mip + 1);
+							if (barrier->oldLayout != subresource->getLayout())
+							{
+								expandedMip = false;
+								break;
+							}
+						}
+
+						if (expandedMip)
+						{
+							barrier->subresourceRange.levelCount++;
+							numSubresources -= barrier->subresourceRange.layerCount;
+							mip++;
+
+							for (UINT32 i = 0; i < barrier->subresourceRange.layerCount; i++)
+							{
+								UINT32 idx = (mip - range.baseMipLevel) * range.layerCount + i;
+								processed[idx] = true;
+							}
+						}
+					}
+					else
+						expandedMip = false;
+
+					// If we can't grow no more, we're done with this square
+					if (!expandedMip && !expandedFace)
+						break;
+				}
+
+				// Look for a new starting point (sub-resource we haven't processed yet)
+				for (UINT32 i = 0; i < range.levelCount; i++)
+				{
+					bool found = false;
+					for (UINT32 j = 0; j < range.layerCount; j++)
+					{
+						UINT32 idx = i * range.layerCount + j;
+						if (!processed[idx])
+						{
+							mip = range.baseMipLevel + i;
+							face = range.baseArrayLayer + j;
+
+							found = true;
+							break;
+						}
+					}
+
+					if (found)
+					{
+						VulkanImageSubresource* subresource = getSubresource(face, mip);
+						addNewBarrier(subresource, face, mip);
+						numSubresources--;
+						break;
+					}
+				}
+			}
+		}
+		bs_frame_clear();
+	}
+
+	VulkanImageSubresource::VulkanImageSubresource(VulkanResourceManager* owner, VkImageLayout layout)
+		:VulkanResource(owner, false), mLayout(layout)
 	{ }
 
 	VulkanTexture::VulkanTexture(const TEXTURE_DESC& desc, const SPtr<PixelData>& initialData,
@@ -628,9 +791,6 @@ namespace bs { namespace ct
 		range.baseMipLevel = 0;
 		range.levelCount = numMipmaps;
 
-		VkAccessFlags srcAccessMask = srcImage->getAccessFlags(srcImage->getLayout());
-		VkAccessFlags dstAccessMask = dstImage->getAccessFlags(dstImage->getLayout());
-
 		VkImageLayout transferSrcLayout, transferDstLayout;
 		if (mDirectlyMappable)
 		{
@@ -644,24 +804,24 @@ namespace bs { namespace ct
 		}
 
 		// Transfer textures to a valid layout
-		cb->setLayout(srcImage->getHandle(), srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, srcImage->getLayout(),
-							  transferSrcLayout, range);
-
-		cb->setLayout(dstImage->getHandle(), dstAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT,
-							  dstImage->getLayout(), transferDstLayout, range);
+		cb->setLayout(srcImage, range, VK_ACCESS_TRANSFER_READ_BIT, transferSrcLayout);
+		cb->setLayout(dstImage, range, VK_ACCESS_TRANSFER_WRITE_BIT, transferDstLayout);
 
 		vkCmdCopyImage(cb->getCB()->getHandle(), srcImage->getHandle(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
 						dstImage->getHandle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, numMipmaps, imageRegions);
 
 		// Transfer back to final layouts
-		srcAccessMask = srcImage->getAccessFlags(srcFinalLayout);
+		VkAccessFlags srcAccessMask = srcImage->getAccessFlags(srcFinalLayout);
 		cb->setLayout(srcImage->getHandle(), VK_ACCESS_TRANSFER_READ_BIT, srcAccessMask,
 							  transferSrcLayout, srcFinalLayout, range);
 
-		dstAccessMask = dstImage->getAccessFlags(dstFinalLayout);
+		VkAccessFlags dstAccessMask = dstImage->getAccessFlags(dstFinalLayout);
 		cb->setLayout(dstImage->getHandle(), VK_ACCESS_TRANSFER_WRITE_BIT, dstAccessMask,
 							  transferDstLayout, dstFinalLayout, range);
 
+		cb->getCB()->registerResource(srcImage, range, VulkanUseFlag::Read);
+		cb->getCB()->registerResource(dstImage, range, VulkanUseFlag::Write);
+
 		bs_stack_free(imageRegions);
 	}
 
@@ -764,8 +924,11 @@ namespace bs { namespace ct
 			if (srcImage == nullptr || dstImage == nullptr)
 				continue;
 
-			VkImageLayout srcLayout = srcImage->getLayout();
-			VkImageLayout dstLayout = dstImage->getLayout();
+			VulkanImageSubresource* srcSubresource = srcImage->getSubresource(srcFace, srcMipLevel);
+			VulkanImageSubresource* dstSubresource = dstImage->getSubresource(destFace, destMipLevel);
+
+			VkImageLayout srcLayout = srcSubresource->getLayout();
+			VkImageLayout dstLayout = dstSubresource->getLayout();
 
 			VulkanTransferBuffer* transferCB = cbManager.getTransferBuffer(i, queueType, localQueueIdx);
 			VkCommandBuffer vkCmdBuf = transferCB->getCB()->getHandle();
@@ -787,8 +950,6 @@ namespace bs { namespace ct
 
 					dstLayout = newImage->getOptimalLayout();
 					copyImage(transferCB, dstImage, newImage, oldDstLayout, dstLayout);
-
-					transferCB->getCB()->registerResource(dstImage, VulkanUseFlag::Read);
 				}
 
 				dstImage->destroy();
@@ -831,15 +992,11 @@ namespace bs { namespace ct
 									transferDstLayout, dstLayout, dstRange);
 
 			// Notify the command buffer that these resources are being used on it
-			transferCB->getCB()->registerResource(srcImage, VulkanUseFlag::Read);
-			transferCB->getCB()->registerResource(dstImage, VulkanUseFlag::Write);
+			transferCB->getCB()->registerResource(srcImage, srcRange, VulkanUseFlag::Read);
+			transferCB->getCB()->registerResource(dstImage, dstRange, VulkanUseFlag::Write);
 
 			// Need to wait if subresource we're reading from is being written, or if the subresource we're writing to is
 			// being accessed in any way
-
-			VulkanImageSubresource* srcSubresource = srcImage->getSubresource(srcFace, srcMipLevel);
-			VulkanImageSubresource* dstSubresource = dstImage->getSubresource(srcFace, srcMipLevel);
-
 			UINT32 srcUseFlags = srcSubresource->getUseInfo(VulkanUseFlag::Write);
 			UINT32 dstUseFlags = dstSubresource->getUseInfo(VulkanUseFlag::Read | VulkanUseFlag::Write);
 
@@ -902,7 +1059,8 @@ namespace bs { namespace ct
 		{
 			// Initially the texture will be in preinitialized layout, and it will transition to general layout on first
 			// use in shader. No further transitions are allowed for directly mappable textures.
-			assert(image->getLayout() == VK_IMAGE_LAYOUT_PREINITIALIZED || image->getLayout() == VK_IMAGE_LAYOUT_GENERAL);
+			assert(subresource->getLayout() == VK_IMAGE_LAYOUT_PREINITIALIZED || 
+				   subresource->getLayout() == VK_IMAGE_LAYOUT_GENERAL);
 
 			// GPU should never be allowed to write to a directly mappable texture, since only linear tiling is supported
 			// for direct mapping, and we don't support using it with either storage textures or render targets.
@@ -1062,8 +1220,8 @@ namespace bs { namespace ct
 										  extent.width, extent.height, extent.depth);
 
 			// Transfer texture to a valid layout
-			VkAccessFlags currentAccessMask = image->getAccessFlags(image->getLayout());
-			transferCB->setLayout(image->getHandle(), currentAccessMask, VK_ACCESS_TRANSFER_READ_BIT, image->getLayout(),
+			VkAccessFlags currentAccessMask = image->getAccessFlags(subresource->getLayout());
+			transferCB->setLayout(image->getHandle(), currentAccessMask, VK_ACCESS_TRANSFER_READ_BIT, subresource->getLayout(),
 								  VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, range);
 
 			// Queue copy command
@@ -1075,7 +1233,7 @@ namespace bs { namespace ct
 
 			transferCB->setLayout(image->getHandle(), VK_ACCESS_TRANSFER_READ_BIT, currentAccessMask,
 								  VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dstLayout, range);
-			transferCB->getCB()->registerResource(image, VulkanUseFlag::Read);
+			transferCB->getCB()->registerResource(image, range, VulkanUseFlag::Read);
 
 			// Ensure data written to the staging buffer is visible
 			VkAccessFlags stagingAccessFlags;
@@ -1128,10 +1286,10 @@ namespace bs { namespace ct
 				UINT32 localQueueIdx = CommandSyncMask::getQueueIdxAndType(mMappedGlobalQueueIdx, queueType);
 
 				VulkanImage* image = mImages[mMappedDeviceIdx];
-				VkImageLayout curLayout = image->getLayout();
 				VulkanTransferBuffer* transferCB = cbManager.getTransferBuffer(mMappedDeviceIdx, queueType, localQueueIdx);
 
 				VulkanImageSubresource* subresource = image->getSubresource(mMappedFace, mMappedMip);
+				VkImageLayout curLayout = subresource->getLayout();
 
 				// If the subresource is used in any way on the GPU, we need to wait for that use to finish before
 				// we issue our copy
@@ -1189,8 +1347,6 @@ namespace bs { namespace ct
 
 							curLayout = newImage->getOptimalLayout();
 							copyImage(transferCB, image, newImage, oldImgLayout, curLayout);
-
-							transferCB->getCB()->registerResource(image, VulkanUseFlag::Read);
 						}
 
 						image->destroy();
@@ -1239,7 +1395,7 @@ namespace bs { namespace ct
 
 				// Notify the command buffer that these resources are being used on it
 				transferCB->getCB()->registerResource(mStagingBuffer, VK_ACCESS_TRANSFER_READ_BIT, VulkanUseFlag::Read);
-				transferCB->getCB()->registerResource(image, VulkanUseFlag::Write);
+				transferCB->getCB()->registerResource(image, range, VulkanUseFlag::Write);
 
 				// We don't actually flush the transfer buffer here since it's an expensive operation, but it's instead
 				// done automatically before next "normal" command buffer submission.

+ 148 - 0
Source/BansheeVulkanRenderAPI/Source/BsVulkanUtility.cpp

@@ -544,4 +544,152 @@ namespace bs { namespace ct
 
 		return ((flags & (1 << idx)) != 0 || (flags == GDF_DEFAULT && device->isPrimary()));
 	}
+
+	void cutHorizontal(const VkImageSubresourceRange& toCut, const VkImageSubresourceRange& cutWith,
+					   VkImageSubresourceRange* output, UINT32& numAreas)
+	{
+		numAreas = 0;
+
+		INT32 leftCut = cutWith.baseArrayLayer - toCut.baseArrayLayer;
+		INT32 rightCut = (cutWith.baseArrayLayer + cutWith.layerCount) - toCut.baseArrayLayer;
+
+		if (leftCut > 0 && leftCut < (INT32)(toCut.baseArrayLayer + toCut.layerCount))
+		{
+			output[numAreas] = toCut;
+			VkImageSubresourceRange& range = output[numAreas];
+
+			range.baseArrayLayer = toCut.baseArrayLayer;
+			range.layerCount = leftCut;
+
+			numAreas++;
+		}
+
+		if (rightCut > 0 && rightCut < (INT32)toCut.layerCount)
+		{
+			output[numAreas] = toCut;
+			VkImageSubresourceRange& range = output[numAreas];
+
+			range.baseArrayLayer = toCut.baseArrayLayer + rightCut;
+			range.layerCount = toCut.layerCount - rightCut;
+
+			numAreas++;
+		}
+
+		// If we made both left and right cuts, this means we need a middle one as well
+		if (numAreas == 2)
+		{
+			output[numAreas] = toCut;
+			VkImageSubresourceRange& range = output[numAreas];
+
+			range.baseArrayLayer = toCut.baseArrayLayer + leftCut;
+			range.layerCount = toCut.layerCount - (toCut.layerCount - rightCut) - leftCut;
+
+			numAreas++;
+		}
+
+		// Nothing to cut
+		if (numAreas == 0)
+		{
+			output[numAreas] = toCut;
+			numAreas++;
+		}
+	}
+
+	void cutVertical(const VkImageSubresourceRange& toCut, const VkImageSubresourceRange& cutWith,
+					 VkImageSubresourceRange* output, UINT32& numAreas)
+	{
+		numAreas = 0;
+
+		INT32 topCut = cutWith.baseMipLevel - toCut.baseMipLevel;
+		INT32 bottomCut = (cutWith.baseMipLevel + cutWith.levelCount) - toCut.baseMipLevel;
+
+		if (topCut > 0 && topCut < (INT32)(toCut.baseMipLevel + toCut.levelCount))
+		{
+			output[numAreas] = toCut;
+			VkImageSubresourceRange& range = output[numAreas];
+
+			range.baseMipLevel = toCut.baseMipLevel;
+			range.levelCount = topCut;
+
+			numAreas++;
+		}
+
+		if (bottomCut > 0 && bottomCut < (INT32)toCut.levelCount)
+		{
+			output[numAreas] = toCut;
+			VkImageSubresourceRange& range = output[numAreas];
+
+			range.baseMipLevel = toCut.baseMipLevel + bottomCut;
+			range.levelCount = toCut.levelCount - bottomCut;
+
+			numAreas++;
+		}
+
+		// If we made both top and bottom cuts, this means we need a middle one as well
+		if (numAreas == 2)
+		{
+			output[numAreas] = toCut;
+			VkImageSubresourceRange& range = output[numAreas];
+
+			range.baseMipLevel = toCut.baseMipLevel + topCut;
+			range.levelCount = toCut.levelCount - (toCut.levelCount - bottomCut) - topCut;
+
+			numAreas++;
+		}
+
+		// Nothing to cut
+		if (numAreas == 0)
+		{
+			output[numAreas] = toCut;
+			numAreas++;
+		}
+	}
+
+	void VulkanUtility::cutRange(const VkImageSubresourceRange& toCut, const VkImageSubresourceRange& cutWith,
+				  std::array<VkImageSubresourceRange, 5>& output, UINT32& numAreas)
+	{
+		numAreas = 0;
+
+		// Cut horizontally
+		UINT32 numHorzCuts = 0;
+		std::array<VkImageSubresourceRange, 3> horzCuts;
+		cutHorizontal(toCut, cutWith, horzCuts.data(), numHorzCuts);
+
+		// Cut vertically
+		for (UINT32 i = 0; i < numHorzCuts; i++)
+		{
+			VkImageSubresourceRange& range = horzCuts[i];
+
+			if (range.baseArrayLayer >= cutWith.baseArrayLayer &&
+				(range.baseArrayLayer + range.layerCount) <= (cutWith.baseArrayLayer + cutWith.layerCount))
+			{
+				UINT32 numVertCuts = 0;
+				cutVertical(range, cutWith, output.data() + numAreas, numVertCuts);
+
+				numAreas += numVertCuts;
+			}
+			else
+			{
+				output[numAreas] = range;
+				numAreas++;
+			}
+		}
+
+		assert(numAreas <= 5);
+	}
+
+	bool VulkanUtility::rangeOverlaps(const VkImageSubresourceRange& a, const VkImageSubresourceRange& b)
+	{
+		INT32 aRight = a.baseArrayLayer + (INT32)a.layerCount;
+		INT32 bRight = b.baseArrayLayer + (INT32)b.layerCount;
+
+		INT32 aBottom = a.baseMipLevel + (INT32)a.levelCount;
+		INT32 bBottom = b.baseMipLevel + (INT32)b.levelCount;
+
+		if ((INT32)a.baseArrayLayer < bRight && aRight >(INT32)b.baseArrayLayer &&
+			(INT32)a.baseMipLevel < bBottom && aBottom >(INT32)b.baseMipLevel)
+			return true;
+
+		return false;
+	}
 }}