Просмотр исходного кода

Vulkan: Use a render-pass with clear ops, when a clear is issued before any draw calls
Vulkan: Pipelines are no longer dependant on render-pass load/store and layout properties

BearishSun 9 лет назад
Родитель
Сommit
b18bb2adc2

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

@@ -307,6 +307,12 @@ namespace bs
 		void clearViewport(const Rect2I& area, UINT32 buffers, const Color& color, float depth, UINT16 stencil, 
 		void clearViewport(const Rect2I& area, UINT32 buffers, const Color& color, float depth, UINT16 stencil, 
 			UINT8 targetMask);
 			UINT8 targetMask);
 
 
+		/** Starts and ends a render pass, intended only for a clear operation. */
+		void executeClearPass();
+
+		/** Executes any queued layout transitions by issuing a pipeline barrier. */
+		void executeLayoutTransitions();
+
 		UINT32 mId;
 		UINT32 mId;
 		UINT32 mQueueFamily;
 		UINT32 mQueueFamily;
 		State mState;
 		State mState;
@@ -344,6 +350,10 @@ namespace bs
 		bool mScissorRequiresBind : 1;
 		bool mScissorRequiresBind : 1;
 		DescriptorSetBindFlags mDescriptorSetsBindState;
 		DescriptorSetBindFlags mDescriptorSetsBindState;
 
 
+		std::array<VkClearValue, BS_MAX_MULTIPLE_RENDER_TARGETS + 1> mClearValues;
+		ClearMask mClearMask;
+		Rect2I mClearArea;
+
 		VulkanSemaphore* mSemaphoresTemp[BS_MAX_UNIQUE_QUEUES];
 		VulkanSemaphore* mSemaphoresTemp[BS_MAX_UNIQUE_QUEUES];
 		VkBuffer mVertexBuffersTemp[BS_MAX_BOUND_VERTEX_BUFFERS];
 		VkBuffer mVertexBuffersTemp[BS_MAX_BOUND_VERTEX_BUFFERS];
 		VkDeviceSize mVertexBufferOffsetsTemp[BS_MAX_BOUND_VERTEX_BUFFERS];
 		VkDeviceSize mVertexBufferOffsetsTemp[BS_MAX_BOUND_VERTEX_BUFFERS];

+ 11 - 4
Source/BansheeVulkanRenderAPI/Include/BsVulkanFramebuffer.h

@@ -81,16 +81,22 @@ namespace bs
 		 * 
 		 * 
 		 * @param[in]	loadMask	Mask that control which render target surface contents should be preserved on load.
 		 * @param[in]	loadMask	Mask that control which render target surface contents should be preserved on load.
 		 * @param[in]	readMask	Mask that controls which render targets can be read by shaders while they're bound.
 		 * @param[in]	readMask	Mask that controls which render targets can be read by shaders while they're bound.
+		 * @param[in]	clearMask	Mask that controls which render targets should be cleared on render pass start. Target
+		 *							cannot have both load and clear bits set. If load bit is set, clear will be ignored.
 		 */
 		 */
-		VkRenderPass getRenderPass(RenderSurfaceMask loadMask, RenderSurfaceMask readMask) const;
+		VkRenderPass getRenderPass(RenderSurfaceMask loadMask, RenderSurfaceMask readMask, 
+								   ClearMask clearMask) const;
 
 
 		/**
 		/**
 		 * Gets internal Vulkan framebuffer object.
 		 * Gets internal Vulkan framebuffer object.
 		 *
 		 *
 		 * @param[in]	loadMask	Mask that control which render target surface contents should be preserved on load.
 		 * @param[in]	loadMask	Mask that control which render target surface contents should be preserved on load.
 		 * @param[in]	readMask	Mask that controls which render targets can be read by shaders while they're bound.
 		 * @param[in]	readMask	Mask that controls which render targets can be read by shaders while they're bound.
+		 * @param[in]	clearMask	Mask that controls which render targets should be cleared on render pass start. Target
+		 *							cannot have both load and clear bits set. If load bit is set, clear will be ignored.
 		 */
 		 */
