Parcourir la source

Vulkan: Simplifying image layout transitions so they can be properly tracked across multiple command buffers

BearishSun il y a 9 ans
Parent
commit
4ce05d637b

+ 11 - 3
Source/BansheeVulkanRenderAPI/Include/BsVulkanCommandBuffer.h

@@ -192,10 +192,18 @@ namespace bs
 
 		/** 
 		 * 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.
+		 * device when the command buffer is submitted. Executes a layout transition to @p newLayout (if needed), and
+		 * updates the externally visible image layout field to @p finalLayout (once submitted).
 		 */
-		void registerResource(VulkanImage* res, VkAccessFlags accessFlags, VkImageLayout currentLayout,
-			VkImageLayout newLayout, VkImageLayout finalLayout, VulkanUseFlags flags, bool isFBAttachment = false);
+		void registerResource(VulkanImage* res, VkAccessFlags accessFlags, 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. Updates the externally visible image layout field to @p finalLayout
+		 * (once submitted), but doesn't actually execute a layout transition (expects you did it manually).
+		 */
+		void registerResource(VulkanImage* res, VkAccessFlags accessFlags, VkImageLayout finalLayout, VulkanUseFlags flags);
 
 		/** 
 		 * Lets the command buffer know that the provided image resource has been queued on it, and will be used by the

+ 20 - 8
Source/BansheeVulkanRenderAPI/Include/BsVulkanTexture.h

@@ -14,6 +14,16 @@ namespace bs
 
 	class VulkanImageSubresource;
 
+	/** Type of a Vulkan image, determining its usage. */
+	enum class VulkanImageUsage
+	{
+		Sampled,
+		SampledDynamic,
+		Storage,
+		ColorAttachment,
+		DepthAttachment
+	};
+
 	/** Descriptor used for initializing a VulkanImage. */
 	struct VULKAN_IMAGE_DESC
 	{
@@ -24,8 +34,7 @@ namespace bs
 		VkFormat format; /**< Pixel format of the image. */
 		UINT32 numFaces; /**< Number of faces (array slices, or cube-map faces). */
 		UINT32 numMipLevels; /**< Number of mipmap levels per face. */
-		bool isDepthStencil; /**< True if the image represents a depth-stencil surface. */
-		bool isStorage; /**< True if the texture supports shader random access reads and writes. */
+		VulkanImageUsage usage; /** Determines how will the image be used. */
 	};
 
 	/** Wrapper around a Vulkan image object that manages its usage and lifetime. */
@@ -56,7 +65,14 @@ namespace bs
 		/** Returns the internal handle to the Vulkan object. */
 		VkImage getHandle() const { return mImage; }
 
-		/** Returns the layout the image is currently in. */
+		/** 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. */
@@ -139,9 +155,8 @@ namespace bs
 		VkImageLayout mLayout;
 		VkImageView mMainView;
 		VkImageView mFramebufferMainView;
+		VulkanImageUsage mUsage;
 		bool mOwnsImage;
-		bool mIsStorage;
-		bool mIsDepthStencil;
 
 		UINT32 mNumFaces;
 		UINT32 mNumMipLevels;
@@ -219,9 +234,6 @@ namespace bs
 		void copyImage(VulkanTransferBuffer* cb, VulkanImage* srcImage, VulkanImage* dstImage, 
 			VkImageLayout srcFinalLayout, VkImageLayout dstFinalLayout);
 
-		/** Returns the optimal layout this image should normally be in. */
-		VkImageLayout getOptimalLayout() const;
-
 		VulkanImage* mImages[BS_MAX_DEVICES];
 		GpuDeviceFlags mDeviceMask;
 

+ 66 - 17
Source/BansheeVulkanRenderAPI/Source/BsVulkanCommandBuffer.cpp

@@ -443,6 +443,7 @@ namespace bs
 			}
 		}
 
