From c78a94ed096a6a6d4ec7312d33a32bdf691ce5f8 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Mon, 2 Mar 2026 17:16:18 +0800 Subject: [PATCH] [Chore] (MG_Backend/DirectVulkan): use `vkCmdClearAttachments` to implement clear semantics when there's one compatible render pass in flight (instead of interrupting it) --- .../Renderer/VkRenderPassManager.cpp | 66 ++++++++++++++++++- .../Renderer/VkRenderPassManager.h | 12 +++- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 13 ++-- 3 files changed, 83 insertions(+), 8 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index 5d114c3a..f271927c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -32,7 +32,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } VkRenderPassManager::HashType VkRenderPassManager::ComputeHash( - const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex) const { + const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex, Bool includePendingClear) const { XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion)); const Bool isDefaultFbo = (&fbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get()); if (isDefaultFbo) { @@ -65,7 +65,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { contentPtr = att.GetRenderbuffer().get(); XXHASH_VERIFY(XXH64_update(m_hashState, &contentPtr, sizeof(contentPtr))); - if (att.IsTexture()) { + if (includePendingClear && att.IsTexture()) { auto* texture = att.GetTexture().get(); auto hasClear = m_clearManager.HasPendingClear(texture); XXHASH_VERIFY(XXH64_update(m_hashState, &hasClear, sizeof(hasClear))); @@ -95,7 +95,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { RenderPassEntry& VkRenderPassManager::GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex) { // retrieve from cache first - auto hash = ComputeHash(fbo, swapchainImageIndex); + auto hash = ComputeHash(fbo, swapchainImageIndex, true); + auto compatibilityHash = ComputeHash(fbo, swapchainImageIndex, false); auto it = m_renderPasses.find(hash); if (it != m_renderPasses.end()) return it->second; @@ -288,6 +289,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { RenderPassEntry renderPassEntry { renderPass, framebuffer, + compatibilityHash, Move(textureResources), Move(pendingClearAttachments), static_cast(attachmentViews.size()), @@ -297,6 +299,64 @@ namespace MobileGL::MG_Backend::DirectVulkan { return insertedIt->second; } + Bool VkRenderPassManager::TryClearPendingAttachmentsOnActiveRenderPass( + VkCommandBuffer commandBuffer, + const RenderPassEntry& compatibleRenderPassEntry) { + if (!s_activeRenderPass || !s_clearManager) { + return false; + } + if (s_activeRenderPass->compatibilityHash != compatibleRenderPassEntry.compatibilityHash) { + return false; + } + + VkClearRect clearRect{}; + clearRect.rect.offset = {0, 0}; + clearRect.rect.extent = { + static_cast(s_activeRenderPass->extent.x()), + static_cast(s_activeRenderPass->extent.y()) + }; + clearRect.baseArrayLayer = 0; + clearRect.layerCount = 1; + + for (const auto& pending : compatibleRenderPassEntry.pendingClearAttachments) { + if (!pending.texture) { + continue; + } + + ClearAttachmentPayload clearPayload{}; + if (!s_clearManager->GetPendingClear(pending.texture, clearPayload)) { + continue; + } + + VkClearAttachment clearAttachment{}; + clearAttachment.clearValue.depthStencil = {1.0f, 0}; + if (clearPayload.attachmentType >= FramebufferAttachmentType::Color0 && + clearPayload.attachmentType <= FramebufferAttachmentType::Color31) { + clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + clearAttachment.colorAttachment = pending.attachmentIndex; + clearAttachment.clearValue.color = { + clearPayload.color.x(), + clearPayload.color.y(), + clearPayload.color.z(), + clearPayload.color.w() + }; + } else if (clearPayload.attachmentType == FramebufferAttachmentType::Depth) { + clearAttachment.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + clearAttachment.clearValue.depthStencil.depth = clearPayload.depth; + } else if (clearPayload.attachmentType == FramebufferAttachmentType::Stencil) { + clearAttachment.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT; + clearAttachment.clearValue.depthStencil.stencil = clearPayload.stencil; + } else { + continue; + } + + vkCmdClearAttachments(commandBuffer, 1, &clearAttachment, 1, &clearRect); + s_clearManager->PopPendingClear(pending.texture); + } + + return true; + } + Bool VkRenderPassManager::BeginRenderPass(VkCommandBuffer commandBuffer, RenderPassEntry& renderPassEntry) { // TODO: Transition all the attachments into proper layout before starting the render pass VkRenderPassBeginInfo renderPassBeginInfo; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h index 9fdfe469..2f8e6077 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h @@ -27,6 +27,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { static inline VkDevice s_device; VkRenderPass renderPass = VK_NULL_HANDLE; VkFramebuffer framebuffer = VK_NULL_HANDLE; + Uint64 compatibilityHash = 0; // Should we hold pointer-to-resource here? Vector textureResources; Vector pendingClearAttachments; @@ -39,6 +40,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { RenderPassEntry(RenderPassEntry&& that) noexcept { std::swap(renderPass, that.renderPass); std::swap(framebuffer, that.framebuffer); + std::swap(compatibilityHash, that.compatibilityHash); std::swap(textureResources, that.textureResources); std::swap(pendingClearAttachments, that.pendingClearAttachments); std::swap(attachmentCount, that.attachmentCount); @@ -48,12 +50,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { RenderPassEntry( VkRenderPass renderpass, VkFramebuffer framebuffer, + Uint64 compatibilityHash, const std::vector& textureResources, const Vector& pendingClearAttachments, Uint32 attachmentCount, IntVec2 extent, int subpass): renderPass(renderpass), framebuffer(framebuffer), + compatibilityHash(compatibilityHash), textureResources(Move(textureResources)), pendingClearAttachments(Move(pendingClearAttachments)), attachmentCount(attachmentCount), @@ -82,8 +86,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool Initialize(); void Shutdown(); - HashType ComputeHash(const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex) const; + HashType ComputeHash( + const MG_State::GLState::FramebufferObject& fbo, + Uint32 swapchainImageIndex, + Bool includePendingClear = true) const; RenderPassEntry& GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex); + static Bool TryClearPendingAttachmentsOnActiveRenderPass( + VkCommandBuffer commandBuffer, + const RenderPassEntry& compatibleRenderPassEntry); static Bool BeginRenderPass(VkCommandBuffer commandBuffer, RenderPassEntry& renderPassEntry); static Bool EndRenderPass(VkCommandBuffer commandBuffer); static RenderPassEntry* GetActiveRenderPass(); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 9d365fea..667e7513 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -514,11 +514,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto& renderPassEntry = m_renderPassManager->GetOrCreateRenderPass(*drawFbo, m_imageIndexAcquired); auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass(); if (activeRenderPass != &renderPassEntry) { - if (activeRenderPass) { - VkRenderPassManager::EndRenderPass(frame.commandBuffer); + if (activeRenderPass && + VkRenderPassManager::TryClearPendingAttachmentsOnActiveRenderPass(frame.commandBuffer, renderPassEntry)) { + // Keep the current compatible render pass open and clear inside it. + } else { + if (activeRenderPass) { + VkRenderPassManager::EndRenderPass(frame.commandBuffer); + } + Bool ok = VkRenderPassManager::BeginRenderPass(frame.commandBuffer, renderPassEntry); + MOBILEGL_ASSERT(ok, "%s: BeginRenderPass failed", __func__); } - Bool ok = VkRenderPassManager::BeginRenderPass(frame.commandBuffer, renderPassEntry); - MOBILEGL_ASSERT(ok, "%s: BeginRenderPass failed", __func__); } else { // We probably already have one compatible render pass running. Keep going. }