-		VkFramebuffer getFramebuffer(RenderSurfaceMask loadMask, RenderSurfaceMask readMask) const;
+		VkFramebuffer getFramebuffer(RenderSurfaceMask loadMask, RenderSurfaceMask readMask, 
+									 ClearMask clearMask) const;
 
 
 		/** 
 		/** 
 		 * Gets the number of layers in each framebuffer surface. A layer is an element in a texture array, or a depth 
 		 * Gets the number of layers in each framebuffer surface. A layer is an element in a texture array, or a depth 
@@ -126,7 +132,7 @@ namespace bs
 		/** Key used for identifying different types of frame-buffer variants. */
 		/** Key used for identifying different types of frame-buffer variants. */
 		struct VariantKey
 		struct VariantKey
 		{
 		{
-			VariantKey(RenderSurfaceMask loadMask, RenderSurfaceMask readMask);
+			VariantKey(RenderSurfaceMask loadMask, RenderSurfaceMask readMask, ClearMask clearMask);
 
 
 			class HashFunction
 			class HashFunction
 			{
 			{
@@ -142,10 +148,11 @@ namespace bs
 
 
 			RenderSurfaceMask loadMask;
 			RenderSurfaceMask loadMask;
 			RenderSurfaceMask readMask;
 			RenderSurfaceMask readMask;
+			ClearMask clearMask;
 		};
 		};
 
 
 		/** Creates a new variant of the framebuffer. */
 		/** Creates a new variant of the framebuffer. */
-		Variant createVariant(RenderSurfaceMask loadMask, RenderSurfaceMask readMask) const;
+		Variant createVariant(RenderSurfaceMask loadMask, RenderSurfaceMask readMask, ClearMask clearMask) const;
 
 
 		UINT32 mId;
 		UINT32 mId;
 
 

+ 3 - 16
Source/BansheeVulkanRenderAPI/Include/BsVulkanGpuPipelineState.h

@@ -55,10 +55,6 @@ namespace bs
 		 * @param[in]	deviceIdx			Index of the device to retrieve the pipeline for.
 		 * @param[in]	deviceIdx			Index of the device to retrieve the pipeline for.
 		 * @param[in]	framebuffer			Framebuffer object that defines the surfaces this pipeline will render to.
 		 * @param[in]	framebuffer			Framebuffer object that defines the surfaces this pipeline will render to.
 		 * @param[in]	readOnlyDepth		True if the pipeline is only allowed to read the depth buffer, without writes.
 		 * @param[in]	readOnlyDepth		True if the pipeline is only allowed to read the depth buffer, without writes.
-		 * @param[in]	loadMask			Mask that controls for which framebuffer surfaces should the existing contents
-		 *									be preserved for.
-		 * @param[in]	readMask			Mask that controls which framebuffer surfaces can be read from the shader while
-		 *									they're bound for rendering.
 		 * @param[in]	drawOp				Type of geometry that will be drawn using the pipeline.
 		 * @param[in]	drawOp				Type of geometry that will be drawn using the pipeline.
 		 * @param[in]	vertexInput			State describing inputs to the vertex program.
 		 * @param[in]	vertexInput			State describing inputs to the vertex program.
 		 * @return							Vulkan graphics pipeline object.
 		 * @return							Vulkan graphics pipeline object.
@@ -66,8 +62,7 @@ namespace bs
 		 * @note	Thread safe.
 		 * @note	Thread safe.
 		 */
 		 */
 		VulkanPipeline* getPipeline(UINT32 deviceIdx, VulkanFramebuffer* framebuffer, bool readOnlyDepth, 
 		VulkanPipeline* getPipeline(UINT32 deviceIdx, VulkanFramebuffer* framebuffer, bool readOnlyDepth, 
-			RenderSurfaceMask loadMask, RenderSurfaceMask readMask, DrawOperationType drawOp, 
-			const SPtr<VulkanVertexInput>& vertexInput);
+			DrawOperationType drawOp, const SPtr<VulkanVertexInput>& vertexInput);
 
 
 		/** 
 		/** 
 		 * Returns a pipeline layout object for the specified device index. If the device index doesn't match a bit in the
 		 * Returns a pipeline layout object for the specified device index. If the device index doesn't match a bit in the
@@ -95,10 +90,6 @@ namespace bs
 		 * @param[in]	deviceIdx			Index of the device to create the pipeline for.
 		 * @param[in]	deviceIdx			Index of the device to create the pipeline for.
 		 * @param[in]	framebuffer			Framebuffer object that defines the surfaces this pipeline will render to.
 		 * @param[in]	framebuffer			Framebuffer object that defines the surfaces this pipeline will render to.
 		 * @param[in]	readOnlyDepth		True if the pipeline is only allowed to read the depth buffer, without writes.
 		 * @param[in]	readOnlyDepth		True if the pipeline is only allowed to read the depth buffer, without writes.
-		 * @param[in]	loadMask			Mask that controls for which framebuffer surfaces should the existing contents 
-		 *									be preserved for.
-		 * @param[in]	readMask			Mask that controls which framebuffer surfaces can be read from the shader while
-		 *									they're bound for rendering.
 		 * @param[in]	drawOp				Type of geometry that will be drawn using the pipeline.
 		 * @param[in]	drawOp				Type of geometry that will be drawn using the pipeline.
 		 * @param[in]	vertexInput			State describing inputs to the vertex program.
 		 * @param[in]	vertexInput			State describing inputs to the vertex program.
 		 * @return							Vulkan graphics pipeline object.
 		 * @return							Vulkan graphics pipeline object.
@@ -106,20 +97,16 @@ namespace bs
 		 * @note	Thread safe.
 		 * @note	Thread safe.
 		 */
 		 */
 		VulkanPipeline* createPipeline(UINT32 deviceIdx, VulkanFramebuffer* framebuffer, bool readOnlyDepth, 
 		VulkanPipeline* createPipeline(UINT32 deviceIdx, VulkanFramebuffer* framebuffer, bool readOnlyDepth, 
-			RenderSurfaceMask loadMask, RenderSurfaceMask readMask, DrawOperationType drawOp, 
-			const SPtr<VulkanVertexInput>& vertexInput);
+			DrawOperationType drawOp, const SPtr<VulkanVertexInput>& vertexInput);
 
 
 		/**	Key uniquely identifying GPU pipelines. */
 		/**	Key uniquely identifying GPU pipelines. */
 		struct GpuPipelineKey
 		struct GpuPipelineKey
 		{
 		{
-			GpuPipelineKey(UINT32 framebufferId, UINT32 vertexInputId, bool readOnlyDepth, RenderSurfaceMask loadMask,
-						   RenderSurfaceMask readMask, DrawOperationType drawOp);
+			GpuPipelineKey(UINT32 framebufferId, UINT32 vertexInputId, bool readOnlyDepth, DrawOperationType drawOp);
 
 
 			UINT32 framebufferId;
 			UINT32 framebufferId;
 			UINT32 vertexInputId;
 			UINT32 vertexInputId;
 			bool readOnlyDepth;
 			bool readOnlyDepth;
-			RenderSurfaceMask loadMask;
-			RenderSurfaceMask readMask;
 			DrawOperationType drawOp;
 			DrawOperationType drawOp;
 		};
 		};
 
 

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

@@ -90,6 +90,26 @@ namespace bs
 		Vector<VkImageMemoryBarrier> imageBarriers;
 		Vector<VkImageMemoryBarrier> imageBarriers;
 		Vector<VkBufferMemoryBarrier> bufferBarriers;
 		Vector<VkBufferMemoryBarrier> bufferBarriers;
 	};
 	};