+		// For images issue queue transitions, as above. Also issue layout transitions to their optimal layouts.
 		for (auto& entry : mImages)
 		{
 			VulkanImage* resource = static_cast<VulkanImage*>(entry.first);
@@ -451,7 +452,9 @@ namespace bs
 			UINT32 currentQueueFamily = resource->getQueueFamily();
 			bool queueMismatch = resource->isExclusive() && currentQueueFamily != -1 && currentQueueFamily != mQueueFamily;
 
-			if (queueMismatch)
+			VkImageLayout currentLayout = resource->getLayout();
+			VkImageLayout optimalLayout = resource->getOptimalLayout();
+			if (queueMismatch || currentLayout != optimalLayout)
 			{
 				Vector<VkImageMemoryBarrier>& barriers = mTransitionInfoTemp[currentQueueFamily].imageBarriers;
 
@@ -459,15 +462,29 @@ namespace bs
 				VkImageMemoryBarrier& barrier = barriers.back();
 				barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
 				barrier.pNext = nullptr;
-				barrier.srcAccessMask = resource->getAccessFlags(imageInfo.currentLayout);
-				barrier.dstAccessMask = imageInfo.accessFlags;
-				barrier.oldLayout = imageInfo.currentLayout;
-				barrier.newLayout = imageInfo.currentLayout;
+				barrier.srcAccessMask = resource->getAccessFlags(currentLayout);
+				barrier.dstAccessMask = resource->getAccessFlags(optimalLayout);
+				barrier.oldLayout = currentLayout;
+				barrier.newLayout = optimalLayout;
 				barrier.image = resource->getHandle();
 				barrier.subresourceRange = imageInfo.range;
 				barrier.srcQueueFamilyIndex = currentQueueFamily;
 				barrier.dstQueueFamilyIndex = mQueueFamily;
+
+				// Check if queue transition needed
+				if (queueMismatch)
+				{
+					barrier.srcQueueFamilyIndex = currentQueueFamily;
+					barrier.dstQueueFamilyIndex = mQueueFamily;
+				}
+				else
+				{
+					barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+					barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+				}
 			}
+
+			resource->setLayout(imageInfo.finalLayout);
 		}
 
 		for (auto& entry : mTransitionInfoTemp)
@@ -478,7 +495,7 @@ namespace bs
 
 			UINT32 entryQueueFamily = entry.first;
 
-			// No queue transition needed for entries on this queue 
+			// No queue transition needed for entries on this queue (this entry is most likely an image layout transition)
 			if (entryQueueFamily == -1 || entryQueueFamily == mQueueFamily)
 				continue;
 
@@ -532,6 +549,10 @@ namespace bs
 
 			cmdBuffer->end();
 			otherQueue->submit(cmdBuffer, nullptr, 0);
+
+			// If there are any layout transitions, reset them as we don't need them for the second pipeline barrier
+			for (auto& barrierEntry : barriers.imageBarriers)
+				barrierEntry.oldLayout = barrierEntry.newLayout;
 		}
 
 		UINT32 deviceIdx = device.getIndex();
@@ -1209,8 +1230,6 @@ namespace bs
 			createLayoutTransitionBarrier(entry.first, imageInfo);
 		}
 
-		mQueuedLayoutTransitions.clear();
-
 		vkCmdPipelineBarrier(mCmdBuffer,
 							 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, // Note: VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT might be more correct here, according to the spec
 							 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
@@ -1218,6 +1237,7 @@ namespace bs
 							 0, nullptr,
 							 (UINT32)mLayoutTransitionBarriersTemp.size(), mLayoutTransitionBarriersTemp.data());
 
+		mQueuedLayoutTransitions.clear();
 		mLayoutTransitionBarriersTemp.clear();
 	}
 
@@ -1242,6 +1262,16 @@ namespace bs
 		vkCmdBeginRenderPass(mCmdBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
 		vkCmdEndRenderPass(mCmdBuffer);
 
+		// Update any layout transitions that were performed by subpass dependencies
+		for (auto& entry : mImages)
+		{
+			UINT32 imageInfoIdx = entry.second;
+			ImageInfo& imageInfo = mImageInfos[imageInfoIdx];
+
+			imageInfo.currentLayout = imageInfo.finalLayout;
+			imageInfo.requiredLayout = imageInfo.finalLayout;
+		}
+
 		mClearMask = CLEAR_NONE;
 	}
 
@@ -1390,8 +1420,16 @@ namespace bs
 		}
 	}
 
