diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index 9a6ca4ce..82241b87 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -602,6 +602,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_rpFastRenderPassHash == activeRenderPass->hash && !hasPendingClearOnFramebuffer()) { auto activeIt = m_renderPasses.find(activeRenderPass->hash); if (activeIt != m_renderPasses.end()) { + activeIt->second.lastUsedFrame = m_frameCounter; return activeIt->second; } } @@ -623,12 +624,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_rpFastTexEpoch = m_textureManager.GetTextureImageEpoch(); m_rpFastRbEpoch = m_renderbufferImageEpoch; m_rpFastRenderPassHash = activeRenderPass->hash; + activeIt->second.lastUsedFrame = m_frameCounter; return activeIt->second; } auto hash = ComputeHash(fbo, swapchainImageIndex, true); auto it = m_renderPasses.find(hash); - if (it != m_renderPasses.end()) + if (it != m_renderPasses.end()) { + it->second.lastUsedFrame = m_frameCounter; return it->second; + } Bool isDefaultFbo = fbo.IsDefaultFramebuffer(); // Color attachment @@ -1026,9 +1030,36 @@ namespace MobileGL::MG_Backend::DirectVulkan { extent.x(), extent.y()); auto [insertedIt, _] = m_renderPasses.emplace(hash, Move(renderPassEntry)); + insertedIt->second.lastUsedFrame = m_frameCounter; return insertedIt->second; } + void VkRenderPassManager::OnPresent() { + ++m_frameCounter; + + // Sweep occasionally; evict entries whose last use is far past every + // in-flight frame so their VkRenderPass/VkFramebuffer can be destroyed + // safely (RenderPassEntry's destructor releases the handles). + constexpr Uint64 kSweepInterval = 256; + constexpr Uint64 kRetireAgeFrames = 1024; + if ((m_frameCounter % kSweepInterval) != 0) { + return; + } + + const Uint64 activeHash = s_hasActiveRenderPass ? s_activeRenderPass.hash : 0; + for (auto it = m_renderPasses.begin(); it != m_renderPasses.end();) { + const Bool isActive = s_hasActiveRenderPass && it->first == activeHash; + if (!isActive && m_frameCounter - it->second.lastUsedFrame > kRetireAgeFrames) { + if (m_rpFastValid && m_rpFastRenderPassHash == it->first) { + m_rpFastValid = false; + } + it = m_renderPasses.erase(it); + } else { + ++it; + } + } + } + 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 7fe370a6..889065ca 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h @@ -75,6 +75,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { IntVec2 extent = {0, 0}; // VkFramebufferCreateInfo::layers of the entry's framebuffer (>1 for layered GL attachments). Uint32 layers = 1; + // Frame counter value of the last GetOrCreateRenderPass hit; drives cache eviction. + Uint64 lastUsedFrame = 0; RenderPassEntry() = default; RenderPassEntry(const RenderPassEntry&) = delete; @@ -91,6 +93,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { std::swap(sampleCount, that.sampleCount); std::swap(extent, that.extent); std::swap(layers, that.layers); + std::swap(lastUsedFrame, that.lastUsedFrame); } RenderPassEntry( Uint64 hash, @@ -172,6 +175,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { void QueueRenderbufferClear(const ClearAttachmentPayload& clearPayload, const MG_State::GLState::FramebufferAttachmentObject& attachment); void PopPendingRenderbufferClear(MG_State::GLState::RenderbufferObject* renderbuffer); + // Frame boundary hook: ages the render-pass cache and evicts long-unused + // entries (their command buffers retired many frames ago). + void OnPresent(); static Bool BeginRenderPass(VkCommandBuffer commandBuffer, RenderPassEntry& renderPassEntry); static Bool EndRenderPass(VkCommandBuffer commandBuffer); static ActiveRenderPassInfo* GetActiveRenderPass(); @@ -184,6 +190,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkTextureManager& m_textureManager; SwapchainObject& m_swapchainObject; UnorderedMap m_renderPasses; + // Monotonic frame counter (bumped in OnPresent) for render-pass cache aging. + Uint64 m_frameCounter = 0; // 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 diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index dbfb8836..976c3a68 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -6268,6 +6268,7 @@ void main() { void VulkanRenderer::Present() { MOBILEGL_ASSERT(m_imageIndexAcquired < m_swapchainObject.GetImageCount(), "Present, acquired image index out of range"); + m_renderPassManager->OnPresent(); auto& frame = m_frameContext.GetCurrent(); auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass(); if (activeRenderPass)