diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index 816ecd74..54cdc7db 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -210,6 +210,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { RenderPassEntry::s_textureResourcesScratch.clear(); s_activeRenderPass = {}; s_hasActiveRenderPass = false; + m_rpFastValid = false; } void VkRenderPassManager::CollectRenderbufferGarbage() { @@ -305,6 +306,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { allocationInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; VK_VERIFY(vmaCreateImage(m_allocator, &imageInfo, &allocationInfo, &resource.image, &resource.allocation, nullptr), "vmaCreateImage(renderbuffer)"); + ++m_renderbufferImageEpoch; // a new attachment image invalidates cached render passes VkImageViewCreateInfo viewInfo{}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; @@ -585,6 +587,25 @@ namespace MobileGL::MG_Backend::DirectVulkan { // retrieve from cache first auto* activeRenderPass = GetActiveRenderPass(); + + // Dirty-flag state tracking: when the framebuffer state is provably unchanged since the + // render pass was last resolved, the active render pass is still valid -> skip the + // expensive per-draw ComputeHash (XXH64 over every attachment + a SyncTexture per + // attachment). Correctness signals: same FBO object + GetObjectVersion (attachment / + // draw-buffer / read-buffer changes bump it), same swapchain image, no attachment VkImage + // recreated since (texture + renderbuffer image epochs), and no pending clear (which alters + // load ops). Any of these differing forces the full recompute below. Portable to VK 1.1. + if (activeRenderPass != nullptr && m_rpFastValid && m_rpFastFbo == &fbo && + m_rpFastFboVersion == fbo.GetObjectVersion() && m_rpFastSwapchainIndex == swapchainImageIndex && + m_rpFastTexEpoch == m_textureManager.GetTextureImageEpoch() && + m_rpFastRbEpoch == m_renderbufferImageEpoch && + m_rpFastRenderPassHash == activeRenderPass->hash && !hasPendingClearOnFramebuffer()) { + auto activeIt = m_renderPasses.find(activeRenderPass->hash); + if (activeIt != m_renderPasses.end()) { + return activeIt->second; + } + } + auto compatibilityHash = ComputeHash(fbo, swapchainImageIndex, false); if (activeRenderPass != nullptr && activeRenderPass->CompatibleWith(compatibilityHash) && @@ -593,6 +614,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(activeIt != m_renderPasses.end(), "GetOrCreateRenderPass: active render pass hash=0x%llx is missing from cache", static_cast(activeRenderPass->hash)); + // Populate the fast-path memo so subsequent unchanged draws skip ComputeHash. Read the + // epochs AFTER ComputeHash: its attachment SyncTexture can create an image (bump the epoch). + m_rpFastValid = true; + m_rpFastFbo = &fbo; + m_rpFastFboVersion = fbo.GetObjectVersion(); + m_rpFastSwapchainIndex = swapchainImageIndex; + m_rpFastTexEpoch = m_textureManager.GetTextureImageEpoch(); + m_rpFastRbEpoch = m_renderbufferImageEpoch; + m_rpFastRenderPassHash = activeRenderPass->hash; return activeIt->second; } auto hash = ComputeHash(fbo, swapchainImageIndex, true); @@ -1069,6 +1099,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool VkRenderPassManager::EndRenderPass(VkCommandBuffer commandBuffer) { auto* activeRenderPass = GetActiveRenderPass(); vkCmdEndRenderPass(commandBuffer); + // The fast-path memo reuses the ACTIVE render pass; once the pass ends it must not carry + // over (the next span may be a different FBO resolved before its render pass is begun). + if (s_renderPassManager != nullptr) { + s_renderPassManager->m_rpFastValid = false; + } if (activeRenderPass != nullptr && !activeRenderPass->trackedAttachmentLayouts.empty()) { for (const auto& trackedAttachment : activeRenderPass->trackedAttachmentLayouts) { switch (trackedAttachment.target) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h index 15040626..9fe0a299 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h @@ -179,6 +179,24 @@ namespace MobileGL::MG_Backend::DirectVulkan { SwapchainObject& m_swapchainObject; UnorderedMap m_renderPasses; + // Bumped whenever a renderbuffer VkImage is (re)created; together with the texture + // manager's image epoch this invalidates the render-pass fast path on any attachment + // image recreation. + Uint64 m_renderbufferImageEpoch = 1; + + // Per-draw fast-path memo for GetOrCreateRenderPass (dirty-flag state tracking): when the + // framebuffer state is provably unchanged since the last resolution, the active render pass + // is reused WITHOUT recomputing the expensive per-draw hash. Invalidated by FBO switch / + // version change, swapchain rotation, any attachment image recreation (the two epochs), + // or a pending clear. Portable to Vulkan 1.1 (no dynamic_rendering / imageless FB needed). + Bool m_rpFastValid = false; + const MG_State::GLState::FramebufferObject* m_rpFastFbo = nullptr; + Uint16 m_rpFastFboVersion = 0; + Uint32 m_rpFastSwapchainIndex = 0; + Uint64 m_rpFastTexEpoch = 0; + Uint64 m_rpFastRbEpoch = 0; + Uint64 m_rpFastRenderPassHash = 0; + struct RenderbufferResource { WeakPtr renderbuffer; VkImage image = VK_NULL_HANDLE; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index 322805b9..5ce20443 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -1252,6 +1252,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { allocationInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; VK_VERIFY(vmaCreateImage(m_allocator, &imageInfo, &allocationInfo, &resource.image, &resource.allocation, nullptr), "vmaCreateImage(texture)"); + ++m_textureImageEpoch; // a new attachment image invalidates cached render passes resource.layout = VK_IMAGE_LAYOUT_UNDEFINED; resource.extent = {static_cast(texelSize.x()), static_cast(texelSize.y())}; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h index 5aca9718..a972dc12 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h @@ -21,6 +21,11 @@ class ITextureObject; namespace MobileGL::MG_Backend::DirectVulkan { class VkTextureManager { public: + // Monotonic epoch bumped whenever a texture VkImage is (re)created. The render-pass + // manager keys its per-draw fast path on this so an attachment's image recreation + // invalidates the cached render pass (dirty-flag tracking; portable to Vulkan 1.1). + Uint64 GetTextureImageEpoch() const { return m_textureImageEpoch; } + struct TextureIdentity { MG_State::GLState::ITextureObject* texture = nullptr; Uint64 lifetimeId = 0; @@ -225,6 +230,8 @@ public: }; private: + // Bumped in SyncTextureResource right after vmaCreateImage(texture). See GetTextureImageEpoch(). + Uint64 m_textureImageEpoch = 1; Bool SyncTexture(MG_State::GLState::ITextureObject &texture, TextureResource &outResource);