-	void VulkanCmdBuffer::registerResource(VulkanImage* res, VkAccessFlags accessFlags, VkImageLayout currentLayout,
-			VkImageLayout newLayout, VkImageLayout finalLayout, VulkanUseFlags flags, bool isFBAttachment)
+	void VulkanCmdBuffer::registerResource(VulkanImage* res, VkAccessFlags accessFlags, VkImageLayout finalLayout, 
+		VulkanUseFlags flags)
+	{
+		VkImageLayout layout = res->getOptimalLayout();
+
+		registerResource(res, accessFlags, layout, finalLayout, flags, false);
+	}
+
+	void VulkanCmdBuffer::registerResource(VulkanImage* res, VkAccessFlags accessFlags, 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
@@ -1408,10 +1446,23 @@ namespace bs
 			UINT32 imageInfoIdx = insertResult.first->second;
 			mImageInfos.push_back(ImageInfo());
 
+			// System always assumes a certain initial layout depending on image's type. If the image isn't in this layout
+			// the system will execute a transition to that layout in submit(), before anything else. This makes the whole
+			// layout transition system simpler, and ensures layout transitions can neatly happen between multiple CB's
+			// for the same resource.
+			VkImageLayout currentLayout = res->getOptimalLayout();
+
+			// If new layout is set to UNDEFINED, that's a signal that we don't need to do a layout transition
+			VkImageLayout requiredLayout;
+			if (newLayout == VK_IMAGE_LAYOUT_UNDEFINED)
+				requiredLayout = currentLayout;
+			else
+				requiredLayout = newLayout;
+
 			ImageInfo& imageInfo = mImageInfos[imageInfoIdx];
 			imageInfo.accessFlags = accessFlags;
 			imageInfo.currentLayout = currentLayout;
-			imageInfo.requiredLayout = newLayout;
+			imageInfo.requiredLayout = requiredLayout;
 			imageInfo.finalLayout = finalLayout;
 			imageInfo.range = range;
 			imageInfo.isFBAttachment = isFBAttachment;
@@ -1570,12 +1621,11 @@ namespace bs
 			}
 			else
 			{
-				layout = attachment.image->getLayout();
+				layout = VK_IMAGE_LAYOUT_UNDEFINED;
 				accessMask = 0;
 			}
 
-			registerResource(attachment.image, accessMask, attachment.image->getLayout(), layout, attachment.finalLayout, 
-				VulkanUseFlag::Write, true);
+			registerResource(attachment.image, accessMask, layout, attachment.finalLayout, VulkanUseFlag::Write, true);
 		}
 
 		if(res->hasDepthAttachment())
@@ -1593,12 +1643,11 @@ namespace bs
 			}
 			else
 			{
-				layout = attachment.image->getLayout();
+				layout = VK_IMAGE_LAYOUT_UNDEFINED;
 				accessMask = 0;
 			}
 
-			registerResource(attachment.image, accessMask, attachment.image->getLayout(), layout, attachment.finalLayout, 
-				VulkanUseFlag::Write, true);
+			registerResource(attachment.image, accessMask, layout, attachment.finalLayout, VulkanUseFlag::Write, true);
 		}
 	}
 

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

@@ -23,7 +23,7 @@ namespace bs
 	}
 
 	bool VulkanFramebuffer::VariantKey::EqualFunction::operator()(const VariantKey& lhs,
-																			 const VariantKey& rhs) const
+																  const VariantKey& rhs) const
 	{
 		return lhs.loadMask == rhs.loadMask && lhs.readMask == rhs.readMask && lhs.clearMask == rhs.clearMask;
 	}

+ 2 - 4
Source/BansheeVulkanRenderAPI/Source/BsVulkanGpuParams.cpp

@@ -651,8 +651,7 @@ namespace bs
 			VkAccessFlags accessFlags = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
 			VulkanUseFlags useFlags = VulkanUseFlag::Read | VulkanUseFlag::Write;
 
-			buffer.registerResource(resource, accessFlags, resource->getLayout(), VK_IMAGE_LAYOUT_GENERAL, 
-				VK_IMAGE_LAYOUT_GENERAL, useFlags);
+			buffer.registerResource(resource, accessFlags, 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);
@@ -695,8 +694,7 @@ namespace bs
 			else
 				layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
 
