mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Config, MG_Backend, MG_Test] (Disaggregated): P5e c0e - the two knobs and the caps arm
- IpcTable::RunAhead (MOBILEGL_IPC_RUN_AHEAD, default 1, forced 0 under PipeVerify beside BatchWaits) and IpcTable::PresentCredit (MOBILEGL_IPC_PRESENT_CREDIT, default 1, range 1..8). Both parsed unconditionally like every other IPC knob and both in the IPC log line, because a knob that silently means nothing on one arm of an A/B is how an A/B stops being one. - RUN_AHEAD=0 is an A/B CONTROL and not a negative one: identical server code, identical records, identical fill decision, and only the client's wait differs. VERB_BARRIER=0 keeps its own meaning as the lockstep arm's negative control. - MG_Backend/Init.cpp: kMGPipeP5eRunAheadReady = false, and the caps arm goes through MGPipeRunAheadCapBitsFor so the "DirectGLES only, and only once the integration commit flips the constant" decision has one spelling. A constant and not a knob: an operator must not be able to turn a half-migrated server into a run-ahead one, because the failure mode is a torn read that renders wrong rather than aborting. - MagmaPipeIdentityTest pins both halves - no bit 10 for DirectVulkan whatever the integration constant says, bit 10 for DirectGLES only when it is true, and that one bit and nothing else. - The push default becomes kMGPipeSubsystemsMigratedAtP5e (0x3fff). Inert: bit 13 is not in kMGPipeWiredSubsystems, so no emission gate opens on it; 0x1fff survives as P5e's "everything P4a had and nothing of mine" control.
This commit is contained in:
+32
-3
@@ -322,9 +322,9 @@ namespace MobileGL::MG_Config {
|
||||
// 0 - the only shipped value until the migration lands - is "pull everything",
|
||||
// i.e. exactly today's behaviour, and is the default of a PULL build, where the
|
||||
// knob is meaningless anyway. A PUSH build defaults to every subsystem migrated so
|
||||
// far (MG_Pipe::kMGPipeSubsystemsMigratedAtP4a), so MOBILEGL_PIPE_PUSH=0 in the
|
||||
// environment is the all-pull control and 0x1ff (kMGPipeSubsystemsMigratedAtP3a) is
|
||||
// the "everything before P4a" control P4a's A/B is run against - each phase's
|
||||
// far (MG_Pipe::kMGPipeSubsystemsMigratedAtP5e = 0x3fff), so MOBILEGL_PIPE_PUSH=0 in
|
||||
// the environment is the all-pull control and 0x1fff (kMGPipeSubsystemsMigratedAtP4a)
|
||||
// is the "everything before P5e" control P5e's A/B is run against - each phase's
|
||||
// constant survives as the next phase's control, which is why none of them is ever
|
||||
// edited. Accepts decimal or 0x-prefixed hex, and operators pass it as hex, so the
|
||||
// bits are listed here (MG_Pipe/MGPipe.h owns them):
|
||||
@@ -342,6 +342,9 @@ namespace MobileGL::MG_Config {
|
||||
// 0x800 samplers (sampler CSO, sampler view, set_sampler_views /
|
||||
// bind_sampler_states / set_shader_images) - requires 0x400
|
||||
// 0x1000 programs (shader CSO, set_draw/dispatch_program, global constants)
|
||||
// 0x2000 buffer binding points (set_shader_buffers, the three indexed binding-point
|
||||
// classes and the dirty bits 15/16/17) - requires 0x80
|
||||
// (every MGPBufferRange::Res names a Buffer handle; P5e)
|
||||
// A dependency that is not met is REFUSED with one ERROR naming both bits and the
|
||||
// family runs its legacy arm; it is never half-run.
|
||||
// 1<<63 NOT a subsystem, a BEHAVIOUR: turn OFF client-side content addressing of
|
||||
@@ -502,6 +505,32 @@ namespace MobileGL::MG_Config {
|
||||
// GLContext by the client's residual fill and a free-running queue lets the server
|
||||
// read a FUTURE value of them.
|
||||
Uint32 VerbBarrier = 1;
|
||||
// MOBILEGL_IPC_RUN_AHEAD (P5e, MG_Remote/CONTRACT-P5E.md §1): 1 = after publishing an
|
||||
// UNBARRIERED record the client returns immediately instead of waiting for its apply.
|
||||
// It is one half of a conjunction and never a switch on its own - the client arms
|
||||
// run-ahead only when the server also publishes kCapRunAheadApply, so on Magma, and on
|
||||
// Espryt before the P5e integration commit, 1 means exactly what 0 means and logs once
|
||||
// saying so.
|
||||
//
|
||||
// 0 IS THE A/B CONTROL AND NOT A NEGATIVE ONE: the server code, the fill decision and
|
||||
// every record are identical on both arms, and the only difference is whether the
|
||||
// client waits. That is what makes "is the picture the same" a question about the wait
|
||||
// rule alone. MOBILEGL_IPC_VERB_BARRIER=0 keeps its own meaning and stays the
|
||||
// lockstep arm's negative control; under run-ahead it is the one that must go red, at
|
||||
// the first barriered row's stale pull.
|
||||
//
|
||||
// Forced to 0 by MOBILEGL_PIPE_VERIFY, beside BatchWaits: the comparator needs a
|
||||
// client-filled gPipeInputs block for every verb, and run-ahead is precisely the
|
||||
// arm that stops filling it.
|
||||
Uint32 RunAhead = 1;
|
||||
// MOBILEGL_IPC_PRESENT_CREDIT (P5e, ruling 4 / ID-92): how many presents the client may
|
||||
// have in flight before it waits for a swap to come back. 1 = the client publishes
|
||||
// frame N+1's records while the server applies and swaps frame N - one frame of
|
||||
// overlap, at most one frame of added latency - and the CREDIT, never the ring's bytes,
|
||||
// is what paces a run-ahead client. 2 is a device MEASUREMENT arm: it buys no CPU on a
|
||||
// client that is already CPU-bound and costs a frame of latency, which is why the
|
||||
// default is 1 and not "as deep as the ring".
|
||||
Uint32 PresentCredit = 1;
|
||||
// MOBILEGL_IPC_STRICT_ERRORS: promote a BARRIER-PULLED field read - and, in a split
|
||||
// build, the seven sticky forwards that are otherwise exempt - from "count it in
|
||||
// rsp" to Fatal (R-7.3).
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#include "Config.h"
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// For kMGPipeSubsystemsMigratedAtP4a, the push build's PipePush default (the P2 and P3a
|
||||
// For kMGPipeSubsystemsMigratedAtP5e, the push build's PipePush default (the P2, P3a and P4a
|
||||
// constants beside it are the phase-by-phase controls, not the default). Push-only, so the
|
||||
// pull build's translation unit is unchanged.
|
||||
#include <MG_Pipe/MGPipe.h>
|
||||
@@ -263,7 +263,7 @@ namespace MobileGL::MG_ConfigLoader {
|
||||
// environment is the all-subsystems-pull control that reproduces P1 exactly, and
|
||||
// kMGPipeSubsystemsMigratedAtP3a (0x1ff) is the phase-by-phase control - P4a's four
|
||||
// subsystems off, everything P3a landed still on.
|
||||
features.PipePush = QueryEnvUint64("MOBILEGL_PIPE_PUSH", MG_Pipe::kMGPipeSubsystemsMigratedAtP4a);
|
||||
features.PipePush = QueryEnvUint64("MOBILEGL_PIPE_PUSH", MG_Pipe::kMGPipeSubsystemsMigratedAtP5e);
|
||||
#else
|
||||
// Meaningless in a pull build: there is nothing to push. Config.h documents 0 as
|
||||
// "pull everything" and that stays literally true.
|
||||
@@ -379,6 +379,17 @@ namespace MobileGL::MG_ConfigLoader {
|
||||
// point of use, which is where the "P11" in the message belongs.
|
||||
ipc.AdoptTier = QueryEnvUint32("MOBILEGL_IPC_ADOPT_TIER", 2, 0, 2);
|
||||
ipc.VerbBarrier = QueryEnvUint32("MOBILEGL_IPC_VERB_BARRIER", 1, 0, 1);
|
||||
// P5e (MG_Remote/CONTRACT-P5E.md §1). The wait rule's A/B, parsed here like every
|
||||
// other IPC knob and armed only where the server publishes kCapRunAheadApply.
|
||||
ipc.RunAhead = QueryEnvUint32("MOBILEGL_IPC_RUN_AHEAD", 1, 0, 1);
|
||||
// ... and forced OFF by the verify harness for BatchWaits' reason, one step further:
|
||||
// run-ahead's whole point is that the client stops filling gPipeInputs for an
|
||||
// unbarriered record, and the comparator has nothing left to compare when it does.
|
||||
if (MG_Config::Features.PipeVerify) ipc.RunAhead = 0;
|
||||
// The present credit (ruling 4). 1 is one frame of overlap; 8 is the ceiling because a
|
||||
// deeper queue buys nothing on a CPU-bound client and pays for it in latency. 0 is NOT
|
||||
// admitted: a credit of zero would mean "publish no present at all".
|
||||
ipc.PresentCredit = QueryEnvUint32("MOBILEGL_IPC_PRESENT_CREDIT", 1, 1, 8);
|
||||
ipc.StrictErrors = QueryEnvFlag("MOBILEGL_IPC_STRICT_ERRORS");
|
||||
ipc.Audit = QueryEnvFlag("MOBILEGL_IPC_AUDIT");
|
||||
QueryEnvVariable("MOBILEGL_IPC_SERVER_AFFINITY", ipc.ServerAffinity, "auto");
|
||||
@@ -387,9 +398,11 @@ namespace MobileGL::MG_ConfigLoader {
|
||||
// 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 "
|
||||
"adopt-tier=%u verb-barrier=%u strict=%d audit=%d affinity='%s'",
|
||||
"adopt-tier=%u verb-barrier=%u run-ahead=%u present-credit=%u strict=%d "
|
||||
"audit=%d affinity='%s'",
|
||||
ipc.RingMb, ipc.StageMb, ipc.SpinUs, ipc.PersistentBlockKb, ipc.AdoptTier,
|
||||
ipc.VerbBarrier, static_cast<int>(ipc.StrictErrors), static_cast<int>(ipc.Audit),
|
||||
ipc.VerbBarrier, ipc.RunAhead, ipc.PresentCredit,
|
||||
static_cast<int>(ipc.StrictErrors), static_cast<int>(ipc.Audit),
|
||||
ipc.ServerAffinity.c_str());
|
||||
if (ipc.VerbBarrier == 0) {
|
||||
MGLOG_W("Config: MOBILEGL_IPC_VERB_BARRIER=0 is the R-1 NEGATIVE CONTROL and is "
|
||||
|
||||
@@ -81,6 +81,22 @@ namespace MobileGL::MG_Backend {
|
||||
// DirectVulkan registers NO resource ops - MGPipeSetResourceOps has exactly one caller
|
||||
// in the whole tree and it is Managers.cpp:2594 - so bit 7 is CLEAR for it, which is
|
||||
// the same fact ObjectSubsystemControlScenario already pins from the client side.
|
||||
// P5e (MG_Remote/CONTRACT-P5E.md §6.2). THE ONE CONSTANT THE WHOLE PHASE HANGS ON.
|
||||
//
|
||||
// kCapRunAheadApply says "this server applies an unbarriered record without reading
|
||||
// client memory". That is only true once EVERY per-draw family reads records instead
|
||||
// of the frontend - vi, sb, pg, tx2 and fb all land before it is - so the caps arm
|
||||
// below is gated on this constant, which the P5e INTEGRATION COMMIT flips to true
|
||||
// after the last of them. Until then every P5e package lands with the wait rule, the
|
||||
// static WaitClass column and the barriered predicate compiled and INERT: the client
|
||||
// never latches RunAheadArmed, so it runs today's lockstep path byte for byte.
|
||||
//
|
||||
// It is a constant and not a knob on purpose. An operator cannot turn a half-migrated
|
||||
// server into a run-ahead one, because the failure mode is not a slow frame - it is
|
||||
// the apply thread reading client memory that has already moved, which renders wrong
|
||||
// rather than aborting.
|
||||
constexpr Bool kMGPipeP5eRunAheadReady = false;
|
||||
|
||||
Uint64 ConsumedSubsystemsFor(BackendType type) {
|
||||
switch (type) {
|
||||
case BackendType::DirectGLES: return MG_Pipe::kMGPipeSubsystemsMigratedAtP4a;
|
||||
@@ -166,6 +182,21 @@ namespace MobileGL::MG_Backend {
|
||||
serverBackend->GetBackendFunctions().GL.EndTransformFeedback != nullptr) {
|
||||
capBits |= MG_Pipe::kCapBackendOwnsXfbCapture;
|
||||
}
|
||||
// P5e (CONTRACT-P5E.md §1, §6): kCapRunAheadApply, THE DIRECTGLES ARM AND ONLY IT.
|
||||
//
|
||||
// Magma is deliberately absent and is not an omission: it keeps the lockstep for
|
||||
// the whole of P5e, its four apply-thread allocator sites are real debt P7 retires,
|
||||
// and MGPipeApplierCurrentRecordIsBarriered() answers true for every record on a
|
||||
// server that does not publish this bit - which is exactly what keeps those probes
|
||||
// and its BARRIER_PULLED reads inside P5C's semantics and its rsp accounting
|
||||
// honest. Publishing the bit here for DirectVulkan would turn accounted pulls into
|
||||
// torn ones, so MagmaPipeIdentityTest pins its absence.
|
||||
//
|
||||
// The Espryt arm is gated on kMGPipeP5eRunAheadReady, which is false until the
|
||||
// integration commit: the bit is what ARMS the client, so every package before it
|
||||
// lands inert.
|
||||
capBits |= MG_Pipe::MGPipeRunAheadCapBitsFor(MG_Config::ActiveBackendType,
|
||||
kMGPipeP5eRunAheadReady);
|
||||
session.SetCapabilityBits(capBits);
|
||||
session.SetBackend(loop.Backend());
|
||||
|
||||
|
||||
@@ -56,7 +56,8 @@ namespace {
|
||||
X(MagmaPipeIdentityTest, AnIdleSlotIsRetiredAndReusedWithANewGeneration) \
|
||||
X(MagmaPipeIdentityTest, AReusedSlotDoesNotServeItsPredecessorsMemo) \
|
||||
X(MagmaPipeIdentityTest, TheAbaControlKnobServesTheStaleMemoAcrossAReusedSlot) \
|
||||
X(MagmaPipeIdentityTest, ALiveObjectKeepsItsSlotItsGenerationAndItsMemo)
|
||||
X(MagmaPipeIdentityTest, ALiveObjectKeepsItsSlotItsGenerationAndItsMemo) \
|
||||
X(MagmaPipeIdentityTest, AMagmaServerNeverPublishesTheRunAheadCapBit)
|
||||
|
||||
#define MGL_DECLARE_PULL_SKIP(Suite, Name) \
|
||||
TEST(Suite, Name) { GTEST_SKIP() << "compiled only under MOBILEGL_PIPE_PUSH"; }
|
||||
@@ -213,5 +214,48 @@ namespace {
|
||||
<< "a live object's memo was cleared without its slot changing owner";
|
||||
EXPECT_EQ(mint.Count(), 1u);
|
||||
}
|
||||
|
||||
// ---- P5e (MG_Remote/CONTRACT-P5E.md §1, §6, ruling 12) -------------------------------
|
||||
//
|
||||
// MAGMA DOES NOT RUN AHEAD, AND THIS IS WHERE THAT IS A TEST RATHER THAN A COMMENT.
|
||||
// kCapRunAheadApply is the client's whole permission to publish a record and move on: the
|
||||
// moment a server sets it, the apply thread promises it reads nothing of the client's. That
|
||||
// promise is FALSE for Magma for the whole of P5e - the four apply-thread allocator sites
|
||||
// inside MagmaP7AllocatorDebtScope are real debt P7 retires, and
|
||||
// MGPipeApplierCurrentRecordIsBarriered() answering true for every record on a server
|
||||
// without the bit is exactly what keeps them inside P5C's semantics and keeps rsp honest.
|
||||
//
|
||||
// The arm is a pure function precisely so this case can reach it: InitSplitRoles needs a
|
||||
// live session, a backend and a handshake, and none of those belong in a unit lane. RED
|
||||
// ONCE by making MGPipeRunAheadCapBitsFor answer for DirectVulkan too (the exact
|
||||
// perturbation the phase's red-once list names) - both EXPECTs below fail, by name.
|
||||
TEST_F(MagmaPipeIdentityTest, AMagmaServerNeverPublishesTheRunAheadCapBit) {
|
||||
// Whatever the integration constant says. `true` is what the P5e integration commit
|
||||
// will pass, so the Magma answer is pinned on BOTH sides of that flip and the case
|
||||
// does not quietly stop asserting anything the day the constant moves.
|
||||
EXPECT_EQ(MG_Pipe::MGPipeRunAheadCapBitsFor(BackendType::DirectVulkan, /*ready=*/false) &
|
||||
static_cast<Uint64>(MG_Pipe::kCapRunAheadApply),
|
||||
0u);
|
||||
EXPECT_EQ(MG_Pipe::MGPipeRunAheadCapBitsFor(BackendType::DirectVulkan, /*ready=*/true) &
|
||||
static_cast<Uint64>(MG_Pipe::kCapRunAheadApply),
|
||||
0u);
|
||||
|
||||
// And the Espryt half, so the case says what the arm IS and not only what it is not:
|
||||
// the bit is published for DirectGLES and ONLY once the integration commit flips
|
||||
// kMGPipeP5eRunAheadReady. Until then every P5e package lands with the wait rule inert.
|
||||
EXPECT_EQ(MG_Pipe::MGPipeRunAheadCapBitsFor(BackendType::DirectGLES, /*ready=*/false) &
|
||||
static_cast<Uint64>(MG_Pipe::kCapRunAheadApply),
|
||||
0u);
|
||||
EXPECT_EQ(MG_Pipe::MGPipeRunAheadCapBitsFor(BackendType::DirectGLES, /*ready=*/true) &
|
||||
static_cast<Uint64>(MG_Pipe::kCapRunAheadApply),
|
||||
static_cast<Uint64>(MG_Pipe::kCapRunAheadApply));
|
||||
|
||||
// Bit 10 and nothing else: the arm contributes ONE bit, so a future editor cannot fold
|
||||
// an unrelated capability into it and have the two pins above still pass.
|
||||
EXPECT_EQ(MG_Pipe::MGPipeRunAheadCapBitsFor(BackendType::DirectGLES, /*ready=*/true),
|
||||
static_cast<Uint64>(MG_Pipe::kCapRunAheadApply));
|
||||
static_assert(static_cast<Uint64>(MG_Pipe::kCapRunAheadApply) == (1ull << 10),
|
||||
"kCapRunAheadApply moved bit; CONTRACT-P5E table 0 names bit 10");
|
||||
}
|
||||
#endif // MOBILEGL_PIPE_PUSH
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user