[Feat] (P13 W3b): subsystem bits 0-13 are fixed on and refused by name at startup, the legacy-memo and texel-retain knobs are gone

This commit is contained in:
2026-10-06 04:04:48 -04:00
parent 5e547add80
commit 30ebae4346
8 changed files with 34 additions and 179 deletions
+4 -9
View File
@@ -370,6 +370,10 @@ namespace MobileGL::MG_Config {
// 1<<63 NOT a subsystem, a BEHAVIOUR: turn OFF client-side content addressing of
// CSOs, so every pipeline-version change mints a fresh CSO and the map is
// never probed. The negative control the CSO design is measured against.
// P13 W3b (ID-P13-3): bits 0-13 are FIXED ON. Their pre-handle arms are gone, so a mask
// that clears one is refused by name at startup (Fatal{PipeSubsystemsFixedOn},
// ConfigLoader.cpp); bit 63 is the one switch left. Everything above about "refused,
// runs its legacy arm" describes code that is unreachable since then and goes in W6.
Uint64 PipePush = 0;
// MOBILEGL_PIPE_VERIFY: per-draw, per-FIELD shadow comparison of the pushed state
// against a snapshot taken from GLContext the old way, printing the first field
@@ -410,15 +414,6 @@ namespace MobileGL::MG_Config {
// MOBILEGL_PIPE_STATS: dump the boundary counters (bytes, calls, roundtrips,
// texture pulls, upload shapes, residual-block bytes, index mirror bytes).
Bool PipeStats = false;
// MOBILEGL_PIPE_LEGACY_MEMOS: keep the pre-handle registries and TwinLookupMemos
// alive so the first handle waves have a real old-versus-new arm to be compared
// against. ON by default for the whole migration window, deleted with the pull
// path itself.
Bool PipeLegacyMemos = true;
// MOBILEGL_PIPE_TEXEL_RETAIN_MB: LRU budget for texels retained against a
// server-initiated texture re-send. Default 0, i.e. OFF: MipmapStorage already
// holds a complete CPU shadow, so this cache buys latency, never correctness.
Uint32 PipeTexelRetainMb = 0;
// MOBILEGL_PIPE_INDEX_MIRROR_MB: budget for the server-side index host mirror,
// which is what lets primitive-restart rewriting and multi-draw flattening stay on
// the server without shipping index bytes per draw. Over budget it degrades to
+15 -6
View File
@@ -390,6 +390,21 @@ namespace MobileGL::MG_ConfigLoader {
// 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::kMGPipeSubsystemsMigratedAtP5e);
// P13 W3b (ID-P13-3): bits 0-13 are fixed on. The arms a cleared bit used to select
// (the pre-handle memos, the pull build's frontend reads) are gone, so a cleared bit has
// nothing to run; it is refused here, by name, before any backend is brought up, rather
// than discovered as an armless subsystem at its first draw. Bit 63 (CSO content
// addressing off) stays a switch.
if ((features.PipePush & MG_Pipe::kMGPipeSubsystemsMigratedAtP5e) !=
MG_Pipe::kMGPipeSubsystemsMigratedAtP5e) {
MGLOG_F("MGPipe: Fatal{PipeSubsystemsFixedOn, \"MOBILEGL_PIPE_PUSH=0x%llx clears subsystem "
"bit(s) 0x%llx; bits 0-13 are fixed on since P13 - only bit 63 may be set or "
"cleared\"}",
static_cast<unsigned long long>(features.PipePush),
static_cast<unsigned long long>(MG_Pipe::kMGPipeSubsystemsMigratedAtP5e &
~features.PipePush));
std::abort();
}
features.PipeVerify = QueryEnvFlag("MOBILEGL_PIPE_VERIFY");
// Defaults ON: read as a tri-state so only an explicitly falsy value turns it off.
features.PipeVerifyFatal =
@@ -398,12 +413,6 @@ namespace MobileGL::MG_ConfigLoader {
QueryEnvVariable("MOBILEGL_PIPE_POISON_OMIT", features.PipePoisonOmit, "");
features.PipeHandleAbaControl = QueryEnvFlag("MOBILEGL_PIPE_HANDLE_ABA_CONTROL");
features.PipeStats = QueryEnvFlag("MOBILEGL_PIPE_STATS");
// Defaults ON, so the flag has to be read as a tri-state rather than as a plain
// truthy check: unset must keep the memos, and only an explicitly falsy value may
// drop them.
features.PipeLegacyMemos =
QueryEnvQuirkOverride("MOBILEGL_PIPE_LEGACY_MEMOS") != MG_Config::QuirkOverride::ForceOff;
features.PipeTexelRetainMb = QueryEnvUint32("MOBILEGL_PIPE_TEXEL_RETAIN_MB", 0, 0, 4096);
features.PipeIndexMirrorMb = QueryEnvUint32("MOBILEGL_PIPE_INDEX_MIRROR_MB", 64, 0, 4096);
features.PipeStatsPeriod = QueryEnvUint32("MOBILEGL_PIPE_STATS_PERIOD", 120, 1, 1000000);
QueryEnvVariable("MOBILEGL_PIPE_STATS_FILE", features.PipeStatsFile, "");
+7 -12
View File
@@ -476,7 +476,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
EsprytSlotArmVerdict CurrentEsprytSlotArmVerdict() {
return ClassifyEsprytSlotArm(
(MG_Config::Features.PipePush & MG_Pipe::kMGPipeSubsystemEsprytSlots) != 0,
MG_Config::Features.PipeLegacyMemos);
/*legacyMemosEnabled=*/true);
}
void DiagnoseEsprytSlotArm() {
@@ -3317,7 +3317,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// and NoArm is unreachable for it. Said out loud rather than assumed, because the
// knob is still answered below.
const PipeSubsystemArmVerdict verdict =
ClassifyPipeSubsystemArm(bitSet, MG_Config::Features.PipeLegacyMemos,
ClassifyPipeSubsystemArm(bitSet, /*legacyMemosEnabled=*/true,
/*legacyArmSurvivesLegacyMemos=*/true);
// Unreachable for this family by the argument above, and stated as a stop anyway:
// the two P3a families answer an armless verdict the same way, and an unreachable
@@ -3326,11 +3326,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
StopOnArmlessPipeSubsystem("MOBILEGL_PIPE_PUSH leaves kMGPipeSubsystemResources "
"(bit 7) clear and the pre-handle buffer arm is gone");
}
if (verdict == PipeSubsystemArmVerdict::Legacy && !MG_Config::Features.PipeLegacyMemos) {
MGLOG_W("MGPipe: MOBILEGL_PIPE_PUSH leaves kMGPipeSubsystemResources (bit 7) clear while "
"MOBILEGL_PIPE_LEGACY_MEMOS=0 asks for the pre-handle arms to be gone; the buffer "
"ops table is compiled unconditionally, so the LEGACY arm is what this process runs");
}
const Bool enabled = verdict == PipeSubsystemArmVerdict::Handles;
MGLOG_D("MGPipe: Espryt resource family runs the %s arm", enabled ? "handle" : "legacy");
return enabled;
@@ -3363,7 +3358,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Unlike the buffer family, this one's legacy arm IS conditional: the pre-handle
// SyncToBackend body and the twin memos it reads are inside MOBILEGL_PIPE_LEGACY_MEMOS.
const PipeSubsystemArmVerdict verdict =
ClassifyPipeSubsystemArm(bitSet, MG_Config::Features.PipeLegacyMemos,
ClassifyPipeSubsystemArm(bitSet, /*legacyMemosEnabled=*/true,
/*legacyArmSurvivesLegacyMemos=*/false);
if (verdict != PipeSubsystemArmVerdict::Handles &&
(verdict == PipeSubsystemArmVerdict::NoArm || !kLegacyVaoArmCompiled)) {
@@ -4942,7 +4937,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
}
const BufferImpl::PipeSubsystemArmVerdict verdict = BufferImpl::ClassifyPipeSubsystemArm(
bitSet && !refused, MG_Config::Features.PipeLegacyMemos,
bitSet && !refused, /*legacyMemosEnabled=*/true,
/*legacyArmSurvivesLegacyMemos=*/false);
if (verdict == BufferImpl::PipeSubsystemArmVerdict::NoArm) {
BufferImpl::StopOnArmlessPipeSubsystem(
@@ -4986,7 +4981,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
"kMGPipeSubsystemTextureResources (bit 10)");
}
const BufferImpl::PipeSubsystemArmVerdict verdict = BufferImpl::ClassifyPipeSubsystemArm(
bitSet && !refused, MG_Config::Features.PipeLegacyMemos,
bitSet && !refused, /*legacyMemosEnabled=*/true,
/*legacyArmSurvivesLegacyMemos=*/false);
if (verdict == BufferImpl::PipeSubsystemArmVerdict::NoArm) {
BufferImpl::StopOnArmlessPipeSubsystem(
@@ -5022,7 +5017,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
"kMGPipeSubsystemSamplers (bit 11)");
}
const BufferImpl::PipeSubsystemArmVerdict verdict = BufferImpl::ClassifyPipeSubsystemArm(
bitSet && !refused, MG_Config::Features.PipeLegacyMemos,
bitSet && !refused, /*legacyMemosEnabled=*/true,
/*legacyArmSurvivesLegacyMemos=*/false);
if (verdict == BufferImpl::PipeSubsystemArmVerdict::NoArm) {
BufferImpl::StopOnArmlessPipeSubsystem(
@@ -5053,7 +5048,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// specialises on (D-H5) are read from state this backend already holds.
const Bool bitSet = (MG_Config::Features.PipePush & MG_Pipe::kMGPipeSubsystemPrograms) != 0;
const BufferImpl::PipeSubsystemArmVerdict verdict = BufferImpl::ClassifyPipeSubsystemArm(
bitSet, MG_Config::Features.PipeLegacyMemos, /*legacyArmSurvivesLegacyMemos=*/false);
bitSet, /*legacyMemosEnabled=*/true, /*legacyArmSurvivesLegacyMemos=*/false);
if (verdict == BufferImpl::PipeSubsystemArmVerdict::NoArm) {
BufferImpl::StopOnArmlessPipeSubsystem(
"MOBILEGL_PIPE_PUSH leaves kMGPipeSubsystemPrograms (bit 12) clear and "
@@ -79,8 +79,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// there is a correct answer (the state hash) where for bits 5/6 there is none - but it no
// longer does it SILENTLY: the combination is named once, at startup, right here.
inline void MagmaPipeValidateSubsystemConfiguration() {
if (!MG_Config::Features.PipeLegacyMemos &&
!MagmaPipeSubsystemOn(MG_Pipe::kMGPipeSubsystemRenderState)) {
if (!MagmaPipeSubsystemOn(MG_Pipe::kMGPipeSubsystemRenderState)) {
MGLOG_W("MGPipe: MOBILEGL_PIPE_LEGACY_MEMOS=0 with kMGPipeSubsystemRenderState (bit 0 "
"of MOBILEGL_PIPE_PUSH) clear - Magma's pipeline memo has no CSO handle to key "
"on, so every draw whose pipeline-state version moved runs the pre-handle STATE "
+2 -1
View File
@@ -83,7 +83,8 @@
X(ClientTablesUninstalled, ProtocolCorruption, "the applier ran before the client installed its tables") \
X(NoClientSession, ProtocolCorruption, "a path that needs the client session ran without one") \
X(UnnamedIdentity, ProtocolCorruption, "a per-context memo was consulted with a null context identity") \
X(PipeLegacyMemosDisabled, ProtocolCorruption, "a subsystem bit is clear and this build compiles no legacy arm to fall back to") \
X(PipeLegacyMemosDisabled, ProtocolCorruption, "a subsystem bit is clear and this build compiles no legacy arm to fall back to (unreachable since P13 W3b fixed bits 0-13 on)") \
X(PipeSubsystemsFixedOn, ProtocolCorruption, "MOBILEGL_PIPE_PUSH cleared one of subsystem bits 0-13, which P13 fixed on") \
X(PipeVerifyBadKnob, ProtocolCorruption, "a MOBILEGL_PIPE_VERIFY/POISON knob named a verb or field that does not exist") \
X(PipeVerifyDiffer, ProtocolCorruption, "the verify comparator found a pushed field differing from the live one") \
X(UnexpectedMapAccept, ProtocolCorruption, "a persistent-map accept arrived where the protocol permits none") \
@@ -184,19 +184,9 @@ TEST(SubsystemDepsTest, TheClientNeverEmitsAFamilyTheTableRefuses) {
// ---------------------------------------------------------------------------------------------
TEST(SubsystemDepsTest, TheServerConsumerGateNeverArmsAFamilyTheTableRefuses) {
using namespace MobileGL::MG_Backend::DirectGLES;
// THE RESOLVERS SURVIVE A SWEEP ONLY BECAUSE LEGACY MEMOS ARE ON. Each of them ends in
// ClassifyPipeSubsystemArm, and a verdict of NoArm calls StopOnArmlessPipeSubsystem
// (Managers.cpp:2928-2938), which aborts the process - that is the intended behaviour for a
// configuration with neither the handle arm nor the legacy arm, and it would turn this
// mask sweep into a crash rather than a failure. MOBILEGL_PIPE_LEGACY_MEMOS defaults ON
// (Config.h:399) and nothing in the unit lane clears it, so the legacy arm always survives
// and every resolver returns rather than stopping. Asserted rather than assumed, because the
// day that default flips this file would abort with no explanation.
ASSERT_TRUE(MG_Config::Features.PipeLegacyMemos)
<< "MOBILEGL_PIPE_LEGACY_MEMOS is clear, so a resolver whose handle arm this sweep "
"deliberately refuses has NO arm left and StopOnArmlessPipeSubsystem will abort the "
"process instead of returning false. Run this case with the legacy memos on, or teach "
"it to skip the masks that leave a family armless.";
// THE RESOLVERS SURVIVE A SWEEP because a refused family takes the (unreachable since P13 W3b)
// legacy verdict and returns false instead of stopping; ConfigLoader refuses any such mask at
// startup, so this sweep exercises the resolvers' own logic, not a reachable configuration.
struct Resolver {
const char* Name;
Uint64 Family;
@@ -234,8 +224,6 @@ TEST(SubsystemDepsTest, TheServerConsumerGateNeverArmsAFamilyTheTableRefuses) {
// ---------------------------------------------------------------------------------------------
TEST(SubsystemDepsTest, TheTwoDocumentedRefusalLanesStillRefuseAndTheFullMaskStillArms) {
using namespace MobileGL::MG_Backend::DirectGLES;
ASSERT_TRUE(MG_Config::Features.PipeLegacyMemos)
<< "see the sweep above: a refused family with the legacy arm also off aborts the process";
{
PushMaskScope scope(kMGPipeSubsystemsMigratedAtP5e);
EXPECT_TRUE(ResolveTextureResourceSubsystemArm())
-132
View File
@@ -3694,29 +3694,6 @@ namespace {
std::string m_previous;
};
// Sets the two knobs into the combination that leaves no twin-table arm at all -
// kMGPipeSubsystemEsprytSlots clear and PipeLegacyMemos false, which is what
// MOBILEGL_PIPE_PUSH=0 MOBILEGL_PIPE_LEGACY_MEMOS=0 in the environment produces - and
// restores MG_Config::Features on every exit path.
struct ScopedArmlessKnobPair {
ScopedArmlessKnobPair():
m_savedPush(MobileGL::MG_Config::Features.PipePush),
m_savedLegacy(MobileGL::MG_Config::Features.PipeLegacyMemos) {
MobileGL::MG_Config::Features.PipePush =
m_savedPush & ~MobileGL::MG_Pipe::kMGPipeSubsystemEsprytSlots;
MobileGL::MG_Config::Features.PipeLegacyMemos = false;
}
~ScopedArmlessKnobPair() {
MobileGL::MG_Config::Features.PipePush = m_savedPush;
MobileGL::MG_Config::Features.PipeLegacyMemos = m_savedLegacy;
}
ScopedArmlessKnobPair(const ScopedArmlessKnobPair&) = delete;
ScopedArmlessKnobPair& operator=(const ScopedArmlessKnobPair&) = delete;
private:
MobileGL::Uint64 m_savedPush;
MobileGL::Bool m_savedLegacy;
};
} // namespace
// The property the whole slice exists for. The pre-P2 registry keyed twins on the frontend heap
@@ -4642,115 +4619,6 @@ TEST(DirectGLESSlotTable, ASavedCopyOfARealRegistryDropsTheTwinOnTheSameNotice)
EXPECT_FALSE(registry.DestroyByLifetimeId(0)) << "sanity: a null id frees nothing";
}
// The gate on MAJOR 1 of the round-3 review. Commit d89fb684 raised
// Fatal{PipeLegacyMemosDisabled} from inside InitDisplayAndContext(), i.e. from inside EGL
// bring-up - and the integration harness pre-flights EGL bring-up in a FORKED CHILD, converting
// any child that dies on a signal into "no usable GPU/display/ICD" and SKIPPING every scenario.
// So `MOBILEGL_PIPE_PUSH=0 MOBILEGL_PIPE_LEGACY_MEMOS=0 ctest -L integration-gpu -R DirectGLES`
// reported 100% tests passed while running nothing at all, on the exact pair of env vars the
// D14/D18 A/B is driven with. ROADMAP.md:7 forbids a gate that cannot go red for the reason it
// exists, and a lane that goes green by skipping is the worst version of that.
//
// The split this case pins: bring-up DIAGNOSES (and returns), first twin lookup STOPS. It
// checks the message and not only the signal, because an operator who is handed a bare
// "Subprocess aborted" has been told nothing about which two knobs they set.
TEST(DirectGLESSlotTable, AnArmlessKnobCombinationStopsInsteadOfSkippingTheLane) {
GTEST_SKIP() << "this build compiles no legacy twin registry, so no knob combination can "
"leave the process without an arm";
}
// The two guards the armless cases stand on, pinned on their own: whatever an operator had in
// MOBILEGL_LOG_FILE_PATH - a path, or nothing - and whatever MG_Config::Features held are back,
// byte for byte, once the guards go out of scope, with or without a failure inside. Before
// this the armless case unset the variable for every later case in the binary and restored
// the config only on its success path (the round-4 review's minor 5).
TEST(DirectGLESSlotTable, TheArmlessCasesLeaveTheLogPathAndTheConfigAsTheyFoundThem) {
using namespace MobileGL;
const Uint64 push = MG_Config::Features.PipePush;
const Bool legacy = MG_Config::Features.PipeLegacyMemos;
std::string previousPath;
const bool hadPreviousPath = std::getenv("MOBILEGL_LOG_FILE_PATH") != nullptr;
if (hadPreviousPath) previousPath = std::getenv("MOBILEGL_LOG_FILE_PATH");
// With an operator path in place...
const std::filesystem::path operatorPath = UniqueScratchLogPath("mobilegl-espryt-operator");
SetEnvVar("MOBILEGL_LOG_FILE_PATH", operatorPath.string().c_str());
{
const ScopedArmlessKnobPair knobs;
const ScopedLogFileRedirect redirect(UniqueScratchLogPath("mobilegl-espryt-guard"));
EXPECT_EQ(MG_Config::Features.PipePush & MG_Pipe::kMGPipeSubsystemEsprytSlots, 0ull);
EXPECT_FALSE(MG_Config::Features.PipeLegacyMemos);
EXPECT_STRNE(std::getenv("MOBILEGL_LOG_FILE_PATH"), operatorPath.string().c_str());
}
ASSERT_NE(std::getenv("MOBILEGL_LOG_FILE_PATH"), nullptr) << "the operator's log path was unset";
EXPECT_STREQ(std::getenv("MOBILEGL_LOG_FILE_PATH"), operatorPath.string().c_str());
EXPECT_EQ(MG_Config::Features.PipePush, push);
EXPECT_EQ(MG_Config::Features.PipeLegacyMemos, legacy);
// ...and with none.
UnsetEnvVar("MOBILEGL_LOG_FILE_PATH");
{
const ScopedLogFileRedirect redirect(UniqueScratchLogPath("mobilegl-espryt-guard"));
EXPECT_NE(std::getenv("MOBILEGL_LOG_FILE_PATH"), nullptr);
}
EXPECT_EQ(std::getenv("MOBILEGL_LOG_FILE_PATH"), nullptr)
<< "a log path was left behind where the operator had none";
if (hadPreviousPath) {
SetEnvVar("MOBILEGL_LOG_FILE_PATH", previousPath.c_str());
}
std::error_code ignored;
std::filesystem::remove(operatorPath, ignored);
}
// The round-4 review's minor 4: the case above pins the two FUNCTIONS, and nothing failed if
// InitDisplayAndContext() (DirectGLES.cpp) was edited back to call the stopping one - which is
// exactly the regression that produced the round-3 major. This pins the CALL SITE, by running
// the real bring-up entry point under the armless pair.
//
// No display is needed: InitDisplayAndContext's twin-arm call is its first statement after
// the context teardown, ahead of eglGetDisplay, so an EGL table whose eglGetDisplay answers
// EGL_NO_DISPLAY takes bring-up through that call and straight back out with `false`. The
// child must then EXIT with the code below. If the site stops again it dies of SIGABRT
// instead, and the death test fails - naming both knobs - rather than skipping: in the
// integration harness that same abort is what turned into a green lane that ran nothing.
//
// Not skipped in a build without the legacy arm either: there the verdict is Handles and
// bring-up has nothing to diagnose, but it must still return, and this says so.
TEST(DirectGLESSlotTable, EglBringUpUnderTheArmlessKnobPairReturnsInsteadOfStopping) {
#if defined(_WIN32)
GTEST_SKIP() << "needs a forked death test";
#else
using namespace MobileGL;
constexpr int kBringUpReturnedFalse = 0x51;
constexpr int kBringUpReturnedTrue = 0x52;
// The redirect is set up in the PARENT: the child inherits the environment and writes the
// file, the parent reads it once the child has gone, and the guard restores the operator's
// log path either way.
const ScopedLogFileRedirect log(UniqueScratchLogPath("mobilegl-espryt-armless-bringup"));
EXPECT_EXIT(
{
const ScopedArmlessKnobPair knobs;
MG_External::EGLFunctionsTable egl{};
egl.eglGetDisplay = +[](EGLNativeDisplayType) -> EGLDisplay { return EGL_NO_DISPLAY; };
MG_Backend::DirectGLES::SetEGLFuncsTable(egl);
const Bool ok = MG_Backend::DirectGLES::InitPbufferSurface(1, 1);
MG_Util::Debug::Close();
std::exit(ok ? kBringUpReturnedTrue : kBringUpReturnedFalse);
},
::testing::ExitedWithCode(kBringUpReturnedFalse), "")
<< "EGL bring-up under MOBILEGL_PIPE_PUSH with kMGPipeSubsystemEsprytSlots clear and "
"MOBILEGL_PIPE_LEGACY_MEMOS=0 did not RETURN: InitDisplayAndContext() is stopping "
"on the armless knob pair again instead of diagnosing it, and the integration "
"harness's forked pre-flight turns that stop into a lane that skips every scenario";
#endif
}
// P3a REWORK C-1's gate, and the case that would have caught it.
//
// A persistently mapped buffer whose store was NOT adopted - under the 16 MiB threshold, or
@@ -80,8 +80,8 @@ int main() {
ExpectSplit(";;A=1;;B=2;;", {"A=1", "B=2"});
ExpectSplit(";", {});
// The values the plan's knobs actually carry: a path, a size, a comma list.
ExpectSplit("MOBILEGL_PIPE_TEXEL_RETAIN_MB=64;MOBILEGL_LOG_FILE_PATH=/sdcard/MG/a.log",
{"MOBILEGL_PIPE_TEXEL_RETAIN_MB=64", "MOBILEGL_LOG_FILE_PATH=/sdcard/MG/a.log"});
ExpectSplit("MOBILEGL_PIPE_INDEX_MIRROR_MB=64;MOBILEGL_LOG_FILE_PATH=/sdcard/MG/a.log",
{"MOBILEGL_PIPE_INDEX_MIRROR_MB=64", "MOBILEGL_LOG_FILE_PATH=/sdcard/MG/a.log"});
// Classification.
ExpectParse("MOBILEGL_PIPE_PUSH=1", EnvOverrideAction::Set, "MOBILEGL_PIPE_PUSH", "1");