mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +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
|
||||
// no cut is Fatal{RingOverrun, "SEG_STAGE"} rather than allowed to exceed it.
|
||||
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.
|
||||
Uint32 SpinUs = 50;
|
||||
// MOBILEGL_IPC_PERSISTENT_BLOCK_KB: block granularity of the persistent-map push.
|
||||
|
||||
@@ -392,6 +392,9 @@ namespace MobileGL::MG_ConfigLoader {
|
||||
// are sanity, not policy.
|
||||
ipc.RingMb = QueryEnvUint32("MOBILEGL_IPC_RING_MB", 8, 1, 1024);
|
||||
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);
|
||||
// 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.
|
||||
@@ -462,10 +465,10 @@ namespace MobileGL::MG_ConfigLoader {
|
||||
if (MG_Config::Transport == MG_Config::TransportMode::Monolith) return;
|
||||
// One line, on the arm where these numbers decide behaviour, because every one of
|
||||
// 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 "
|
||||
"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,
|
||||
static_cast<int>(ipc.StrictErrors), static_cast<int>(ipc.Audit),
|
||||
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 "
|
||||
"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) {
|
||||
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");
|
||||
|
||||
@@ -365,10 +365,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
uploaded = StagedWireRangeCopy(*resource, bytes, static_cast<SizeT>(offset),
|
||||
static_cast<SizeT>(size));
|
||||
}
|
||||
if (!uploaded && !WaitForWireBufferHostAccess(*resource)) {
|
||||
MGLOG_F("Magma: Fatal{ResourceUnavailable, \"buffer-write-sync\"}");
|
||||
std::abort();
|
||||
}
|
||||
if (!uploaded && !WaitForWireBufferHostAccess(*resource)) WireBufferSyncFatal("host-write");
|
||||
}
|
||||
if (!uploaded) {
|
||||
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) -
|
||||
// so the only cadence that tracks the workload is the workload itself.
|
||||
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() {
|
||||
if (m_deferredWireReleases.empty() || pVulkanRenderer == nullptr) return 0;
|
||||
if (m_deferredWireReleases.empty()) return 0;
|
||||
SizeT dead = 0;
|
||||
if (pVulkanRenderer->IsSubmitIndexComplete(pVulkanRenderer->GetSyncPointSubmitIndex())) {
|
||||
// Idle: nothing recorded, every submission retired. Every parked store is dead,
|
||||
// including one tagged for a batch that was abandoned instead of submitted.
|
||||
if (pVulkanRenderer == nullptr ||
|
||||
pVulkanRenderer->IsSubmitIndexComplete(pVulkanRenderer->GetSyncPointSubmitIndex())) {
|
||||
// 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();
|
||||
} else {
|
||||
// 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.
|
||||
Uint64 bytes = 0;
|
||||
};
|
||||
// Park a wire store, then sweep. `lastUseSerial` is the releasing resource's, captured
|
||||
// before the caller resets it.
|
||||
// Park a wire store, sweep, then hold the watermark. `lastUseSerial` is the releasing
|
||||
// resource's, captured before the caller resets it.
|
||||
void DeferWireRelease(VkBufferObject&& buffer, Uint64 lastUseSerial);
|
||||
// Destroy every parked store proven dead by the rules above. Returns how many.
|
||||
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).
|
||||
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
|
||||
Bool InitializeTransientArenas();
|
||||
static VkBufferUsageFlags GetVkBufferUsage(BufferKind kind);
|
||||
|
||||
Reference in New Issue
Block a user