+
+	/** Bits that map to a specific part of a render target and signify whether it should be cleared or not. */
+	enum ClearMaskBits
+	{
+		CLEAR_NONE = 0,
+		CLEAR_COLOR0 = 1 << 0,
+		CLEAR_COLOR1 = 1 << 1,
+		CLEAR_COLOR2 = 1 << 2,
+		CLEAR_COLOR3 = 1 << 3,
+		CLEAR_COLOR4 = 1 << 4,
+		CLEAR_COLOR5 = 1 << 5,
+		CLEAR_COLOR6 = 1 << 6,
+		CLEAR_COLOR7 = 1 << 7,
+		CLEAR_STENCIL = 1 << 30,
+		CLEAR_DEPTH = 1 << 31,
+		CLEAR_ALL = 0xFF
+	};
+
+	typedef Flags<ClearMaskBits> ClearMask;
+	BS_FLAGS_OPERATORS(ClearMaskBits);
 }
 }
 
 
 /** Macro to get a procedure address based on a Vulkan instance. */
 /** Macro to get a procedure address based on a Vulkan instance. */

+ 226 - 115
Source/BansheeVulkanRenderAPI/Source/BsVulkanCommandBuffer.cpp

@@ -130,8 +130,8 @@ namespace bs
 		, mRenderTargetHeight(0), mRenderTargetDepthReadOnly(false), mRenderTargetLoadMask(RT_NONE), mGlobalQueueIdx(-1)
 		, mRenderTargetHeight(0), mRenderTargetDepthReadOnly(false), mRenderTargetLoadMask(RT_NONE), mGlobalQueueIdx(-1)
 		, mViewport(0.0f, 0.0f, 1.0f, 1.0f), mScissor(0, 0, 0, 0), mStencilRef(0), mDrawOp(DOT_TRIANGLE_LIST)
 		, mViewport(0.0f, 0.0f, 1.0f, 1.0f), mScissor(0, 0, 0, 0), mStencilRef(0), mDrawOp(DOT_TRIANGLE_LIST)
 		, mNumBoundDescriptorSets(0), mGfxPipelineRequiresBind(true), mCmpPipelineRequiresBind(true)
 		, mNumBoundDescriptorSets(0), mGfxPipelineRequiresBind(true), mCmpPipelineRequiresBind(true)
