mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-09 11:03:23 +09:00
[MG_Backend] (P7): when the orphaned wire buffer stores still parked exceed MOBILEGL_IPC_WIRE_DEFERRED_MB (64 by default), the server takes the host-access sync point mid-frame and reclaims them all
The defer-path sweep frees an orphan once its last submission has retired, but inside one long
frame a store that was drawn from is named by recorded, unsubmitted work, and nothing submits it
until the frame ends. The knob is read once through MG_Config::Ipc like its siblings; above it
the park calls WaitForSubmitIndex(GetSyncPointSubmitIndex(), flush) - the call
WaitForWireBufferHostAccess makes - and sweeps again, which empties the list because no entry can
be tagged past the sync point just waited out. A sync point that cannot complete dies through
the existing Fatal{ResourceUnavailable, "buffer-write-sync"} site, now one helper shared with the
host write (fatal_census: 79 abort sites, unchanged). 0 is the negative control.
This commit is contained in:
@@ -497,6 +497,18 @@ namespace MobileGL::MG_Config {
|
|||||||
// stage chunk budget (MGPipeStageChunkBytes, a quarter of this), and a record type with
|
// stage chunk budget (MGPipeStageChunkBytes, a quarter of this), and a record type with
|
||||||
// no cut is Fatal{RingOverrun, "SEG_STAGE"} rather than allowed to exceed it.
|
// no cut is Fatal{RingOverrun, "SEG_STAGE"} rather than allowed to exceed it.
|
||||||
Uint32 StageMb = 32;
|
Uint32 StageMb = 32;
|
||||||
|
// MOBILEGL_IPC_WIRE_DEFERRED_MB (P7 wave 4 M2, ID-P7-32): the SERVER's budget, in MiB,
|
||||||
|
// for orphaned wire buffer stores - the old VkBuffer every glBufferData that crosses the
|
||||||
|
// wire leaves behind - that a GPU command recorded but not yet retired may still name.
|
||||||
|
// Stores no command names are destroyed at once and stores whose last submission has
|
||||||
|
// retired are destroyed at the next park; this bounds the REST. When the parked bytes
|
||||||
|
// exceed it after a sweep, the server flushes what it has recorded and waits for it
|
||||||
|
// (WaitForWireBufferHostAccess's sync point, mid-frame), which retires every one. It is
|
||||||
|
// not a frame count because a frame is not bounded: a snapshot-exiting pbuffer replay
|
||||||
|
// delivers one present for 1.3 M calls. 0 IS THE NEGATIVE CONTROL, not "unlimited by
|
||||||
|
// design": no forced sync, so a one-frame respecify-and-draw loop grows without bound
|
||||||
|
// and MagmaWireReclaimScenario's watermark case must go red.
|
||||||
|
Uint32 WireDeferredMb = 64;
|
||||||
// MOBILEGL_IPC_SPIN_US: spin before parking on a doorbell, either direction.
|
// MOBILEGL_IPC_SPIN_US: spin before parking on a doorbell, either direction.
|
||||||
Uint32 SpinUs = 50;
|
Uint32 SpinUs = 50;
|
||||||
// MOBILEGL_IPC_PERSISTENT_BLOCK_KB: block granularity of the persistent-map push.
|
// MOBILEGL_IPC_PERSISTENT_BLOCK_KB: block granularity of the persistent-map push.
|
||||||
|
|||||||
@@ -392,6 +392,9 @@ namespace MobileGL::MG_ConfigLoader {
|
|||||||
// are sanity, not policy.
|
// are sanity, not policy.
|
||||||
ipc.RingMb = QueryEnvUint32("MOBILEGL_IPC_RING_MB", 8, 1, 1024);
|
ipc.RingMb = QueryEnvUint32("MOBILEGL_IPC_RING_MB", 8, 1, 1024);
|
||||||
ipc.StageMb = QueryEnvUint32("MOBILEGL_IPC_STAGE_MB", 32, 1, 4096);
|
ipc.StageMb = QueryEnvUint32("MOBILEGL_IPC_STAGE_MB", 32, 1, 4096);
|
||||||
|
// P7 wave 4 M2 (Config.h has the semantics). 0 is admitted ON PURPOSE as the negative
|
||||||
|
// control: no forced sync, and MagmaWireReclaimScenario's watermark case must go red.
|
||||||
|
ipc.WireDeferredMb = QueryEnvUint32("MOBILEGL_IPC_WIRE_DEFERRED_MB", 64, 0, 65536);
|
||||||
ipc.SpinUs = QueryEnvUint32("MOBILEGL_IPC_SPIN_US", 50, 0, 1000000);
|
ipc.SpinUs = QueryEnvUint32("MOBILEGL_IPC_SPIN_US", 50, 0, 1000000);
|
||||||
// 0 is admitted ON PURPOSE and is the negative control of exit gate E3(a): it turns
|
// 0 is admitted ON PURPOSE and is the negative control of exit gate E3(a): it turns
|
||||||
// the persistent-map push OFF, and PersistentCoherentMapScenario must go red.
|
// the persistent-map push OFF, and PersistentCoherentMapScenario must go red.
|
||||||
@@ -462,10 +465,10 @@ namespace MobileGL::MG_ConfigLoader {
|
|||||||
if (MG_Config::Transport == MG_Config::TransportMode::Monolith) return;
|
if (MG_Config::Transport == MG_Config::TransportMode::Monolith) return;
|
||||||
// One line, on the arm where these numbers decide behaviour, because every one of
|
// One line, on the arm where these numbers decide behaviour, because every one of
|
||||||
// them is a number a bug report has to quote.
|
// them is a number a bug report has to quote.
|
||||||
MGLOG_I("Config: IPC ring=%uMiB stage=%uMiB spin=%uus persistent-block=%uKiB "
|
MGLOG_I("Config: IPC ring=%uMiB stage=%uMiB wire-deferred=%uMiB spin=%uus persistent-block=%uKiB "
|
||||||
"adopt-tier=%u verb-barrier=%u run-ahead=%u present-credit=%u strict=%d "
|
"adopt-tier=%u verb-barrier=%u run-ahead=%u present-credit=%u strict=%d "
|
||||||
"audit=%d role-split-state=%d affinity='%s'",
|
"audit=%d role-split-state=%d affinity='%s'",
|
||||||
ipc.RingMb, ipc.StageMb, ipc.SpinUs, ipc.PersistentBlockKb, ipc.AdoptTier,
|
ipc.RingMb, ipc.StageMb, ipc.WireDeferredMb, ipc.SpinUs, ipc.PersistentBlockKb, ipc.AdoptTier,
|
||||||
ipc.VerbBarrier, ipc.RunAhead, ipc.PresentCredit,
|
ipc.VerbBarrier, ipc.RunAhead, ipc.PresentCredit,
|
||||||
static_cast<int>(ipc.StrictErrors), static_cast<int>(ipc.Audit),
|
static_cast<int>(ipc.StrictErrors), static_cast<int>(ipc.Audit),
|
||||||
static_cast<int>(ipc.RoleSplitState), ipc.ServerAffinity.c_str());
|
static_cast<int>(ipc.RoleSplitState), ipc.ServerAffinity.c_str());
|
||||||
@@ -474,6 +477,11 @@ namespace MobileGL::MG_ConfigLoader {
|
|||||||
"expected to fail: the client still pulls 31 of 63 PipeInputs fields from a "
|
"expected to fail: the client still pulls 31 of 63 PipeInputs fields from a "
|
||||||
"live GLContext, so an unbarriered queue lets the server read future values");
|
"live GLContext, so an unbarriered queue lets the server read future values");
|
||||||
}
|
}
|
||||||
|
if (ipc.WireDeferredMb == 0) {
|
||||||
|
MGLOG_W("Config: MOBILEGL_IPC_WIRE_DEFERRED_MB=0 is the M2 NEGATIVE CONTROL: the "
|
||||||
|
"server never forces a sync for orphaned wire buffer stores, so a long frame "
|
||||||
|
"that respecifies and draws holds every one of them until the frame ends");
|
||||||
|
}
|
||||||
if (ipc.PersistentBlockKb == 0) {
|
if (ipc.PersistentBlockKb == 0) {
|
||||||
MGLOG_W("Config: MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0 is the E3(a) NEGATIVE CONTROL: "
|
MGLOG_W("Config: MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0 is the E3(a) NEGATIVE CONTROL: "
|
||||||
"the persistent-map push is OFF and a coherent-map scenario must go red");
|
"the persistent-map push is OFF and a coherent-map scenario must go red");
|
||||||
|
|||||||
@@ -365,10 +365,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
uploaded = StagedWireRangeCopy(*resource, bytes, static_cast<SizeT>(offset),
|
uploaded = StagedWireRangeCopy(*resource, bytes, static_cast<SizeT>(offset),
|
||||||
static_cast<SizeT>(size));
|
static_cast<SizeT>(size));
|
||||||
}
|
}
|
||||||
if (!uploaded && !WaitForWireBufferHostAccess(*resource)) {
|
if (!uploaded && !WaitForWireBufferHostAccess(*resource)) WireBufferSyncFatal("host-write");
|
||||||
MGLOG_F("Magma: Fatal{ResourceUnavailable, \"buffer-write-sync\"}");
|
|
||||||
std::abort();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!uploaded) {
|
if (!uploaded) {
|
||||||
uploaded = resource->buffer.Upload(bytes, size, offset);
|
uploaded = resource->buffer.Upload(bytes, size, offset);
|
||||||
@@ -606,14 +603,38 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// pbuffer replay that snapshot-exits delivers ONE present for 1.3 M calls (ID-P7-32) -
|
// pbuffer replay that snapshot-exits delivers ONE present for 1.3 M calls (ID-P7-32) -
|
||||||
// so the only cadence that tracks the workload is the workload itself.
|
// so the only cadence that tracks the workload is the workload itself.
|
||||||
SweepDeferredWireReleases();
|
SweepDeferredWireReleases();
|
||||||
|
EnforceWireDeferredWatermark();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VkBufferManager::EnforceWireDeferredWatermark() {
|
||||||
|
const Uint64 budget = static_cast<Uint64>(MG_Config::Ipc.WireDeferredMb) * 1024u * 1024u;
|
||||||
|
if (budget == 0 || m_deferredWireBytes <= budget) return;
|
||||||
|
// What is left after the sweep is named by work that is recorded but unsubmitted, or
|
||||||
|
// submitted but unretired. Every entry is tagged at or below the sync point taken HERE,
|
||||||
|
// so waiting it out - the host-access wait's own call, which flushes the recording first
|
||||||
|
// - proves all of them dead. A mid-frame flush is what WaitForWireBufferHostAccess and a
|
||||||
|
// glClientWaitSync already do to this command stream; it costs one submission per
|
||||||
|
// budget's worth of orphans, not one per glBufferData.
|
||||||
|
if (pVulkanRenderer == nullptr ||
|
||||||
|
!pVulkanRenderer->WaitForSubmitIndex(pVulkanRenderer->GetSyncPointSubmitIndex(), UINT64_MAX, true)) {
|
||||||
|
WireBufferSyncFatal("deferred-watermark");
|
||||||
|
}
|
||||||
|
SweepDeferredWireReleases();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VkBufferManager::WireBufferSyncFatal(const char* site) {
|
||||||
|
MGLOG_F("Magma: Fatal{ResourceUnavailable, \"buffer-write-sync\"} {site=%s}", site);
|
||||||
|
std::abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
SizeT VkBufferManager::SweepDeferredWireReleases() {
|
SizeT VkBufferManager::SweepDeferredWireReleases() {
|
||||||
if (m_deferredWireReleases.empty() || pVulkanRenderer == nullptr) return 0;
|
if (m_deferredWireReleases.empty()) return 0;
|
||||||
SizeT dead = 0;
|
SizeT dead = 0;
|
||||||
if (pVulkanRenderer->IsSubmitIndexComplete(pVulkanRenderer->GetSyncPointSubmitIndex())) {
|
if (pVulkanRenderer == nullptr ||
|
||||||
// Idle: nothing recorded, every submission retired. Every parked store is dead,
|
pVulkanRenderer->IsSubmitIndexComplete(pVulkanRenderer->GetSyncPointSubmitIndex())) {
|
||||||
// including one tagged for a batch that was abandoned instead of submitted.
|
// Idle: nothing recorded, every submission retired - or no renderer, so no queue.
|
||||||
|
// Every parked store is dead, including one tagged for a batch that was abandoned
|
||||||
|
// instead of submitted.
|
||||||
dead = m_deferredWireReleases.size();
|
dead = m_deferredWireReleases.size();
|
||||||
} else {
|
} else {
|
||||||
// The list is in park order and GetSyncPointSubmitIndex() never decreases, so the
|
// The list is in park order and GetSyncPointSubmitIndex() never decreases, so the
|
||||||
|
|||||||
@@ -294,13 +294,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// running total has to stay exact as entries leave.
|
// running total has to stay exact as entries leave.
|
||||||
Uint64 bytes = 0;
|
Uint64 bytes = 0;
|
||||||
};
|
};
|
||||||
// Park a wire store, then sweep. `lastUseSerial` is the releasing resource's, captured
|
// Park a wire store, sweep, then hold the watermark. `lastUseSerial` is the releasing
|
||||||
// before the caller resets it.
|
// resource's, captured before the caller resets it.
|
||||||
void DeferWireRelease(VkBufferObject&& buffer, Uint64 lastUseSerial);
|
void DeferWireRelease(VkBufferObject&& buffer, Uint64 lastUseSerial);
|
||||||
// Destroy every parked store proven dead by the rules above. Returns how many.
|
// Destroy every parked store proven dead by the rules above. Returns how many.
|
||||||
SizeT SweepDeferredWireReleases();
|
SizeT SweepDeferredWireReleases();
|
||||||
|
// MOBILEGL_IPC_WIRE_DEFERRED_MB (Config.h has the semantics): when the parked bytes
|
||||||
|
// still exceed the budget after a sweep, take the sync point WaitForWireBufferHostAccess
|
||||||
|
// takes - flush what is recorded, wait for it - and sweep again, which retires every
|
||||||
|
// parked store, because none can be tagged past the sync point it just waited out.
|
||||||
|
void EnforceWireDeferredWatermark();
|
||||||
// Teardown: the caller has proven the device idle (Shutdown / RecreateTransientArenas).
|
// Teardown: the caller has proven the device idle (Shutdown / RecreateTransientArenas).
|
||||||
void DestroyAllDeferredWireReleases();
|
void DestroyAllDeferredWireReleases();
|
||||||
|
// THE one Fatal{ResourceUnavailable, "buffer-write-sync"} site (rule I: no second abort
|
||||||
|
// for the census to count), shared by the host write that cannot wait for the GPU and
|
||||||
|
// the watermark that cannot. `site` says which.
|
||||||
|
[[noreturn]] static void WireBufferSyncFatal(const char* site);
|
||||||
#endif
|
#endif
|
||||||
Bool InitializeTransientArenas();
|
Bool InitializeTransientArenas();
|
||||||
static VkBufferUsageFlags GetVkBufferUsage(BufferKind kind);
|
static VkBufferUsageFlags GetVkBufferUsage(BufferKind kind);
|
||||||
|
|||||||
Reference in New Issue
Block a user