From 0f0ce67aa5d23553bd9309bfd326ebf2c496a162 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:02:18 +0200 Subject: [PATCH] Latte: Add support for VK_EXT_attachment_feedback_loop Requires both VK_EXT_attachment_feedback_loop and VK_EXT_attachment_feedback_loop_dynamic_state to be provided by the driver. Otherwise it will fall back to the old code. Main benefit is for improving performance as it makes barriers cheaper and doesn't force us to unnecessarily interrupt renderpasses --- dependencies/Vulkan-Headers | 2 +- .../HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp | 65 ++++++----- .../HW/Latte/Renderer/Vulkan/CachedFBOVk.h | 23 +++- .../Latte/Renderer/Vulkan/LatteTextureVk.cpp | 6 + .../HW/Latte/Renderer/Vulkan/LatteTextureVk.h | 5 +- .../Renderer/Vulkan/TextureReadbackVk.cpp | 6 +- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h | 3 + .../Vulkan/VulkanPipelineCompiler.cpp | 6 +- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 108 +++++++++++++++--- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 8 +- .../Renderer/Vulkan/VulkanRendererCore.cpp | 59 +++++++--- .../Renderer/Vulkan/VulkanSurfaceCopy.cpp | 10 +- 12 files changed, 225 insertions(+), 76 deletions(-) diff --git a/dependencies/Vulkan-Headers b/dependencies/Vulkan-Headers index 9b9fd871..01393c3d 160000 --- a/dependencies/Vulkan-Headers +++ b/dependencies/Vulkan-Headers @@ -1 +1 @@ -Subproject commit 9b9fd871b08110cd8f0b74e721b03213d9cc3081 +Subproject commit 01393c3df0e5285b54ee6527466513f9e614be94 diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp index 8a999000..20c88b28 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp @@ -126,7 +126,10 @@ void CachedFBOVk::InitDynamicRenderingData() continue; } else + { + m_vkColorAttachments[i].imageLayout = static_cast(buffer.texture->baseTexture)->GetDefaultLayout(); m_vkColorAttachments[i].imageView = cbView->m_textureImageView; + } } m_vkRenderingInfo.pColorAttachments = m_vkColorAttachments; @@ -167,6 +170,9 @@ void CachedFBOVk::InitDynamicRenderingData() // setup depth and stencil attachment if (depthStencilView) { + auto depthTexVk = static_cast(depthBuffer.texture->baseTexture); + m_vkDepthAttachment.imageLayout = depthTexVk->GetDefaultLayout(); + m_vkStencilAttachment.imageLayout = depthTexVk->GetDefaultLayout(); m_vkDepthAttachment.imageView = depthStencilView->m_textureImageView; m_vkDepthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; m_vkDepthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; @@ -189,41 +195,46 @@ void CachedFBOVk::InitDynamicRenderingData() m_vkRenderingInfo.layerCount = 1; } +static uint32 s_selfDependencyCheckIndex = 1; -uint32 s_currentCollisionCheckIndex = 1; - -bool CachedFBOVk::CheckForCollision(VkDescriptorSetInfo* vsDS, VkDescriptorSetInfo* gsDS, VkDescriptorSetInfo* psDS) const +CachedFBOVk::RendertargetSelfDependencyMask CachedFBOVk::CheckForSelfDependency(VkDescriptorSetInfo* vsDS, VkDescriptorSetInfo* gsDS, VkDescriptorSetInfo* psDS) const { - s_currentCollisionCheckIndex++; - const uint32 curColIndex = s_currentCollisionCheckIndex; - for (auto& itr : m_referencedTextures) + s_selfDependencyCheckIndex++; + const uint32 curColIndex = s_selfDependencyCheckIndex; + for (auto& colorAttachment : colorBuffer) { - LatteTextureVk* vkTex = (LatteTextureVk*)itr; - vkTex->m_collisionCheckIndex = curColIndex; - } - if (vsDS) - { - for (auto& itr : vsDS->list_fboCandidates) + if (colorAttachment.texture) { - if (itr->m_collisionCheckIndex == curColIndex) - return true; + LatteTextureVk* vkTex = static_cast(colorAttachment.texture->baseTexture); + vkTex->m_selfDependencyCheckIndex = curColIndex; + vkTex->m_selfDependencyCheckAspectMask = VK_IMAGE_ASPECT_COLOR_BIT; } } - if (gsDS) + if (depthBuffer.texture) { - for (auto& itr : gsDS->list_fboCandidates) - { - if (itr->m_collisionCheckIndex == curColIndex) - return true; - } + LatteTextureVk* vkTex = static_cast(depthBuffer.texture->baseTexture); + vkTex->m_selfDependencyCheckIndex = curColIndex; + vkTex->m_selfDependencyCheckAspectMask = depthBuffer.hasStencil ? (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT): VK_IMAGE_ASPECT_DEPTH_BIT; } - if (psDS) + + auto getSelfDependencyMask = [curColIndex](VkDescriptorSetInfo* ds) -> VkImageAspectFlags { - for (auto& itr : psDS->list_fboCandidates) + VkImageAspectFlags aspectMask = 0; + if (!ds) + return aspectMask; + for (auto& itr : ds->list_fboCandidates) { - if (itr->m_collisionCheckIndex == curColIndex) - return true; + if (itr->m_selfDependencyCheckIndex == curColIndex) + aspectMask |= itr->m_selfDependencyCheckAspectMask; } - } - return false; -} \ No newline at end of file + return aspectMask; + }; + + RendertargetSelfDependencyMask selfDepInfo{}; + VkImageAspectFlags vertexAspectFlags = getSelfDependencyMask(vsDS); + VkImageAspectFlags geometryAspectFlags = getSelfDependencyMask(gsDS); + VkImageAspectFlags pixelAspectFlags = getSelfDependencyMask(psDS); + selfDepInfo.aspectMaskFlags = vertexAspectFlags | geometryAspectFlags | pixelAspectFlags; + selfDepInfo.hasNonPixelSelfDependency = (vertexAspectFlags | geometryAspectFlags) != 0; + return selfDepInfo; +} diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.h index bf72996e..5a603579 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.h @@ -51,8 +51,29 @@ public: [[nodiscard]] const VkExtent2D& GetExtend() const { return m_extend;} + struct RendertargetSelfDependencyMask + { + VkImageAspectFlags aspectMaskFlags{}; // aspect flags which are simultaneously sampled and written + bool hasNonPixelSelfDependency{false}; + + VkImageAspectFlags GetAspectMask() const + { + return aspectMaskFlags; + } + + bool HasSelfDependency() const + { + return GetAspectMask() != 0; + } + + bool HasVertexOrGeometrySelfDependency() const + { + return hasNonPixelSelfDependency; // vertex or geometry shader samples texture which is written to + } + }; + // checks if any of the sampled textures are output by the FBO - bool CheckForCollision(VkDescriptorSetInfo* vsDS, VkDescriptorSetInfo* gsDS, VkDescriptorSetInfo* psDS) const; + RendertargetSelfDependencyMask CheckForSelfDependency(VkDescriptorSetInfo* vsDS, VkDescriptorSetInfo* gsDS, VkDescriptorSetInfo* psDS) const; private: diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp index 88d3291d..c6a9b274 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp @@ -67,6 +67,12 @@ LatteTextureVk::LatteTextureVk(class VulkanRenderer* vkRenderer, Latte::E_DIM di imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; } + if (m_vkr->UseAttachmentFeedbackLoop() && (imageInfo.usage & (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)) != 0) + { + imageInfo.usage |= VK_IMAGE_USAGE_ATTACHMENT_FEEDBACK_LOOP_BIT_EXT; + m_defaultLayout = VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT; + } + if (dim == Latte::E_DIM::DIM_2D) imageInfo.imageType = VK_IMAGE_TYPE_2D; else if (dim == Latte::E_DIM::DIM_1D) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h index 612e2e70..b0d74879 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h @@ -20,6 +20,7 @@ public: VkFormat GetFormat() const { return vkObjTex->m_format; } VkImageAspectFlags GetImageAspect() const { return vkObjTex->m_imageAspect; } + VkImageLayout GetDefaultLayout() const { return m_defaultLayout; } VkImageLayout GetImageLayout(VkImageSubresource& subresource) { @@ -83,12 +84,14 @@ public: uint64 m_vkFlushIndex_read{}; uint64 m_vkFlushIndex_write{}; - uint32 m_collisionCheckIndex{}; // used to track if texture is being both sampled and output to during drawcall + uint32 m_selfDependencyCheckIndex{}; // used to track if texture is being both sampled and output to during drawcall + VkImageAspectFlags m_selfDependencyCheckAspectMask{}; private: class VulkanRenderer* m_vkr; VKRObjectTexture* vkObjTex{}; + VkImageLayout m_defaultLayout{ VK_IMAGE_LAYOUT_GENERAL }; // the targetted long term layout of the texture. Can be either VK_IMAGE_LAYOUT_GENERAL or VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT for potential rendertargets if supported std::vector m_layouts; uint32 m_layoutsMips; uint32 m_layoutsDepth; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/TextureReadbackVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/TextureReadbackVk.cpp index bce23b59..81281ebc 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/TextureReadbackVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/TextureReadbackVk.cpp @@ -122,15 +122,15 @@ void LatteTextureReadbackInfoVk::StartTransfer() const auto renderer = VulkanRenderer::GetInstance(); renderer->draw_endRenderPass(); - renderer->barrier_image(baseTexture, region.imageSubresource, VK_IMAGE_LAYOUT_GENERAL); + renderer->barrier_image(baseTexture, region.imageSubresource, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); renderer->barrier_sequentializeTransfer(); - vkCmdCopyImageToBuffer(renderer->getCurrentCommandBuffer(), baseTexture->GetImageObj()->m_image, VK_IMAGE_LAYOUT_GENERAL, m_buffer, 1, ®ion); + vkCmdCopyImageToBuffer(renderer->getCurrentCommandBuffer(), baseTexture->GetImageObj()->m_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, m_buffer, 1, ®ion); renderer->barrier_sequentializeTransfer(); - renderer->barrier_image(baseTexture, region.imageSubresource, VK_IMAGE_LAYOUT_GENERAL); // make sure transfer is finished before image is modified + renderer->barrier_image(baseTexture, region.imageSubresource, baseTexture->GetDefaultLayout()); // make sure transfer is finished before image is modified renderer->barrier_bufferRange(m_buffer, m_buffer_offset, m_image_size); // make sure transfer is finished before result is read m_associatedCommandBufferId = renderer->GetCurrentCommandBufferId(); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h index c44a7b1c..f503c108 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h @@ -189,6 +189,9 @@ VKFUNC_DEVICE(vkCmdPipelineBarrier2KHR); VKFUNC_DEVICE(vkCmdBeginRenderingKHR); VKFUNC_DEVICE(vkCmdEndRenderingKHR); +// ext_attachment_feedback_loop_dynamic_state +VKFUNC_DEVICE(vkCmdSetAttachmentFeedbackLoopEnableEXT); + // khr_present_wait VKFUNC_DEVICE(vkWaitForPresentKHR); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp index ca166e69..2800b81e 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp @@ -815,6 +815,10 @@ void PipelineCompiler::InitDynamicState(PipelineInfo* pipelineInfo, bool usesBle dynamicStates.emplace_back(VK_DYNAMIC_STATE_DEPTH_BIAS); pipelineInfo->usesDepthBias = true; } + if (VulkanRenderer::GetInstance()->UseAttachmentFeedbackLoop()) + { + dynamicStates.emplace_back(VK_DYNAMIC_STATE_ATTACHMENT_FEEDBACK_LOOP_ENABLE_EXT); + } dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; dynamicState.dynamicStateCount = dynamicStates.size(); @@ -1135,4 +1139,4 @@ void PipelineCompiler::CompileThreadPool_Stop() void PipelineCompiler::CompileThreadPool_QueueCompilation(PipelineCompiler* v) { s_pipelineCompileRequests.push(v); -} \ No newline at end of file +} diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 136cc76b..bf9ae10c 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -50,7 +50,9 @@ const std::vector kOptionalDeviceExtensions = VK_KHR_PRESENT_WAIT_EXTENSION_NAME, VK_KHR_PRESENT_ID_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME, - VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME + VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME, + VK_EXT_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_EXTENSION_NAME, + VK_EXT_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_EXTENSION_NAME }; const std::vector kRequiredDeviceExtensions = @@ -271,6 +273,22 @@ void VulkanRenderer::GetDeviceFeatures() prevStruct = &pprf; } + VkPhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT attachmentFeedbackLoopDynamicStateFeature{}; + if (m_featureControl.deviceExtensions.attachment_feedback_loop_dynamic_state) + { + attachmentFeedbackLoopDynamicStateFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_FEATURES_EXT; + attachmentFeedbackLoopDynamicStateFeature.pNext = prevStruct; + prevStruct = &attachmentFeedbackLoopDynamicStateFeature; + } + + VkPhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT attachmentFeedbackLoopLayoutFeature{}; + if (m_featureControl.deviceExtensions.attachment_feedback_loop_layout) + { + attachmentFeedbackLoopLayoutFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_FEATURES_EXT; + attachmentFeedbackLoopLayoutFeature.pNext = prevStruct; + prevStruct = &attachmentFeedbackLoopLayoutFeature; + } + VkPhysicalDeviceFeatures2 physicalDeviceFeatures2{}; physicalDeviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; physicalDeviceFeatures2.pNext = prevStruct; @@ -330,6 +348,12 @@ void VulkanRenderer::GetDeviceFeatures() if ( pprf.pipelineRobustness != VK_TRUE ) m_featureControl.deviceExtensions.pipeline_robustness = false; } + if (m_featureControl.deviceExtensions.attachment_feedback_loop_layout) + m_featureControl.deviceExtensions.attachment_feedback_loop_layout = attachmentFeedbackLoopLayoutFeature.attachmentFeedbackLoopLayout == VK_TRUE; + if (m_featureControl.deviceExtensions.attachment_feedback_loop_dynamic_state && m_featureControl.deviceExtensions.attachment_feedback_loop_layout) + m_featureControl.deviceExtensions.attachment_feedback_loop_dynamic_state = attachmentFeedbackLoopDynamicStateFeature.attachmentFeedbackLoopDynamicState == VK_TRUE; + if (!UseAttachmentFeedbackLoop()) + cemuLog_log(LogType::Force, "VK_EXT_attachment_feedback_loop_layout(_dynamic_state) not supported"); // get limits m_featureControl.limits.minUniformBufferOffsetAlignment = std::max(prop2.properties.limits.minUniformBufferOffsetAlignment, (VkDeviceSize)4); m_featureControl.limits.nonCoherentAtomSize = std::max(prop2.properties.limits.nonCoherentAtomSize, (VkDeviceSize)4); @@ -698,6 +722,21 @@ VulkanRenderer::VulkanRenderer() deviceExtensionFeatures = &pipelineRobustnessFeature; pipelineRobustnessFeature.pipelineRobustness = VK_TRUE; } + // enable attachment feedback loop layout + dynamic state if both are supported + VkPhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT attachmentFeedbackLoopLayoutFeature{}; + VkPhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT attachmentFeedbackLoopDynamicStateFeature{}; + if (UseAttachmentFeedbackLoop()) + { + attachmentFeedbackLoopLayoutFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_FEATURES_EXT; + attachmentFeedbackLoopLayoutFeature.pNext = deviceExtensionFeatures; + deviceExtensionFeatures = &attachmentFeedbackLoopLayoutFeature; + attachmentFeedbackLoopLayoutFeature.attachmentFeedbackLoopLayout = VK_TRUE; + + attachmentFeedbackLoopDynamicStateFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_FEATURES_EXT; + attachmentFeedbackLoopDynamicStateFeature.pNext = deviceExtensionFeatures; + deviceExtensionFeatures = &attachmentFeedbackLoopDynamicStateFeature; + attachmentFeedbackLoopDynamicStateFeature.attachmentFeedbackLoopDynamicState = VK_TRUE; + } std::vector used_extensions; VkDeviceCreateInfo createInfo = CreateDeviceCreateInfo(queueCreateInfos, deviceFeatures, deviceExtensionFeatures, used_extensions); @@ -1122,7 +1161,7 @@ void VulkanRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool pad range.mipLevel = 0; range.baseArrayLayer = texViewVk->firstSlice; range.layerCount = 1; - barrier_image(baseImageTex, range, VK_IMAGE_LAYOUT_GENERAL); + barrier_image(baseImageTex, range, baseImageTex->GetDefaultLayout()); } format = VK_FORMAT_R8G8B8A8_UNORM; @@ -1178,6 +1217,10 @@ void VulkanRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool pad } vkCmdCopyImageToBuffer(m_state.currentCommandBuffer, dumpImage, VK_IMAGE_LAYOUT_GENERAL, buffer, 1, ®ion); + if (dumpImage == baseImage) + { + barrier_image(baseImageTex, region.imageSubresource, baseImageTex->GetDefaultLayout()); + } SubmitCommandBuffer(); WaitCommandBufferFinished(GetCurrentCommandBufferId()); @@ -1282,6 +1325,11 @@ VkDeviceCreateInfo VulkanRenderer::CreateDeviceCreateInfo(const std::vector(texViewVk->baseTexture)->GetDefaultLayout(); imageInfo.imageView = texViewVk->GetViewRGBA()->m_textureImageView; imageInfo.sampler = texViewVk->GetDefaultTextureSampler(useLinearTexFilter); @@ -3395,7 +3445,7 @@ void VulkanRenderer::texture_clearSlice(LatteTexture* hostTexture, sint32 sliceI else { cemu_assert_debug(vkTexture->dim != Latte::E_DIM::DIM_3D); - ClearColorImage(vkTexture, sliceIndex, mipIndex, { 0,0,0,0 }, VK_IMAGE_LAYOUT_GENERAL); + ClearColorImage(vkTexture, sliceIndex, mipIndex, { 0,0,0,0 }, vkTexture->GetDefaultLayout()); } } @@ -3406,7 +3456,7 @@ void VulkanRenderer::texture_clearColorSlice(LatteTexture* hostTexture, sint32 s { cemu_assert_unimplemented(); } - ClearColorImage(vkTexture, sliceIndex, mipIndex, {r, g, b, a}, VK_IMAGE_LAYOUT_GENERAL); + ClearColorImage(vkTexture, sliceIndex, mipIndex, {r, g, b, a}, vkTexture->GetDefaultLayout()); } void VulkanRenderer::texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) @@ -3447,7 +3497,7 @@ void VulkanRenderer::texture_clearDepthSlice(LatteTexture* hostTexture, uint32 s vkCmdClearDepthStencilImage(m_state.currentCommandBuffer, imageObj->m_image, VK_IMAGE_LAYOUT_GENERAL, &depthStencilValue, 1, &range); - barrier_image(vkTexture, subresourceRange, VK_IMAGE_LAYOUT_GENERAL); + barrier_image(vkTexture, subresourceRange, vkTexture->GetDefaultLayout()); } void VulkanRenderer::texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) @@ -3550,7 +3600,7 @@ void VulkanRenderer::texture_loadSlice(LatteTexture* hostTexture, sint32 width, vkCmdCopyBufferToImage(m_state.currentCommandBuffer, uploadResv.vkBuffer, vkImageObj->m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, imageRegionCount, imageRegion); - barrier_image(vkTexture, barrierSubresourceRange, VK_IMAGE_LAYOUT_GENERAL); + barrier_image(vkTexture, barrierSubresourceRange, vkTexture->GetDefaultLayout()); } LatteTexture* VulkanRenderer::texture_createTextureEx(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, @@ -3628,13 +3678,11 @@ void VulkanRenderer::texture_copyImageSubData(LatteTexture* src, sint32 srcMip, sint32 mipWidth = std::max(dst->width >> dstMip, 1); sint32 mipHeight = std::max(dst->height >> dstMip, 1); - if (mipWidth < 4 || mipHeight < 4) { cemuLog_logDebug(LogType::Force, "vkCmdCopyImage - blocked copy for unsupported uncompressed->compressed copy with dst smaller than 4x4"); return; } - } // make sure all write operations to the src image have finished @@ -3645,7 +3693,8 @@ void VulkanRenderer::texture_copyImageSubData(LatteTexture* src, sint32 srcMip, vkCmdCopyImage(m_state.currentCommandBuffer, srcVkObj->m_image, VK_IMAGE_LAYOUT_GENERAL, dstVkObj->m_image, VK_IMAGE_LAYOUT_GENERAL, 1, ®ion); // make sure the transfer is finished before the image is read or written - barrier_image(dstVk, region.dstSubresource, VK_IMAGE_LAYOUT_GENERAL); + barrier_image(srcVk, region.srcSubresource, srcVk->GetDefaultLayout()); + barrier_image(dstVk, region.dstSubresource, dstVk->GetDefaultLayout()); } LatteTextureReadbackInfo* VulkanRenderer::texture_createReadback(LatteTextureView* textureView) @@ -4064,6 +4113,10 @@ void VKRObjectSampler::DestroyCache() VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint32 colorAttachmentCount) { + VulkanRenderer* vkRenderer = VulkanRenderer::GetInstance(); + bool useAttachmentFeedbackLoop = vkRenderer->UseAttachmentFeedbackLoop(); + VkImageLayout attachmentLayout = useAttachmentFeedbackLoop ? VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT : VK_IMAGE_LAYOUT_GENERAL; + // generate helper hash for pipeline state uint64 stateHash = 0; for (int i = 0; i < Latte::GPU_LIMITS::NUM_COLOR_ATTACHMENTS; ++i) @@ -4097,7 +4150,7 @@ VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint3 m_colorAttachmentFormat[i] = attachmentInfo.colorAttachment[i].format; color_attachments_references[i].attachment = (uint32)attachments_descriptions.size(); - color_attachments_references[i].layout = VK_IMAGE_LAYOUT_GENERAL; + color_attachments_references[i].layout = attachmentLayout; VkAttachmentDescription entry{}; entry.format = attachmentInfo.colorAttachment[i].format; @@ -4106,8 +4159,8 @@ VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint3 entry.storeOp = VK_ATTACHMENT_STORE_OP_STORE; entry.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; entry.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - entry.initialLayout = VK_IMAGE_LAYOUT_GENERAL; - entry.finalLayout = VK_IMAGE_LAYOUT_GENERAL; + entry.initialLayout = attachmentLayout; + entry.finalLayout = attachmentLayout; attachments_descriptions.emplace_back(entry); numColorAttachments = i + 1; @@ -4124,7 +4177,7 @@ VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint3 { hasDepthStencilAttachment = true; depth_stencil_attachments_references.attachment = (uint32)attachments_descriptions.size(); - depth_stencil_attachments_references.layout = VK_IMAGE_LAYOUT_GENERAL; + depth_stencil_attachments_references.layout = attachmentLayout; m_depthAttachmentFormat = attachmentInfo.depthAttachment.format; VkAttachmentDescription entry{}; @@ -4142,8 +4195,8 @@ VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint3 entry.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; entry.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; } - entry.initialLayout = VK_IMAGE_LAYOUT_GENERAL; - entry.finalLayout = VK_IMAGE_LAYOUT_GENERAL; + entry.initialLayout = attachmentLayout; + entry.finalLayout = attachmentLayout; attachments_descriptions.emplace_back(entry); } @@ -4164,12 +4217,29 @@ VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint3 renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; - renderPassInfo.pDependencies = nullptr; - renderPassInfo.dependencyCount = 0; + VkSubpassDependency feedbackLoopDependency{}; + if (useAttachmentFeedbackLoop) + { + feedbackLoopDependency.srcSubpass = 0; + feedbackLoopDependency.dstSubpass = 0; + feedbackLoopDependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + feedbackLoopDependency.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + feedbackLoopDependency.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + feedbackLoopDependency.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + feedbackLoopDependency.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT | VK_DEPENDENCY_FEEDBACK_LOOP_BIT_EXT; + renderPassInfo.pDependencies = &feedbackLoopDependency; + renderPassInfo.dependencyCount = 1; + } + else + { + renderPassInfo.pDependencies = nullptr; + renderPassInfo.dependencyCount = 0; + } // before Cemu 1.25.5 we used zero here, which means implicit synchronization. For 1.25.5 it was changed to 2 (using the subpass dependencies above) // Reverted this again to zero for Cemu 1.25.5b as the performance cost is just too high. Manual synchronization is preferred + // as of Cemu 2.7 we are now using VK_EXT_attachment_feedback_loop_layout with a matching renderpass dependency if supported. Otherwise we are falling back to the above - if (vkCreateRenderPass(VulkanRenderer::GetInstance()->GetLogicalDevice(), &renderPassInfo, nullptr, &m_renderPass) != VK_SUCCESS) + if (vkCreateRenderPass(vkRenderer->GetLogicalDevice(), &renderPassInfo, nullptr, &m_renderPass) != VK_SUCCESS) { cemuLog_log(LogType::Force, "Vulkan-Error: Failed to create render pass"); throw std::runtime_error("failed to create render pass!"); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index dc90eb09..4581e9e7 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -380,7 +380,7 @@ private: VkDescriptorSetInfo* activePixelDS{ nullptr }; VkDescriptorSetInfo* activeGeometryDS{ nullptr }; bool descriptorSetsChanged{ false }; - bool hasRenderSelfDependency{ false }; // set if current drawcall samples textures which are also output as a rendertarget + VkImageAspectFlags feedbackLoopImageAspect{0xFFFFFFFF}; // viewport and scissor box VkViewport currentViewport{}; VkRect2D currentScissorRect{}; @@ -414,6 +414,7 @@ private: activeIndexType = Renderer::INDEX_TYPE::NONE; activeIndexBufferIndex = std::numeric_limits::max(); activeIndexBufferOffset = std::numeric_limits::max(); + feedbackLoopImageAspect = 0xFFFFFFFF; } // invalidation / flushing @@ -465,6 +466,8 @@ private: bool present_wait = false; // VK_KHR_present_wait bool depth_clip_enable = false; // VK_EXT_depth_clip_enable bool pipeline_robustness = false; // VK_EXT_pipeline_robustness + bool attachment_feedback_loop_layout = false; // VK_EXT_attachment_feedback_loop_layout + bool attachment_feedback_loop_dynamic_state = false; // VK_EXT_attachment_feedback_loop_dynamic_state (this is forced to false if VK_EXT_attachment_feedback_loop_layout is not supported) }deviceExtensions; struct @@ -553,7 +556,7 @@ private: void draw_handleSpecialState5(); // draw synchronization helper - void sync_inputTexturesChanged(); + void sync_inputTexturesChanged(bool withinFeedbackLoopRenderPass = false); void sync_RenderPassLoadTextures(CachedFBOVk* fboVk); void sync_RenderPassStoreTextures(CachedFBOVk* fboVk); @@ -952,6 +955,7 @@ public: bool HasSPRIVRoundingModeRTE32() const { return m_featureControl.shaderFloatControls.shaderRoundingModeRTEFloat32; } bool IsDebugMarkersEnabled() const { return m_featureControl.usingDebugMarkerTool; } bool IsTracingToolEnabled() const { return m_featureControl.usingTracingTool; } + bool UseAttachmentFeedbackLoop() const { return m_featureControl.deviceExtensions.attachment_feedback_loop_dynamic_state; } private: diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index 01295a4d..717d14fe 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -711,14 +711,13 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo* continue; } - info.imageLayout = VK_IMAGE_LAYOUT_GENERAL; - VkSamplerCustomBorderColorCreateInfoEXT samplerCustomBorderColor{}; VkSamplerCreateInfo samplerInfo{}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; - LatteTexture* baseTexture = textureView->baseTexture; + LatteTextureVk* baseTexture = static_cast(textureView->baseTexture); + info.imageLayout = baseTexture->GetDefaultLayout(); // get texture register word 0 uint32 word4 = LatteGPUState.contextRegister[texUnitRegIndex + 4]; @@ -731,7 +730,7 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo* textureView->AddDescriptorSetReference(dsInfo); if (!baseTexture->IsCompressedFormat()) - vectorAppendUnique(dsInfo->list_fboCandidates, (LatteTextureVk*)baseTexture); + vectorAppendUnique(dsInfo->list_fboCandidates, baseTexture); uint32 stageSamplerIndex = shader->textureUnitSamplerAssignment[relative_textureUnit]; if (stageSamplerIndex != LATTE_DECOMPILER_SAMPLER_NONE) @@ -1001,7 +1000,7 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo* return dsInfo; } -void VulkanRenderer::sync_inputTexturesChanged() +void VulkanRenderer::sync_inputTexturesChanged(bool withinFeedbackLoopRenderPass) { bool writeFlushRequired = false; @@ -1050,14 +1049,27 @@ void VulkanRenderer::sync_inputTexturesChanged() srcStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; memoryBarrier.srcAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - // dst - dstStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - memoryBarrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; + if (withinFeedbackLoopRenderPass) + { + // this renderpass has a pixel self dependency. Feedback loop extension allows barriers during renderpass, but they must be BY_REGION + dstStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + memoryBarrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; - dstStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - memoryBarrier.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; + dstStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + memoryBarrier.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; + } + else + { + // dst + dstStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + memoryBarrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; - vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr); + dstStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + memoryBarrier.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; + } + + VkDependencyFlags dependencyFlags = withinFeedbackLoopRenderPass ? VK_DEPENDENCY_BY_REGION_BIT : 0; + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, dependencyFlags, 1, &memoryBarrier, 0, nullptr, 0, nullptr); performanceMonitor.vk.numDrawBarriersPerFrame.increment(); @@ -1180,22 +1192,32 @@ bool s_syncOnNextDraw = false; void VulkanRenderer::draw_setRenderPass() { CachedFBOVk* fboVk = m_state.activeFBO; + // note - pixel self dependency can be handled via feedback_loop extension + // vertex/geometry self dependency needs renderpass split + CachedFBOVk::RendertargetSelfDependencyMask renderSelfDependencyInfo{}; - // update self-dependency flag + // update self-dependency state if (m_state.descriptorSetsChanged || m_state.activeRenderpassFBO != fboVk) { - m_state.hasRenderSelfDependency = fboVk->CheckForCollision(m_state.activeVertexDS, m_state.activeGeometryDS, m_state.activePixelDS); + renderSelfDependencyInfo = fboVk->CheckForSelfDependency(m_state.activeVertexDS, m_state.activeGeometryDS, m_state.activePixelDS); } auto vkObjRenderPass = fboVk->GetRenderPassObj(); auto vkObjFramebuffer = fboVk->GetFramebufferObj(); - bool overridePassReuse = m_state.hasRenderSelfDependency && (GetConfig().vk_accurate_barriers || m_state.activePipelineInfo->neverSkipAccurateBarrier); + bool feedbackLoopHandlesSelfDependency = UseAttachmentFeedbackLoop() && renderSelfDependencyInfo.HasSelfDependency() && !renderSelfDependencyInfo.HasVertexOrGeometrySelfDependency(); + bool selfDependencyNeedsPassSplit = renderSelfDependencyInfo.HasSelfDependency() && !feedbackLoopHandlesSelfDependency; + bool overridePassReuse = selfDependencyNeedsPassSplit && (GetConfig().vk_accurate_barriers || m_state.activePipelineInfo->neverSkipAccurateBarrier); if (!overridePassReuse && m_state.activeRenderpassFBO == fboVk) { if (m_state.descriptorSetsChanged) - sync_inputTexturesChanged(); + sync_inputTexturesChanged(feedbackLoopHandlesSelfDependency); + if (UseAttachmentFeedbackLoop() && renderSelfDependencyInfo.GetAspectMask() != m_state.feedbackLoopImageAspect) + { + m_state.feedbackLoopImageAspect = renderSelfDependencyInfo.GetAspectMask(); + vkCmdSetAttachmentFeedbackLoopEnableEXT(m_state.currentCommandBuffer, renderSelfDependencyInfo.GetAspectMask()); + } return; } draw_endRenderPass(); @@ -1203,7 +1225,7 @@ void VulkanRenderer::draw_setRenderPass() sync_inputTexturesChanged(); // assume that FBO changed, update self-dependency state - m_state.hasRenderSelfDependency = fboVk->CheckForCollision(m_state.activeVertexDS, m_state.activeGeometryDS, m_state.activePixelDS); + renderSelfDependencyInfo = fboVk->CheckForSelfDependency(m_state.activeVertexDS, m_state.activeGeometryDS, m_state.activePixelDS); sync_RenderPassLoadTextures(fboVk); @@ -1228,6 +1250,11 @@ void VulkanRenderer::draw_setRenderPass() } m_state.activeRenderpassFBO = fboVk; + if (UseAttachmentFeedbackLoop() && renderSelfDependencyInfo.GetAspectMask() != m_state.feedbackLoopImageAspect) + { + m_state.feedbackLoopImageAspect = renderSelfDependencyInfo.GetAspectMask(); + vkCmdSetAttachmentFeedbackLoopEnableEXT(m_state.currentCommandBuffer, renderSelfDependencyInfo.GetAspectMask()); + } vkObjRenderPass->flagForCurrentCommandBuffer(); vkObjFramebuffer->flagForCurrentCommandBuffer(); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp index e3e42012..d102a89e 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp @@ -557,7 +557,7 @@ VKRObjectDescriptorSet* VulkanRenderer::surfaceCopy_getOrCreateDescriptorSet(VkC descriptorImageInfo.sampler = vkObjImageView->m_textureDefaultSampler[0]; descriptorImageInfo.imageView = vkObjImageView->m_textureImageView; - descriptorImageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + descriptorImageInfo.imageLayout = state.sourceTexture->GetDefaultLayout(); VkWriteDescriptorSet write_descriptor{}; write_descriptor.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; @@ -673,8 +673,8 @@ void VulkanRenderer::surfaceCopy_viaDrawcall(LatteTextureVk* srcTextureVk, sint3 cemu_assert_debug(srcTextureVk->GetImageObj()->m_image != dstTextureVk->GetImageObj()->m_image); - barrier_image(srcTextureVk, srcImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for any modifying operations on source image to complete - barrier_image(dstTextureVk, dstImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for any operations on destination image to complete + barrier_image(srcTextureVk, srcImageSubresource, srcTextureVk->GetDefaultLayout()); // wait for any modifying operations on source image to complete + barrier_image(dstTextureVk, dstImageSubresource, dstTextureVk->GetDefaultLayout()); // wait for any operations on destination image to complete vkCmdBeginRenderPass(m_state.currentCommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); @@ -692,8 +692,8 @@ void VulkanRenderer::surfaceCopy_viaDrawcall(LatteTextureVk* srcTextureVk, sint3 vkCmdEndRenderPass(m_state.currentCommandBuffer); - barrier_image(srcTextureVk, srcImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for drawcall to complete before any other operations on the source image - barrier_image(dstTextureVk, dstImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for drawcall to complete before any other operations on the destination image + barrier_image(srcTextureVk, srcImageSubresource, srcTextureVk->GetDefaultLayout()); // wait for drawcall to complete before any other operations on the source image + barrier_image(dstTextureVk, dstImageSubresource, dstTextureVk->GetDefaultLayout()); // wait for drawcall to complete before any other operations on the destination image // restore viewport and scissor box vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &m_state.currentViewport);