-		, mViewportRequiresBind(true), mStencilRefRequiresBind(true), mScissorRequiresBind(true), mVertexBuffersTemp()
-		, mVertexBufferOffsetsTemp()
+		, mViewportRequiresBind(true), mStencilRefRequiresBind(true), mScissorRequiresBind(true), mClearValues()
+		, mClearMask(), mVertexBuffersTemp(), mVertexBufferOffsetsTemp()
 	{
 	{
 		UINT32 maxBoundDescriptorSets = device.getDeviceProperties().limits.maxBoundDescriptorSets;
 		UINT32 maxBoundDescriptorSets = device.getDeviceProperties().limits.maxBoundDescriptorSets;
 		mDescriptorSetsTemp = (VkDescriptorSet*)bs_alloc(sizeof(VkDescriptorSet) * maxBoundDescriptorSets);
 		mDescriptorSetsTemp = (VkDescriptorSet*)bs_alloc(sizeof(VkDescriptorSet) * maxBoundDescriptorSets);
@@ -237,6 +237,10 @@ namespace bs
 	{
 	{
 		assert(mState == State::Recording);
 		assert(mState == State::Recording);
 
 
+		// If a clear is queued, execute the render pass with no additional instructions
+		if (mClearMask)
+			executeClearPass();
+
 		VkResult result = vkEndCommandBuffer(mCmdBuffer);
 		VkResult result = vkEndCommandBuffer(mCmdBuffer);
 		assert(result == VK_SUCCESS);
 		assert(result == VK_SUCCESS);
 
 
@@ -253,46 +257,16 @@ namespace bs
 			return;
 			return;
 		}
 		}
 
 
-		// Perform any queued layout transitions
-		auto createLayoutTransitionBarrier = [&](VulkanImage* image, ImageInfo& imageInfo)
+		if(mClearMask != CLEAR_NONE)
 		{
 		{
-			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 = imageInfo.accessFlags;
-			barrier.srcQueueFamilyIndex = mQueueFamily;
-			barrier.dstQueueFamilyIndex = mQueueFamily;
-			barrier.oldLayout = imageInfo.currentLayout;
-			barrier.newLayout = imageInfo.requiredLayout;
-			barrier.image = image->getHandle();
-			barrier.subresourceRange = imageInfo.range;
-
-			imageInfo.currentLayout = imageInfo.requiredLayout;
-		};
-
-		// Note: These layout transitions will contain transitions for offscreen framebuffer attachments (while they 
-		// transition to shader read-only layout). This can be avoided, since they're immediately used by the render pass
-		// as color attachments, making the layout change redundant.
-		for (auto& entry : mQueuedLayoutTransitions)
-		{
-			UINT32 imageInfoIdx = entry.second;
-			ImageInfo& imageInfo = mImageInfos[imageInfoIdx];
-
-			createLayoutTransitionBarrier(entry.first, imageInfo);
+			// If a previous clear is queued, but it doesn't match the rendered area, need to execute a separate pass
+			// just for it
+			Rect2I rtArea(0, 0, mRenderTargetWidth, mRenderTargetHeight);
+			if (mClearArea != rtArea)
+				executeClearPass();
 		}
 		}
 
 
-		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,
-							 0, 0, nullptr,
-							 0, nullptr,
-							 (UINT32)mLayoutTransitionBarriersTemp.size(), mLayoutTransitionBarriersTemp.data());
-
-		mLayoutTransitionBarriersTemp.clear();
+		executeLayoutTransitions();
 
 
 		// Check if any frame-buffer attachments are also used as shader inputs, in which case we make them read-only
 		// Check if any frame-buffer attachments are also used as shader inputs, in which case we make them read-only
 		RenderSurfaceMask readMask = RT_NONE;
 		RenderSurfaceMask readMask = RT_NONE;
@@ -327,17 +301,18 @@ namespace bs
 		VkRenderPassBeginInfo renderPassBeginInfo;
 		VkRenderPassBeginInfo renderPassBeginInfo;
 		renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
 		renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
 		renderPassBeginInfo.pNext = nullptr;
 		renderPassBeginInfo.pNext = nullptr;
-		renderPassBeginInfo.framebuffer = mFramebuffer->getFramebuffer(mRenderTargetLoadMask, readMask);
-		renderPassBeginInfo.renderPass = mFramebuffer->getRenderPass(mRenderTargetLoadMask, readMask);
+		renderPassBeginInfo.framebuffer = mFramebuffer->getFramebuffer(mRenderTargetLoadMask, readMask, mClearMask);
+		renderPassBeginInfo.renderPass = mFramebuffer->getRenderPass(mRenderTargetLoadMask, readMask, mClearMask);
 		renderPassBeginInfo.renderArea.offset.x = 0;
 		renderPassBeginInfo.renderArea.offset.x = 0;
 		renderPassBeginInfo.renderArea.offset.y = 0;
 		renderPassBeginInfo.renderArea.offset.y = 0;
 		renderPassBeginInfo.renderArea.extent.width = mRenderTargetWidth;
 		renderPassBeginInfo.renderArea.extent.width = mRenderTargetWidth;
 		renderPassBeginInfo.renderArea.extent.height = mRenderTargetHeight;
 		renderPassBeginInfo.renderArea.extent.height = mRenderTargetHeight;
-		renderPassBeginInfo.clearValueCount = 0;
-		renderPassBeginInfo.pClearValues = nullptr;
+		renderPassBeginInfo.clearValueCount = mFramebuffer->getNumAttachments();
+		renderPassBeginInfo.pClearValues = mClearValues.data();
 
 
 		vkCmdBeginRenderPass(mCmdBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
 		vkCmdBeginRenderPass(mCmdBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
 
 
+		mClearMask = CLEAR_NONE;
 		mState = State::RecordingRenderPass;
 		mState = State::RecordingRenderPass;
 	}
 	}
 
 
@@ -668,6 +643,12 @@ namespace bs
 		{
 		{
 			if (isInRenderPass())
 			if (isInRenderPass())
 				endRenderPass();
 				endRenderPass();
+			else
+			{
+				// If a clear is queued for previous FB, execute the render pass with no additional instructions
+				if (mClearMask)
+					executeClearPass();
+			}
 
 
 			// Reset flags that signal image usage
 			// Reset flags that signal image usage
 			for (auto& entry : mImages)
 			for (auto& entry : mImages)
@@ -694,101 +675,165 @@ namespace bs
 		if (buffers == 0 || mFramebuffer == nullptr)
 		if (buffers == 0 || mFramebuffer == nullptr)
 			return;
 			return;
 
 
-		VkClearAttachment attachments[BS_MAX_MULTIPLE_RENDER_TARGETS + 1];
-		UINT32 baseLayer = 0;
-
-		UINT32 attachmentIdx = 0;
-		if ((buffers & FBT_COLOR) != 0)
+		// Add clear command if currently in render pass
+		if (isInRenderPass())
 		{
 		{
-			UINT32 numColorAttachments = mFramebuffer->getNumColorAttachments();
-			for (UINT32 i = 0; i < numColorAttachments; i++)
+			VkClearAttachment attachments[BS_MAX_MULTIPLE_RENDER_TARGETS + 1];
+			UINT32 baseLayer = 0;
+
+			UINT32 attachmentIdx = 0;
+			if ((buffers & FBT_COLOR) != 0)
 			{
 			{
-				const VulkanFramebufferAttachment& attachment = mFramebuffer->getColorAttachment(i);
+				UINT32 numColorAttachments = mFramebuffer->getNumColorAttachments();
+				for (UINT32 i = 0; i < numColorAttachments; i++)
+				{
+					const VulkanFramebufferAttachment& attachment = mFramebuffer->getColorAttachment(i);
 
 
-				if (((1 << attachment.index) & targetMask) == 0)
-					continue;
+					if (((1 << attachment.index) & targetMask) == 0)
+						continue;
 
 
-				attachments[attachmentIdx].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-				attachments[attachmentIdx].colorAttachment = i;
+					attachments[attachmentIdx].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+					attachments[attachmentIdx].colorAttachment = i;
 
 
-				VkClearColorValue& colorValue = attachments[attachmentIdx].clearValue.color;
-				colorValue.float32[0] = color.r;
-				colorValue.float32[1] = color.g;
-				colorValue.float32[2] = color.b;
-				colorValue.float32[3] = color.a;
+					VkClearColorValue& colorValue = attachments[attachmentIdx].clearValue.color;
+					colorValue.float32[0] = color.r;
+					colorValue.float32[1] = color.g;
+					colorValue.float32[2] = color.b;
+					colorValue.float32[3] = color.a;
 
 
-				UINT32 curBaseLayer = attachment.baseLayer;
-				if (attachmentIdx == 0)
-					baseLayer = curBaseLayer;
-				else
-				{
-					if(baseLayer != curBaseLayer)
+					UINT32 curBaseLayer = attachment.baseLayer;
+					if (attachmentIdx == 0)
+						baseLayer = curBaseLayer;
+					else
 					{
 					{
-						// Note: This could be supported relatively easily: we would need to issue multiple separate
-						// clear commands for such framebuffers. 
-						LOGERR("Attempting to clear a texture that has multiple multi-layer surfaces with mismatching "
-								"starting layers. This is currently not supported.");
+						if (baseLayer != curBaseLayer)
+						{
+							// Note: This could be supported relatively easily: we would need to issue multiple separate
+							// clear commands for such framebuffers. 
+							LOGERR("Attempting to clear a texture that has multiple multi-layer surfaces with mismatching "
+								   "starting layers. This is currently not supported.");
+						}
 					}
 					}
-				}
 
 
-				attachmentIdx++;
+					attachmentIdx++;
+				}
 			}
 			}
-		}
 
 
-		if ((buffers & FBT_DEPTH) != 0 || (buffers & FBT_STENCIL) != 0)
-		{
-			if (mFramebuffer->hasDepthAttachment())
+			if ((buffers & FBT_DEPTH) != 0 || (buffers & FBT_STENCIL) != 0)
 			{
 			{
-				attachments[attachmentIdx].aspectMask = 0;
-
-				if ((buffers & FBT_DEPTH) != 0)
+				if (mFramebuffer->hasDepthAttachment())
 				{
 				{
-					attachments[attachmentIdx].aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
-					attachments[attachmentIdx].clearValue.depthStencil.depth = depth;
+					attachments[attachmentIdx].aspectMask = 0;
+
+					if ((buffers & FBT_DEPTH) != 0)
+					{
+						attachments[attachmentIdx].aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
+						attachments[attachmentIdx].clearValue.depthStencil.depth = depth;
+					}
+
+					if ((buffers & FBT_STENCIL) != 0)
+					{
+						attachments[attachmentIdx].aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
+						attachments[attachmentIdx].clearValue.depthStencil.stencil = stencil;
+					}
+
+					attachments[attachmentIdx].colorAttachment = 0;
+
+					UINT32 curBaseLayer = mFramebuffer->getDepthStencilAttachment().baseLayer;
+					if (attachmentIdx == 0)
+						baseLayer = curBaseLayer;
+					else
+					{
+						if (baseLayer != curBaseLayer)
+						{
+							// Note: This could be supported relatively easily: we would need to issue multiple separate
+							// clear commands for such framebuffers. 
+							LOGERR("Attempting to clear a texture that has multiple multi-layer surfaces with mismatching "
+								   "starting layers. This is currently not supported.");
+						}
+					}
+
+					attachmentIdx++;
 				}
 				}
+			}
+
+			UINT32 numAttachments = attachmentIdx;
+			if (numAttachments == 0)
+				return;
+
+			VkClearRect clearRect;
+			clearRect.baseArrayLayer = baseLayer;
+			clearRect.layerCount = mFramebuffer->getNumLayers();
+			clearRect.rect.offset.x = area.x;
+			clearRect.rect.offset.y = area.y;
+			clearRect.rect.extent.width = area.width;
+			clearRect.rect.extent.height = area.height;
 
 
-				if ((buffers & FBT_STENCIL) != 0)
+			vkCmdClearAttachments(mCmdBuffer, numAttachments, attachments, 1, &clearRect);
+		}
+		// Otherwise we use a render pass that performs a clear on begin
+		else
+		{
+			UINT32 attachmentIdx = 0;
+			ClearMask clearMask;
+			std::array<VkClearValue, BS_MAX_MULTIPLE_RENDER_TARGETS + 1> clearValues;
+
+			if ((buffers & FBT_COLOR) != 0)
+			{
+				UINT32 numColorAttachments = mFramebuffer->getNumColorAttachments();
+				for (UINT32 i = 0; i < numColorAttachments; i++)
 				{
 				{
-					attachments[attachmentIdx].aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
-					attachments[attachmentIdx].clearValue.depthStencil.stencil = stencil;
-				}
+					const VulkanFramebufferAttachment& attachment = mFramebuffer->getColorAttachment(i);
 
 
-				attachments[attachmentIdx].colorAttachment = 0;
+					if (((1 << attachment.index) & targetMask) == 0)
+						continue;
 
 
-				UINT32 curBaseLayer = mFramebuffer->getDepthStencilAttachment().baseLayer;
-				if (attachmentIdx == 0)
-					baseLayer = curBaseLayer;
-				else
+					clearMask |= (ClearMaskBits)(1 << attachment.index);
+
+					VkClearColorValue& colorValue = clearValues[attachmentIdx].color;
+					colorValue.float32[0] = color.r;
+					colorValue.float32[1] = color.g;
+					colorValue.float32[2] = color.b;
+					colorValue.float32[3] = color.a;
+
+					attachmentIdx++;
+				}
+			}
+
+			if ((buffers & FBT_DEPTH) != 0 || (buffers & FBT_STENCIL) != 0)
+			{
+				if (mFramebuffer->hasDepthAttachment())
 				{
 				{
-					if (baseLayer != curBaseLayer)
+					if ((buffers & FBT_DEPTH) != 0)
 					{
 					{
-						// Note: This could be supported relatively easily: we would need to issue multiple separate
-						// clear commands for such framebuffers. 
-						LOGERR("Attempting to clear a texture that has multiple multi-layer surfaces with mismatching "
-							   "starting layers. This is currently not supported.");
+						clearValues[attachmentIdx].depthStencil.depth = depth;
+						clearMask |= CLEAR_DEPTH;
+					}
+
+					if ((buffers & FBT_STENCIL) != 0)
+					{
+						clearValues[attachmentIdx].depthStencil.stencil = stencil;
+						clearMask |= CLEAR_STENCIL;
 					}
 					}
-				}
 
 
-				attachmentIdx++;
+					attachmentIdx++;
+				}
 			}
 			}
-		}
 
 
-		UINT32 numAttachments = attachmentIdx;
-		if (numAttachments == 0)
-			return;
-		
-		if (!isInRenderPass())
-			beginRenderPass();
+			UINT32 numAttachments = attachmentIdx;
+			if (numAttachments == 0)
+				return;
 
 
-		VkClearRect clearRect;
-		clearRect.baseArrayLayer = baseLayer;
-		clearRect.layerCount = mFramebuffer->getNumLayers();
-		clearRect.rect.offset.x = area.x;
-		clearRect.rect.offset.y = area.y;
-		clearRect.rect.extent.width = area.width;
-		clearRect.rect.extent.height = area.height;
-				
-		vkCmdClearAttachments(mCmdBuffer, numAttachments, attachments, 1, &clearRect);
+			// Some previous clear operation is already queued, execute it first
+			bool previousClearNeedsToFinish = (mClearMask & clearMask) != CLEAR_NONE;
+			
+			if(previousClearNeedsToFinish)
+				executeClearPass();
+
+			mClearMask = clearMask;
+			mClearValues = clearValues;
+			mClearArea = area;
+		}
 	}
 	}
 
 
 	void VulkanCmdBuffer::clearRenderTarget(UINT32 buffers, const Color& color, float depth, UINT16 stencil, UINT8 targetMask)
 	void VulkanCmdBuffer::clearRenderTarget(UINT32 buffers, const Color& color, float depth, UINT16 stencil, UINT8 targetMask)
@@ -955,8 +1000,7 @@ namespace bs
 		SPtr<VulkanVertexInput> vertexInput = VulkanVertexInputManager::instance().getVertexInfo(mVertexDecl, inputDecl);
 		SPtr<VulkanVertexInput> vertexInput = VulkanVertexInputManager::instance().getVertexInfo(mVertexDecl, inputDecl);
 
 
 		VulkanPipeline* pipeline = mGraphicsPipeline->getPipeline(mDevice.getIndex(), mFramebuffer,
 		VulkanPipeline* pipeline = mGraphicsPipeline->getPipeline(mDevice.getIndex(), mFramebuffer,
-																  mRenderTargetDepthReadOnly, mRenderTargetLoadMask,
-																  RT_NONE, mDrawOp, vertexInput);
+																  mRenderTargetDepthReadOnly, mDrawOp, vertexInput);
 
 
 		if (pipeline == nullptr)
 		if (pipeline == nullptr)
 			return false;
 			return false;
@@ -1047,6 +1091,73 @@ namespace bs
 		}
 		}
 	}
 	}
 
 
