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).

This commit is contained in:
2026-10-09 12:54:27 -04:00
parent c4032d2022
commit 1b2e817a97
3 changed files with 31 additions and 7 deletions
@@ -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<std::ptrdiff_t>(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);
@@ -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<Uint64>(std::chrono::duration_cast<std::chrono::nanoseconds>(
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) {
@@ -1018,6 +1018,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Vector<VkFence> 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