-			buffer.registerResource(resource, VK_ACCESS_SHADER_READ_BIT, resource->getLayout(), layout, layout, 
-				VulkanUseFlag::Read);
+			buffer.registerResource(resource, VK_ACCESS_SHADER_READ_BIT, 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);

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

@@ -129,10 +129,9 @@ namespace bs
 		VulkanResourceManager& resManager = device->getResourceManager();
 
 		VULKAN_IMAGE_DESC imageDesc;
-		imageDesc.isDepthStencil = false;
-		imageDesc.isStorage = false;
 		imageDesc.format = colorFormat;
 		imageDesc.type = TEX_TYPE_2D;
+		imageDesc.usage = VulkanImageUsage::ColorAttachment;
 		imageDesc.layout = VK_IMAGE_LAYOUT_UNDEFINED;
 		imageDesc.numFaces = 1;
 		imageDesc.numMipLevels = 1;
@@ -176,7 +175,7 @@ namespace bs
 			assert(result == VK_SUCCESS);
 
 			imageDesc.image = depthStencilImage;
-			imageDesc.isDepthStencil = true;
+			imageDesc.usage = VulkanImageUsage::DepthAttachment;
 			imageDesc.format = depthFormat;
 			imageDesc.memory = mDevice->allocateMemory(depthStencilImage, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
 

+ 49 - 46
Source/BansheeVulkanRenderAPI/Source/BsVulkanTexture.cpp

@@ -21,10 +21,20 @@ namespace bs
 		desc.format = VulkanUtility::getPixelFormat(props.getFormat(), props.isHardwareGammaEnabled());
 		desc.numFaces = props.getNumFaces();
 		desc.numMipLevels = props.getNumMipmaps() + 1;
-		desc.isDepthStencil = (props.getUsage() & TU_DEPTHSTENCIL) != 0;
-		desc.isStorage = (props.getUsage() & TU_LOADSTORE) != 0;
 		desc.layout = layout;
 
+		int usage = props.getUsage();
+		if ((usage & TU_RENDERTARGET) != 0)
+			desc.usage = VulkanImageUsage::ColorAttachment;
+		else if ((usage & TU_DEPTHSTENCIL) != 0)
+			desc.usage = VulkanImageUsage::DepthAttachment;
+		else if ((usage & TU_LOADSTORE) != 0)
+			desc.usage = VulkanImageUsage::Storage;
+		else if ((usage & TU_DYNAMIC) != 0)
+			desc.usage = VulkanImageUsage::SampledDynamic;
+		else
+			desc.usage = VulkanImageUsage::Sampled;
+
 		return desc;
 	}
 
@@ -36,7 +46,7 @@ namespace bs
 	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), mIsStorage(desc.isStorage)
+		, mNumMipLevels(desc.numMipLevels), mUsage(desc.usage)
 	{
 		mImageViewCI.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
 		mImageViewCI.pNext = nullptr;
@@ -67,10 +77,8 @@ namespace bs
 			break;
 		}
 
-		mIsDepthStencil = desc.isDepthStencil;
-
 		TextureSurface completeSurface(0, desc.numMipLevels, 0, desc.numFaces);