+	void VulkanCmdBuffer::executeLayoutTransitions()
+	{
+		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 = imageInfo.accessFlags;
+			barrier.srcQueueFamilyIndex = mQueueFamily;
+			barrier.dstQueueFamilyIndex = mQueueFamily;
+			barrier.oldLayout = imageInfo.currentLayout;
+			barrier.newLayout = imageInfo.requiredLayout;
+			barrier.image = image->getHandle();
+			barrier.subresourceRange = imageInfo.range;
+
+			imageInfo.currentLayout = imageInfo.requiredLayout;
+		};
+
+		// Note: These layout transitions will contain transitions for offscreen framebuffer attachments (while they 
+		// transition to shader read-only layout). This can be avoided, since they're immediately used by the render pass
+		// as color attachments, making the layout change redundant.
+		for (auto& entry : mQueuedLayoutTransitions)
+		{
+			UINT32 imageInfoIdx = entry.second;
+			ImageInfo& imageInfo = mImageInfos[imageInfoIdx];
+
+			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,
+							 0, 0, nullptr,
+							 0, nullptr,
+							 (UINT32)mLayoutTransitionBarriersTemp.size(), mLayoutTransitionBarriersTemp.data());
+
+		mLayoutTransitionBarriersTemp.clear();
+	}
+
+	void VulkanCmdBuffer::executeClearPass()
+	{
+		assert(mState == State::Recording);
+
+		executeLayoutTransitions();
+
+		VkRenderPassBeginInfo renderPassBeginInfo;
+		renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+		renderPassBeginInfo.pNext = nullptr;
+		renderPassBeginInfo.framebuffer = mFramebuffer->getFramebuffer(RT_NONE, RT_NONE, mClearMask);
+		renderPassBeginInfo.renderPass = mFramebuffer->getRenderPass(RT_NONE, RT_NONE, mClearMask);
+		renderPassBeginInfo.renderArea.offset.x = mClearArea.x;
+		renderPassBeginInfo.renderArea.offset.y = mClearArea.y;
+		renderPassBeginInfo.renderArea.extent.width = mClearArea.width;
+		renderPassBeginInfo.renderArea.extent.height = mClearArea.height;
+		renderPassBeginInfo.clearValueCount = mFramebuffer->getNumAttachments();
+		renderPassBeginInfo.pClearValues = mClearValues.data();
+
+		vkCmdBeginRenderPass(mCmdBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+		vkCmdEndRenderPass(mCmdBuffer);
+
+		mClearMask = CLEAR_NONE;
+	}
+
 	void VulkanCmdBuffer::draw(UINT32 vertexOffset, UINT32 vertexCount, UINT32 instanceCount)
 	void VulkanCmdBuffer::draw(UINT32 vertexOffset, UINT32 vertexCount, UINT32 instanceCount)
 	{
 	{
 		if (!isReadyForRender())
 		if (!isReadyForRender())

+ 32 - 15
Source/BansheeVulkanRenderAPI/Source/BsVulkanFramebuffer.cpp

@@ -7,8 +7,9 @@
 
 
 namespace bs
 namespace bs
 {
 {
-	VulkanFramebuffer::VariantKey::VariantKey(RenderSurfaceMask loadMask, RenderSurfaceMask readMask)
-		:loadMask(loadMask), readMask(readMask)
+	VulkanFramebuffer::VariantKey::VariantKey(RenderSurfaceMask loadMask, RenderSurfaceMask readMask, 
+		ClearMask clearMask)
+		:loadMask(loadMask), readMask(readMask), clearMask(clearMask)
 	{ }
 	{ }
 
 
 	size_t VulkanFramebuffer::VariantKey::HashFunction::operator()(const VariantKey& v) const
 	size_t VulkanFramebuffer::VariantKey::HashFunction::operator()(const VariantKey& v) const
@@ -16,6 +17,7 @@ namespace bs
 		size_t hash = 0;
 		size_t hash = 0;
 		hash_combine(hash, v.readMask);
 		hash_combine(hash, v.readMask);
 		hash_combine(hash, v.loadMask);
 		hash_combine(hash, v.loadMask);
+		hash_combine(hash, v.clearMask);
 
 
 		return hash;
 		return hash;
 	}
 	}
@@ -23,7 +25,7 @@ namespace bs
 	bool VulkanFramebuffer::VariantKey::EqualFunction::operator()(const VariantKey& lhs,
 	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;
+		return lhs.loadMask == rhs.loadMask && lhs.readMask == rhs.readMask && lhs.clearMask == rhs.clearMask;
 	}
 	}
 
 
 	UINT32 VulkanFramebuffer::sNextValidId = 1;
 	UINT32 VulkanFramebuffer::sNextValidId = 1;
@@ -159,7 +161,7 @@ namespace bs
 		mFramebufferCI.height = desc.height;
 		mFramebufferCI.height = desc.height;
 		mFramebufferCI.layers = desc.layers;
 		mFramebufferCI.layers = desc.layers;
 
 
-		mDefault = createVariant(RT_NONE, RT_NONE);		
+		mDefault = createVariant(RT_NONE, RT_NONE, CLEAR_NONE);		
 	}
 	}
 
 
 	VulkanFramebuffer::~VulkanFramebuffer()
 	VulkanFramebuffer::~VulkanFramebuffer()
