From b13af729e4971b5ad05bacb6945e89484a5c91cb Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 6 Oct 2026 12:01:49 -0400 Subject: [PATCH] [Refactor] (P13 W5 S5): the record arm's three knobs move from IpcTable to RecordArmTable, which every record-arm build parses. --- MobileGL/Config.h | 53 +++++++++++-------- MobileGL/ConfigLoader.cpp | 35 ++++++++---- .../DirectVulkan/Renderer/VkBufferManager.cpp | 2 +- .../DirectVulkan/Renderer/VkBufferManager.h | 4 +- .../MG_Remote/Client/PersistentMapTracker.cpp | 4 +- MobileGL/MG_Test/Buffer/SplitBufferTest.cpp | 8 +-- MobileGL/MG_Test/SanityTest.cpp | 6 +-- 7 files changed, 67 insertions(+), 45 deletions(-) diff --git a/MobileGL/Config.h b/MobileGL/Config.h index 1eff35c0..9d807937 100644 --- a/MobileGL/Config.h +++ b/MobileGL/Config.h @@ -462,6 +462,37 @@ namespace MobileGL::MG_Config { NamedPipe = 4, // P6: Windows named pipe (Endpoint = ) }; +#if MOBILEGL_BUILD_RECORD_ARM + // P13 W5: THE RECORD ARM'S OWN KNOBS. They kept their MOBILEGL_IPC_* names (logs, CI lanes and + // notes spell them so), but they tune the record arm - the persistent-map push and Magma's wire + // stores - which runs in a library without a transport too, so they are not IpcTable's. + struct RecordArmTable { + // 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. The same sync point also fires above a fixed + // 1024 parked stores (VkBufferManager::kWireDeferredCountCeiling), because small + // orphans never reach a byte budget. 0 IS THE NEGATIVE CONTROL for both, not "unlimited + // by design": no forced sync, so a one-frame respecify-and-draw loop grows without bound + // and MagmaWireReclaimScenario's watermark cases must go red. + Uint32 WireDeferredMb = 64; + // MOBILEGL_IPC_PERSISTENT_BLOCK_KB: block granularity of the persistent-map push. + // 0 IS A NEGATIVE CONTROL, NOT "unlimited": it disables the push, and + // PersistentCoherentMapScenario must go RED under it (exit gate E3(a)). + Uint32 PersistentBlockKb = 64; + // MOBILEGL_IPC_PERSISTENT_HASH_SUPPRESS: 1 = the persistent-map push ships only + // blocks whose xxHash64 changed since the last push, instead of the whole mapped + // range every verb. 0 restores the whole-range push (A/B control). + Uint32 PersistentHashSuppress = 1; + }; + extern RecordArmTable RecordArm; +#endif + #if MOBILEGL_BUILD_DISAGGREGATED // Parsed once by MG_ConfigLoader::Init(). Defaults to Monolith even here: building the // transport in is not the same as using it, and every existing lane of a build-split @@ -520,20 +551,6 @@ 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. The same sync point also fires above a fixed - // 1024 parked stores (VkBufferManager::kWireDeferredCountCeiling), because small - // orphans never reach a byte budget. 0 IS THE NEGATIVE CONTROL for both, not "unlimited - // by design": no forced sync, so a one-frame respecify-and-draw loop grows without bound - // and MagmaWireReclaimScenario's watermark cases must go red. - Uint32 WireDeferredMb = 64; // MOBILEGL_IPC_SPIN_US: spin before parking on a doorbell, either direction. Uint32 SpinUs = 50; // MOBILEGL_IPC_EVENT_WAIT_MS (PH-6, ID-P7-2): the SERVER's patience, in ms, for ONE @@ -551,14 +568,6 @@ namespace MobileGL::MG_Config { // spent twice. Read by the server only (ServerSpawn.cpp passes it to a spawned child); // the lockstep arm (no kCapRunAheadApply, i.e. Magma) never waits and keeps P5C's Fatal. Uint32 EventWaitMs = 2000; - // MOBILEGL_IPC_PERSISTENT_BLOCK_KB: block granularity of the persistent-map push. - // 0 IS A NEGATIVE CONTROL, NOT "unlimited": it disables the push, and - // PersistentCoherentMapScenario must go RED under it (exit gate E3(a)). - Uint32 PersistentBlockKb = 64; - // MOBILEGL_IPC_PERSISTENT_HASH_SUPPRESS: 1 = the persistent-map push ships only - // blocks whose xxHash64 changed since the last push, instead of the whole mapped - // range every verb. 0 restores the whole-range push (A/B control). - Uint32 PersistentHashSuppress = 1; // MOBILEGL_IPC_BATCH_WAITS: 1 = value-class records (kCtxState / kCtxCso / kCtxObject // with no reply slot) are published without waiting for their own apply; the barrier // is taken at the next pull-reading verb (kCtxVerb syncs, queries, screen rows, and diff --git a/MobileGL/ConfigLoader.cpp b/MobileGL/ConfigLoader.cpp index 93fc8aac..45d00915 100644 --- a/MobileGL/ConfigLoader.cpp +++ b/MobileGL/ConfigLoader.cpp @@ -40,6 +40,9 @@ namespace MobileGL::MG_Config { IpcTable Ipc; Bool MonolithTakesRecordArm = false; #endif +#if MOBILEGL_BUILD_RECORD_ARM + RecordArmTable RecordArm; +#endif } // namespace MobileGL::MG_Config namespace MobileGL::MG_ConfigLoader { @@ -562,20 +565,11 @@ 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); // PH-6 (ID-P7-2; Config.h has the semantics). 0 is NOT admitted: a server with no // patience at all would forfeit a healthy run-ahead client the first time its ring // filled between two of the client's drains, which is an ordinary backlog. ipc.EventWaitMs = QueryEnvUint32("MOBILEGL_IPC_EVENT_WAIT_MS", 2000, 1, 600000); - // 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. - ipc.PersistentBlockKb = QueryEnvUint32("MOBILEGL_IPC_PERSISTENT_BLOCK_KB", 64, 0, 65536); - // Whole-range push is the 0 arm; with it on (default) only blocks whose - // xxHash64 changed since the last push are shipped. - ipc.PersistentHashSuppress = QueryEnvUint32("MOBILEGL_IPC_PERSISTENT_HASH_SUPPRESS", 1, 0, 1); ipc.BatchWaits = QueryEnvUint32("MOBILEGL_IPC_BATCH_WAITS", 1, 0, 1); // The create window. DEFAULT 6, and it works because a link that declares RetainsReplies // keeps a declared answer until its reader takes it - Config.h carries the three reasons it @@ -699,8 +693,8 @@ namespace MobileGL::MG_ConfigLoader { "persistent-block=%uKiB create-window=%u pbo-readback-sync=%u poll-escalate=%u " "adopt-tier=%s allow-t0=%u verb-barrier=%u run-ahead=%u present-credit=%u control-timeout=%ums " "cold-start=%ums strict=%d audit=%d role-split-state=%d affinity='%s' surface=%s", - ipc.RingMb, ipc.StageMb, ipc.WireDeferredMb, ipc.SpinUs, ipc.EventWaitMs, - ipc.PersistentBlockKb, ipc.CreateWindow, ipc.PboReadbackSync, ipc.PollEscalate, + ipc.RingMb, ipc.StageMb, MG_Config::RecordArm.WireDeferredMb, ipc.SpinUs, ipc.EventWaitMs, + MG_Config::RecordArm.PersistentBlockKb, ipc.CreateWindow, ipc.PboReadbackSync, ipc.PollEscalate, ipc.AdoptTier == 0 ? "0" : ipc.AdoptTier == 1 ? "1" : ipc.AdoptTier == 2 ? "2" : "unset(T0)", ipc.AllowAdoptT0, ipc.VerbBarrier, ipc.RunAhead, ipc.PresentCredit, ipc.ControlTimeoutMs, ipc.ColdStartMs, @@ -712,6 +706,22 @@ 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"); } + } +#endif + +#if MOBILEGL_BUILD_RECORD_ARM + // P13 W5: the record arm's knobs (Config.h RecordArmTable), in every build that has the arm. + inline void InitRecordArm() { + MG_Config::RecordArmTable& ipc = MG_Config::RecordArm; + // 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); + // 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. + ipc.PersistentBlockKb = QueryEnvUint32("MOBILEGL_IPC_PERSISTENT_BLOCK_KB", 64, 0, 65536); + // Whole-range push is the 0 arm; with it on (default) only blocks whose + // xxHash64 changed since the last push are shipped. + ipc.PersistentHashSuppress = QueryEnvUint32("MOBILEGL_IPC_PERSISTENT_HASH_SUPPRESS", 1, 0, 1); 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 " @@ -730,6 +740,9 @@ namespace MobileGL::MG_ConfigLoader { InitBackendType(); InitFeatures(); +#if MOBILEGL_BUILD_RECORD_ARM + InitRecordArm(); +#endif #if MOBILEGL_BUILD_DISAGGREGATED // After InitFeatures, so the one line InitIpc logs is the last word on this run's // configuration, and before the accepted-env map is destroyed just below. diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp index de499189..ed5d8f0f 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp @@ -1111,7 +1111,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } void VkBufferManager::EnforceWireDeferredWatermark() { - const Uint64 budget = static_cast(MG_Config::Ipc.WireDeferredMb) * 1024u * 1024u; + const Uint64 budget = static_cast(MG_Config::RecordArm.WireDeferredMb) * 1024u * 1024u; // TWO TRIGGERS, ONE SWITCH. Bytes are what the knob names, but a VkBuffer costs per // OBJECT as well as per byte, and small orphans never reach a byte budget: measured on // bsl-esc-menu-854 (spawn, lavapipe), one stretch with no submission parked 12,498 stores diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h index d0aeebb9..32ad2b23 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h @@ -232,7 +232,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // back, and none was persistently mapped, in between - so a memo of resolved // slices needs no per-buffer re-check. See AcquirePersistentMap for the mapping half. Uint64 GetSliceEpochCounter() const { return m_sliceEpochCounter; } -#if MOBILEGL_BUILD_DISAGGREGATED +#if MOBILEGL_BUILD_RECORD_ARM // Bumped every time this manager destroys a WIRE store's VkBuffer (see // m_wireStoreDestroyEpoch). Unchanged since a memo was taken means no VkBuffer handle // that memo names can have been freed and re-minted in between, which is the one @@ -408,7 +408,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Vector> m_deferredBufferReleases; Vector>> m_deferredResourceReleases; Vector> m_liveResources; -#if MOBILEGL_BUILD_DISAGGREGATED +#if MOBILEGL_BUILD_RECORD_ARM std::unordered_map m_wireBuffers; // See DeferredWireRelease. ONE FLAT LIST rather than the per-frame-slot buckets above: // the whole point is that these entries do not wait for a frame slot to come round. diff --git a/MobileGL/MG_Remote/Client/PersistentMapTracker.cpp b/MobileGL/MG_Remote/Client/PersistentMapTracker.cpp index b6e781ae..22d8d4df 100644 --- a/MobileGL/MG_Remote/Client/PersistentMapTracker.cpp +++ b/MobileGL/MG_Remote/Client/PersistentMapTracker.cpp @@ -700,7 +700,7 @@ namespace MobileGL::MG_Remote::Client { } Uint64 PersistentMapTracker::BlockBytes() { - return static_cast(MG_Config::Ipc.PersistentBlockKb) * 1024ull; + return static_cast(MG_Config::RecordArm.PersistentBlockKb) * 1024ull; } Bool PersistentMapTracker::PushIsArmed() { @@ -1049,7 +1049,7 @@ namespace MobileGL::MG_Remote::Client { // in both directions. BlockHashState* state = nullptr; const Uint8* shadow = nullptr; - if (MG_Config::Ipc.PersistentHashSuppress != 0) { + if (MG_Config::RecordArm.PersistentHashSuppress != 0) { shadow = buffer.MappedData(); if (shadow != nullptr) { state = &m_blockHashes[buffer.GetLifetimeId()]; diff --git a/MobileGL/MG_Test/Buffer/SplitBufferTest.cpp b/MobileGL/MG_Test/Buffer/SplitBufferTest.cpp index 520894bd..024744ce 100644 --- a/MobileGL/MG_Test/Buffer/SplitBufferTest.cpp +++ b/MobileGL/MG_Test/Buffer/SplitBufferTest.cpp @@ -64,13 +64,13 @@ namespace { protected: void SetUp() override { m_transport = MG_Config::Transport; - m_blockKb = MG_Config::Ipc.PersistentBlockKb; + m_blockKb = MG_Config::RecordArm.PersistentBlockKb; m_adoptTier = MG_Config::Ipc.AdoptTier; m_pipeStats = MG_Config::Features.PipeStats; m_context = Move(MG_State::pGLContext); MG_State::pGLContext = MakeUnique(); MG_Config::Transport = MG_Config::TransportMode::InProcess; - MG_Config::Ipc.PersistentBlockKb = 64; + MG_Config::RecordArm.PersistentBlockKb = 64; MG_Config::Ipc.AdoptTier = 2; MG_Config::Features.PipeStats = true; MG_Util::PipeStats::Init(); @@ -87,7 +87,7 @@ namespace { PersistentMapTracker::Instance().ClearForTest(); MG_State::pGLContext = Move(m_context); MG_Config::Transport = m_transport; - MG_Config::Ipc.PersistentBlockKb = m_blockKb; + MG_Config::RecordArm.PersistentBlockKb = m_blockKb; MG_Config::Ipc.AdoptTier = m_adoptTier; MG_Config::Features.PipeStats = m_pipeStats; MG_Util::PipeStats::Init(); @@ -470,7 +470,7 @@ TEST_F(SplitBufferSet, BothEdgesOfAWriteMapPublishOneStateRecord) { // whole buffer at unmap, so a scenario that unmapped before reading back went green and the // control was dead. TEST_F(SplitBufferSet, AZeroBlockSizeTurnsThePushOffRatherThanMakingItUnlimited) { - MG_Config::Ipc.PersistentBlockKb = 0; + MG_Config::RecordArm.PersistentBlockKb = 0; constexpr SizeT kSize = 128u * 1024u; auto buffer = MakeBuffer(23u, kSize); diff --git a/MobileGL/MG_Test/SanityTest.cpp b/MobileGL/MG_Test/SanityTest.cpp index b49036dd..ed451c6c 100644 --- a/MobileGL/MG_Test/SanityTest.cpp +++ b/MobileGL/MG_Test/SanityTest.cpp @@ -4592,12 +4592,12 @@ TEST(DirectGLESBufferDrawProbe, UnderSplitTheRecordAloneAnswersTheLiveHostMapQue // case reaches is optional. const Uint64 previousPush = MG_Config::Features.PipePush; const auto previousTransport = MG_Config::Transport; - const Uint32 previousBlockKb = MG_Config::Ipc.PersistentBlockKb; + const Uint32 previousBlockKb = MG_Config::RecordArm.PersistentBlockKb; MG_Pipe::MGPipeResourceOps ops{}; MG_Config::Features.PipePush |= MG_Pipe::kMGPipeSubsystemResources; MG_Pipe::MGPipeSetResourceOps(&ops); MG_Config::Transport = MG_Config::TransportMode::InProcess; - MG_Config::Ipc.PersistentBlockKb = 64; + MG_Config::RecordArm.PersistentBlockKb = 64; // P5 c1 / R-8: UNDER SPLIT THE OP TABLE IS NO LONGER THE ARMING CONDITION, and this case is // the first place that shows. `MGPipeSetResourceOps(&ops)` is the SERVER's registration; the // client's liveness gate now reads the caps mirror's consumer mask instead, because under a @@ -4691,7 +4691,7 @@ TEST(DirectGLESBufferDrawProbe, UnderSplitTheRecordAloneAnswersTheLiveHostMapQue BufferImpl::g_backendBufferResources.ReleaseByHandle(res); } - MG_Config::Ipc.PersistentBlockKb = previousBlockKb; + MG_Config::RecordArm.PersistentBlockKb = previousBlockKb; MG_Config::Transport = previousTransport; MG_Pipe::MGPipeSetResourceOps(nullptr); MG_Config::Features.PipePush = previousPush;