From 1b2e817a978faf4391ebda85d85702271a4aafd4 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Fri, 9 Oct 2026 10:15:28 -0400 Subject: [PATCH] Magma: wire-store rename spares 4 -> 12 with one fence question per rename, and a 50 us quiet window on unsignaled fence polls (FCL monolith GL CPU 2.44 -> 2.38 ms, 272 -> 278 fps at cpuhunt). --- .../DirectVulkan/Renderer/VkBufferManager.cpp | 17 +++++++++++------ .../DirectVulkan/Renderer/VulkanRenderer.cpp | 18 +++++++++++++++++- .../DirectVulkan/Renderer/VulkanRenderer.h | 3 +++ 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp index 26220392..9d7bd0c1 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp @@ -308,12 +308,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { return false; VkBufferObject old = std::move(resource.buffer); Bool reused = false; - for (SizeT i = 0; pVulkanRenderer != nullptr && i < resource.renameSpares.size(); ++i) { - if (!pVulkanRenderer->IsSubmitIndexComplete(resource.renameSpares[i].submitIndex)) continue; - resource.buffer = std::move(resource.renameSpares[i].buffer); - resource.renameSpares.erase(resource.renameSpares.begin() + static_cast(i)); + // Spares are queued in rename order, so their sync points only grow: when the oldest is not + // complete, no later one is (one fence question instead of one per spare). + if (pVulkanRenderer != nullptr && !resource.renameSpares.empty() && + pVulkanRenderer->IsSubmitIndexComplete(resource.renameSpares.front().submitIndex)) { + resource.buffer = std::move(resource.renameSpares.front().buffer); + resource.renameSpares.erase(resource.renameSpares.begin()); reused = true; - break; } if (!reused) { if (!CreateWireStoreBuffer(resource)) { @@ -332,7 +333,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Busy means a GPU command recorded so far may name the old buffer: it waits behind this // frame's serial and the current sync point, whatever its own stamps said - as a spare // for the next rename, or (past the bound) through DeferWireRelease. - constexpr SizeT kRenameSpares = 4; + // P15: 4 spares recycled too few for frames in flight x renames per frame (FCL MC 1.21.5: + // VkBufferObject::Create 0.031 + the retired stores' Destroy 0.014 ms/frame). Only shadowed + // stores rename, and those are at most kWireShadowMaxStore (64 KiB), so 12 spares bound a + // resource's extra memory at 768 KiB. + constexpr SizeT kRenameSpares = 12; if (resource.renameSpares.size() >= kRenameSpares) { auto& oldest = resource.renameSpares.front(); DeferWireRelease(std::move(oldest.buffer), oldest.lastUseSerial); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 1413c064..441f4562 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -5837,8 +5837,24 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (submitIndex > m_submitCounter) { return false; // not even submitted; no point polling fences } + // P15: a fence just seen unsignaled is not polled again for a short while. Callers ask per + // buffer write (FCL MC 1.21.5: vkGetFenceStatus was 0.020 ms/frame), and every one of them + // treats "not yet" as "still busy" - rename, defer, report unsignaled - so a late "yes" costs + // at most kUnsignaledPollQuietNs of extra caution, never correctness. + constexpr Uint64 kUnsignaledPollQuietNs = 50000; + const Uint64 now = static_cast(std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count()); + if (!m_inFlightSubmits.empty() && m_unsignaledPollFront == m_inFlightSubmits.front().submitIndex && + now - m_unsignaledPollNs < kUnsignaledPollQuietNs) { + return false; + } RefreshCompletedSubmits(); - return submitIndex <= m_completedSubmitCounter; + if (submitIndex <= m_completedSubmitCounter) return true; + if (!m_inFlightSubmits.empty()) { + m_unsignaledPollFront = m_inFlightSubmits.front().submitIndex; + m_unsignaledPollNs = now; + } + return false; } void VulkanRenderer::RegisterSubmit(VkFence fence, Bool pooledFence) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 50777acd..d9c938fb 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -1018,6 +1018,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { Vector m_freeSubmitFences; Uint64 m_submitCounter = 0; Uint64 m_completedSubmitCounter = 0; + // IsSubmitIndexComplete's quiet window: the in-flight front last seen unsignaled, and when. + Uint64 m_unsignaledPollFront = 0; + Uint64 m_unsignaledPollNs = 0; // Drains since the last Present, gating the drain's frame-boundary-equivalent // work (arena rewind + cache aging): a presenting app's mid-frame // readbacks/waits must neither churn the transient caches nor accelerate the