@@ -177,7 +179,7 @@ namespace bs
 	}
 	}
 
 
 	VulkanFramebuffer::Variant VulkanFramebuffer::createVariant(RenderSurfaceMask loadMask, 
 	VulkanFramebuffer::Variant VulkanFramebuffer::createVariant(RenderSurfaceMask loadMask, 
-		RenderSurfaceMask readMask) const
+		RenderSurfaceMask readMask, ClearMask clearMask) const
 	{
 	{
 		for (UINT32 i = 0; i < mNumColorAttachments; i++)
 		for (UINT32 i = 0; i < mNumColorAttachments; i++)
 		{
 		{
@@ -190,6 +192,11 @@ namespace bs
 				attachmentDesc.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
 				attachmentDesc.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
 				attachmentDesc.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
 				attachmentDesc.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
 			}
 			}
+			else if (clearMask.isSet((ClearMaskBits)(i << attachment.index)))
+			{
+				attachmentDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+				attachmentDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+			}
 			else
 			else
 			{
 			{
 				attachmentDesc.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
 				attachmentDesc.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
@@ -215,8 +222,16 @@ namespace bs
 			}
 			}
 			else
 			else
 			{
 			{
-				attachmentDesc.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-				attachmentDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+				if(clearMask.isSet(CLEAR_DEPTH))
+					attachmentDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+				else
+					attachmentDesc.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+
+				if(clearMask.isSet(CLEAR_STENCIL))
+					attachmentDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+				else
+					attachmentDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+
 				attachmentDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
 				attachmentDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
 			}
 			}
 
 
@@ -240,33 +255,35 @@ namespace bs
 		return variant;
 		return variant;
 	}
 	}
 
 