-		if (mIsDepthStencil)
+		if (mUsage == VulkanImageUsage::DepthAttachment)
 		{
 			mFramebufferMainView = createView(completeSurface, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT);
 			mMainView = createView(completeSurface, VK_IMAGE_ASPECT_DEPTH_BIT);
@@ -126,7 +134,7 @@ namespace bs
 
 	VkImageView VulkanImage::getView(bool framebuffer) const
 	{
-		if(framebuffer && mIsDepthStencil)
+		if(framebuffer && mUsage == VulkanImageUsage::DepthAttachment)
 			return mFramebufferMainView;
 
 		return mMainView;
@@ -141,7 +149,7 @@ namespace bs
 				surface.arraySlice == entry.surface.arraySlice &&
 				surface.numArraySlices == entry.surface.numArraySlices)
 			{
-				if(!mIsDepthStencil)
+				if(mUsage != VulkanImageUsage::DepthAttachment)
 					return entry.view;
 				else
 				{
@@ -155,7 +163,7 @@ namespace bs
 		info.surface = surface;
 		info.framebuffer = framebuffer;
 
-		if (mIsDepthStencil)
+		if (mUsage == VulkanImageUsage::DepthAttachment)
 		{
 			if(framebuffer)
 				info.view = createView(surface, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT);
@@ -207,6 +215,22 @@ namespace bs
 		return view;
 	}
 
+	VkImageLayout VulkanImage::getOptimalLayout() const
+	{
+		switch (mUsage)
+		{
+		case VulkanImageUsage::ColorAttachment:
+			return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+		case VulkanImageUsage::DepthAttachment:
+			return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+		case VulkanImageUsage::Storage:
+		case VulkanImageUsage::SampledDynamic:
+			return VK_IMAGE_LAYOUT_GENERAL;
+		default:
+			return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		}
+	}
+
 	VkImageSubresourceRange VulkanImage::getRange() const
 	{
 		VkImageSubresourceRange range;
@@ -215,7 +239,7 @@ namespace bs
 		range.baseMipLevel = 0;
 		range.levelCount = mNumMipLevels;
 
-		if (mIsDepthStencil)
+		if (mUsage == VulkanImageUsage::DepthAttachment)
 			range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
 		else
 			range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
@@ -296,7 +320,7 @@ namespace bs
 		switch (layout)
 		{
 		case VK_IMAGE_LAYOUT_GENERAL: // Only used for render targets that are also read by shaders, or for storage textures
-			if (mIsStorage)
+			if (mUsage == VulkanImageUsage::Storage)
 				accessFlags = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
 			else
 				accessFlags = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_SHADER_READ_BIT;
@@ -596,21 +620,6 @@ namespace bs
 		bs_stack_free(imageRegions);
 	}
 
-	VkImageLayout VulkanTextureCore::getOptimalLayout() const
-	{
-		int usage = getProperties().getUsage();
-		if ((usage & TU_RENDERTARGET) != 0)
-			return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-		else if ((usage & TU_DEPTHSTENCIL) != 0)
-			return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
-		else if ((usage & TU_LOADSTORE) != 0)
-			return VK_IMAGE_LAYOUT_GENERAL;
-		else if ((usage & TU_DYNAMIC) != 0)
-			return VK_IMAGE_LAYOUT_GENERAL;
-		else
-			return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
-	}
-
 	void VulkanTextureCore::copyImpl(UINT32 srcFace, UINT32 srcMipLevel, UINT32 destFace, UINT32 destMipLevel,
 									 const SPtr<TextureCore>& target, UINT32 queueIdx)
 	{
@@ -731,14 +740,13 @@ namespace bs
 				{
 					VkImageLayout oldDstLayout = dstLayout;
 					if (oldDstLayout == VK_IMAGE_LAYOUT_UNDEFINED || oldDstLayout == VK_IMAGE_LAYOUT_PREINITIALIZED)
-						oldDstLayout = getOptimalLayout();
+						oldDstLayout = dstImage->getOptimalLayout();
 
-					dstLayout = getOptimalLayout();
+					dstLayout = newImage->getOptimalLayout();
 					copyImage(transferCB, dstImage, newImage, oldDstLayout, dstLayout);
 
 					VkAccessFlags accessMask = dstImage->getAccessFlags(oldDstLayout);
-					transferCB->getCB()->registerResource(dstImage, accessMask, oldDstLayout, oldDstLayout, oldDstLayout,
-						VulkanUseFlag::Read);
+					transferCB->getCB()->registerResource(dstImage, accessMask, oldDstLayout, VulkanUseFlag::Read);
 				}
 
 				dstImage->destroy();
@@ -769,24 +777,22 @@ namespace bs
 
 			// Transfer back to original layouts
 			if (srcLayout == VK_IMAGE_LAYOUT_UNDEFINED || srcLayout == VK_IMAGE_LAYOUT_PREINITIALIZED)
-				srcLayout = getOptimalLayout();
+				srcLayout = srcImage->getOptimalLayout();
 
 			srcAccessMask = srcImage->getAccessFlags(srcLayout);
 			transferCB->setLayout(srcImage->getHandle(), VK_ACCESS_TRANSFER_READ_BIT, srcAccessMask,
 									transferSrcLayout, srcLayout, srcRange);
 
 			if (dstLayout == VK_IMAGE_LAYOUT_UNDEFINED || dstLayout == VK_IMAGE_LAYOUT_PREINITIALIZED)
-				dstLayout = other->getOptimalLayout();
+				dstLayout = dstImage->getOptimalLayout();
 
 			dstAccessMask = dstImage->getAccessFlags(dstLayout);
 			transferCB->setLayout(dstImage->getHandle(), VK_ACCESS_TRANSFER_WRITE_BIT, dstAccessMask,
 									transferDstLayout, dstLayout, dstRange);
 
 			// Notify the command buffer that these resources are being used on it
-			transferCB->getCB()->registerResource(srcImage, srcAccessMask, srcLayout, srcLayout, srcLayout, 
-				VulkanUseFlag::Read);
-			transferCB->getCB()->registerResource(dstImage, dstAccessMask, dstLayout, dstLayout, dstLayout, 
-				VulkanUseFlag::Write);
+			transferCB->getCB()->registerResource(srcImage, srcAccessMask, srcLayout, VulkanUseFlag::Read);
+			transferCB->getCB()->registerResource(dstImage, dstAccessMask, dstLayout, 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
@@ -1031,14 +1037,13 @@ namespace bs
 			VkImageLayout dstLayout = image->getLayout();
 			if (dstLayout == VK_IMAGE_LAYOUT_UNDEFINED || dstLayout == VK_IMAGE_LAYOUT_PREINITIALIZED)
 			{
-				dstLayout = getOptimalLayout();
+				dstLayout = image->getOptimalLayout();
 				currentAccessMask = image->getAccessFlags(dstLayout);
 			}
 
 			transferCB->setLayout(image->getHandle(), VK_ACCESS_TRANSFER_READ_BIT, currentAccessMask,
 								  VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dstLayout, range);
-			transferCB->getCB()->registerResource(image, currentAccessMask, dstLayout, dstLayout, dstLayout,
-				VulkanUseFlag::Read);
+			transferCB->getCB()->registerResource(image, currentAccessMask, dstLayout, VulkanUseFlag::Read);
 
 			// Ensure data written to the staging buffer is visible
 			VkAccessFlags stagingAccessFlags;
@@ -1150,14 +1155,13 @@ namespace bs
 						{
 							VkImageLayout oldImgLayout = image->getLayout();
 							if (oldImgLayout == VK_IMAGE_LAYOUT_UNDEFINED || oldImgLayout == VK_IMAGE_LAYOUT_PREINITIALIZED)
-								oldImgLayout = getOptimalLayout();
+								oldImgLayout = image->getOptimalLayout();
 
-							curLayout = getOptimalLayout();
+							curLayout = newImage->getOptimalLayout();
 							copyImage(transferCB, image, newImage, oldImgLayout, curLayout);
 
 							VkAccessFlags accessMask = image->getAccessFlags(oldImgLayout);
-							transferCB->getCB()->registerResource(image, accessMask, oldImgLayout, oldImgLayout, 
-								oldImgLayout, VulkanUseFlag::Read);
+							transferCB->getCB()->registerResource(image, accessMask, oldImgLayout, VulkanUseFlag::Read);
 						}
 
 						image->destroy();
@@ -1204,7 +1208,7 @@ namespace bs
 				// Transfer back to original  (or optimal if initial layout was undefined/preinitialized)
 				VkImageLayout dstLayout = curLayout;
 				if(dstLayout == VK_IMAGE_LAYOUT_UNDEFINED || dstLayout == VK_IMAGE_LAYOUT_PREINITIALIZED)
-					dstLayout = getOptimalLayout();
+					dstLayout = image->getOptimalLayout();
 
 				currentAccessMask = image->getAccessFlags(dstLayout);
 				transferCB->setLayout(image->getHandle(), VK_ACCESS_TRANSFER_WRITE_BIT, currentAccessMask,
@@ -1212,8 +1216,7 @@ namespace bs
 
 				// 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, currentAccessMask, dstLayout, dstLayout, dstLayout,
-					VulkanUseFlag::Write);
+				transferCB->getCB()->registerResource(image, currentAccessMask, dstLayout, 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.