-	VkRenderPass VulkanFramebuffer::getRenderPass(RenderSurfaceMask loadMask, RenderSurfaceMask readMask) const
+	VkRenderPass VulkanFramebuffer::getRenderPass(RenderSurfaceMask loadMask, RenderSurfaceMask readMask,
+												  ClearMask clearMask) const
 	{
 	{
-		if (loadMask == RT_NONE && readMask == RT_NONE)
+		if (loadMask == RT_NONE && readMask == RT_NONE && clearMask == CLEAR_NONE)
 			return mDefault.renderPass;
 			return mDefault.renderPass;
 
 
-		VariantKey key(loadMask, readMask);
+		VariantKey key(loadMask, readMask, clearMask);
 		auto iterFind = mVariants.find(key);
 		auto iterFind = mVariants.find(key);
 		if (iterFind != mVariants.end())
 		if (iterFind != mVariants.end())
 			return iterFind->second.renderPass;
 			return iterFind->second.renderPass;
 
 
-		Variant newVariant = createVariant(loadMask, readMask);
+		Variant newVariant = createVariant(loadMask, readMask, clearMask);
 		mVariants[key] = newVariant;
 		mVariants[key] = newVariant;
 
 
 		return newVariant.renderPass;
 		return newVariant.renderPass;
 	}
 	}
 
 
-	VkFramebuffer VulkanFramebuffer::getFramebuffer(RenderSurfaceMask loadMask, RenderSurfaceMask readMask) const
+	VkFramebuffer VulkanFramebuffer::getFramebuffer(RenderSurfaceMask loadMask, RenderSurfaceMask readMask,
+													ClearMask clearMask) const
 	{
 	{
-		if (loadMask == RT_NONE && readMask == RT_NONE)
+		if (loadMask == RT_NONE && readMask == RT_NONE && clearMask == CLEAR_NONE)
 			return mDefault.framebuffer;
 			return mDefault.framebuffer;
 
 
-		VariantKey key(loadMask, readMask);
+		VariantKey key(loadMask, readMask, clearMask);
 		auto iterFind = mVariants.find(key);
 		auto iterFind = mVariants.find(key);
 		if (iterFind != mVariants.end())
 		if (iterFind != mVariants.end())
 			return iterFind->second.framebuffer;
 			return iterFind->second.framebuffer;
 
 
-		Variant newVariant = createVariant(loadMask, readMask);
+		Variant newVariant = createVariant(loadMask, readMask, clearMask);
 		mVariants[key] = newVariant;
 		mVariants[key] = newVariant;
 
 
 		return newVariant.framebuffer;
 		return newVariant.framebuffer;

+ 11 - 19
Source/BansheeVulkanRenderAPI/Source/BsVulkanGpuPipelineState.cpp

@@ -32,10 +32,9 @@ namespace bs
 	}
 	}
 
 
 	VulkanGraphicsPipelineStateCore::GpuPipelineKey::GpuPipelineKey(
 	VulkanGraphicsPipelineStateCore::GpuPipelineKey::GpuPipelineKey(
-		UINT32 framebufferId, UINT32 vertexInputId, bool readOnlyDepth, RenderSurfaceMask loadMask, 
-		RenderSurfaceMask readMask, DrawOperationType drawOp)
+		UINT32 framebufferId, UINT32 vertexInputId, bool readOnlyDepth, DrawOperationType drawOp)
 		: framebufferId(framebufferId), vertexInputId(vertexInputId), readOnlyDepth(readOnlyDepth)
 		: framebufferId(framebufferId), vertexInputId(vertexInputId), readOnlyDepth(readOnlyDepth)
-		, loadMask(loadMask), readMask(readMask), drawOp(drawOp)
+		, drawOp(drawOp)
 	{
 	{
 		
 		
 	}
 	}
@@ -46,8 +45,6 @@ namespace bs
 		hash_combine(hash, key.framebufferId);
 		hash_combine(hash, key.framebufferId);
 		hash_combine(hash, key.vertexInputId);
 		hash_combine(hash, key.vertexInputId);
 		hash_combine(hash, key.readOnlyDepth);
 		hash_combine(hash, key.readOnlyDepth);
-		hash_combine(hash, key.loadMask);
-		hash_combine(hash, key.readMask);
 		hash_combine(hash, key.drawOp);
 		hash_combine(hash, key.drawOp);
 
 
 		return hash;
 		return hash;
@@ -64,12 +61,6 @@ namespace bs
 		if (a.readOnlyDepth != b.readOnlyDepth)
 		if (a.readOnlyDepth != b.readOnlyDepth)
 			return false;
 			return false;
 
 
-		if (a.loadMask != b.loadMask)
-			return false;
-
-		if (a.readMask != b.readMask)
-			return false;
-
 		if (a.drawOp != b.drawOp)
 		if (a.drawOp != b.drawOp)
 			return false;
 			return false;
 
 
@@ -325,23 +316,22 @@ namespace bs
 	}
 	}
 
 
 	VulkanPipeline* VulkanGraphicsPipelineStateCore::getPipeline(
 	VulkanPipeline* VulkanGraphicsPipelineStateCore::getPipeline(
-		UINT32 deviceIdx, VulkanFramebuffer* framebuffer, bool readOnlyDepth, RenderSurfaceMask loadMask, 
-		RenderSurfaceMask readMask, DrawOperationType drawOp, const SPtr<VulkanVertexInput>& vertexInput)
+		UINT32 deviceIdx, VulkanFramebuffer* framebuffer, bool readOnlyDepth, DrawOperationType drawOp, 
+			const SPtr<VulkanVertexInput>& vertexInput)
 	{
 	{
 		Lock(mMutex);
 		Lock(mMutex);
 
 
 		if (mPerDeviceData[deviceIdx].device == nullptr)
 		if (mPerDeviceData[deviceIdx].device == nullptr)
 			return nullptr;
 			return nullptr;
 
 
-		GpuPipelineKey key(framebuffer->getId(), vertexInput->getId(), readOnlyDepth, loadMask, readMask, drawOp);
+		GpuPipelineKey key(framebuffer->getId(), vertexInput->getId(), readOnlyDepth, drawOp);
 
 
 		PerDeviceData& perDeviceData = mPerDeviceData[deviceIdx];
 		PerDeviceData& perDeviceData = mPerDeviceData[deviceIdx];
 		auto iterFind = perDeviceData.pipelines.find(key);
 		auto iterFind = perDeviceData.pipelines.find(key);
 		if (iterFind != perDeviceData.pipelines.end())
 		if (iterFind != perDeviceData.pipelines.end())
 			return iterFind->second;
 			return iterFind->second;
 
 
-		VulkanPipeline* newPipeline = createPipeline(deviceIdx, framebuffer, readOnlyDepth, loadMask, readMask,
-			drawOp, vertexInput);
+		VulkanPipeline* newPipeline = createPipeline(deviceIdx, framebuffer, readOnlyDepth, drawOp, vertexInput);
 		perDeviceData.pipelines[key] = newPipeline;
 		perDeviceData.pipelines[key] = newPipeline;
 
 
 		return newPipeline;
 		return newPipeline;
@@ -377,8 +367,7 @@ namespace bs
 	}
 	}
 
 
 	VulkanPipeline* VulkanGraphicsPipelineStateCore::createPipeline(UINT32 deviceIdx, VulkanFramebuffer* framebuffer,
 	VulkanPipeline* VulkanGraphicsPipelineStateCore::createPipeline(UINT32 deviceIdx, VulkanFramebuffer* framebuffer,
-		bool readOnlyDepth, RenderSurfaceMask loadMask, RenderSurfaceMask readMask, DrawOperationType drawOp, 
-		const SPtr<VulkanVertexInput>& vertexInput)
+		bool readOnlyDepth, DrawOperationType drawOp, const SPtr<VulkanVertexInput>& vertexInput)
 	{
 	{
 		mInputAssemblyInfo.topology = VulkanUtility::getDrawOp(drawOp);
 		mInputAssemblyInfo.topology = VulkanUtility::getDrawOp(drawOp);
 		mTesselationInfo.patchControlPoints = 3; // Not provided by our shaders for now
 		mTesselationInfo.patchControlPoints = 3; // Not provided by our shaders for now
@@ -411,7 +400,10 @@ namespace bs
 			mDepthStencilInfo.back.depthFailOp = VK_STENCIL_OP_KEEP;
 			mDepthStencilInfo.back.depthFailOp = VK_STENCIL_OP_KEEP;
 		}
 		}
 
 
-		mPipelineInfo.renderPass = framebuffer->getRenderPass(loadMask, readMask);
+		// Note: We can use the default render pass here (default clear/load/read flags), even though that might not be the
+		// exact one currently bound. This is because load/store operations and layout transitions are allowed to differ
+		// (as per spec 7.2., such render passes are considered compatible).
+		mPipelineInfo.renderPass = framebuffer->getRenderPass(RT_NONE, RT_NONE, CLEAR_NONE);
 		mPipelineInfo.layout = mPerDeviceData[deviceIdx].pipelineLayout;
 		mPipelineInfo.layout = mPerDeviceData[deviceIdx].pipelineLayout;
 		mPipelineInfo.pVertexInputState = vertexInput->getCreateInfo();
 		mPipelineInfo.pVertexInputState = vertexInput->getCreateInfo();