mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
- PLAN-B.md §8.2 and appendix B add a batch of new runtime switches
(MOBILEGL_PIPE_PUSH / _VERIFY / _STATS / _LEGACY_MEMOS / _TEXEL_RETAIN_MB /
_INDEX_MIRROR_MB, plus MOBILEGL_IPC_* later), and §11 P0 wants them parsed beside
the existing ones. Today every knob that has to reach an Android replay costs an
edit in five files - run_android_retrace_local.py, trace-replay-ci.sh,
TraceReplayActivity's request record, the JNI marshalling, and the setenv block in
trace_replay_core.cpp. That per-knob tax is what this replaces: one extra,
`--es mobilegl_env "K=V;K=V"`, carries all of them.
- Applied last, immediately before dlopen(libMobileGL.so), so it can also override
the dedicated fields above it - MobileGL's config is read during the load, and an
escape hatch that cannot beat the defaults is not one. An entry with no '=' unsets
the variable, which is the only way to clear a default the marshalling sets.
- The existing per-knob flags stay: they carry semantics beyond a setenv (use_angle
also selects a variant, the dump lists are joined, DirectVulkan forces the
R11G11B10F fallback), and rewriting them as env strings would move that logic into
the callers.
- Surface: --env / MOBILEGL_TRACE_ENV in trace-replay-ci.sh, repeatable --env
KEY=VALUE in run_android_retrace_local.py, `mobilegl_env` intent extra,
Request::envOverrides.
- The two-level parse now lives in trace_env_overrides.hpp, beside the semicolon
splitter it shares with the texture and FBO dump lists, and
tools/trace_replay/trace_env_overrides_test.cpp pins it: the empty entries a
trailing ';' leaves behind must not become unsetenv(""), `K=` must stay a Set of
the empty string rather than an Unset (a knob read with getenv() != nullptr sees
those as opposite answers), and only the FIRST '=' may separate, or a value
carrying '=' is truncated without a word of warning. The whole MOBILEGL_PIPE_*
batch rides on this parse, and the only lane that exercised it end to end was an
on-device retrace, which would have reported a splitting bug as "the knob had no
effect".
- The check is built and RUN at build time and mobilegl_trace_replay depends on it,
so `cmake --build ... --target mobilegl_trace_replay` - the exact command of
test.yml's "Build trace replay" job, which never invokes ctest - runs it. It is
assert-free on purpose: that lane configures Release, and <cassert> under NDEBUG
would compile every check into a green run that checked nothing. Negative control:
swapping find('=') for rfind('=') fails 2 checks, and keeping the splitter's empty
entries fails 2 more.
109 lines
4.7 KiB
C++
109 lines
4.7 KiB
C++
// Host-side check for the retrace lane's `K=V;K=V` environment passthrough.
|
|
//
|
|
// The passthrough is the one hand-rolled parse between a CI flag and setenv(), and the
|
|
// only lane that runs it end to end is an on-device retrace - too slow and too indirect
|
|
// to notice a splitting bug, and it would report the bug as "the knob had no effect".
|
|
// This program is built and RUN at build time by every desktop configure that builds
|
|
// mobilegl_trace_replay (tools/trace_replay/CMakeLists.txt), so the CI job that only
|
|
// builds the runner still exercises it.
|
|
//
|
|
// Deliberately assert-free: the retrace lane configures Release, NDEBUG is defined, and
|
|
// <cassert> would compile every check away into a green run that checked nothing.
|
|
|
|
#include "trace_env_overrides.hpp"
|
|
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
|
|
int gFailures = 0;
|
|
|
|
void ExpectSplit(const std::string& input, const std::vector<std::string>& expected) {
|
|
const std::vector<std::string> actual = mobilegl_trace::SplitSemicolonList(input);
|
|
if (actual == expected) {
|
|
return;
|
|
}
|
|
++gFailures;
|
|
std::cerr << "SplitSemicolonList(\"" << input << "\") gave " << actual.size()
|
|
<< " entries, expected " << expected.size() << ":";
|
|
for (const std::string& entry : actual) {
|
|
std::cerr << " [" << entry << "]";
|
|
}
|
|
std::cerr << "\n";
|
|
}
|
|
|
|
const char* ActionName(mobilegl_trace::EnvOverrideAction action) {
|
|
switch (action) {
|
|
case mobilegl_trace::EnvOverrideAction::Ignore:
|
|
return "Ignore";
|
|
case mobilegl_trace::EnvOverrideAction::Set:
|
|
return "Set";
|
|
case mobilegl_trace::EnvOverrideAction::Unset:
|
|
return "Unset";
|
|
}
|
|
return "?";
|
|
}
|
|
|
|
void ExpectParse(const std::string& entry,
|
|
mobilegl_trace::EnvOverrideAction expectedAction,
|
|
const std::string& expectedKey,
|
|
const std::string& expectedValue) {
|
|
std::string key = "<untouched>";
|
|
std::string value = "<untouched>";
|
|
const mobilegl_trace::EnvOverrideAction action =
|
|
mobilegl_trace::ParseEnvOverride(entry, &key, &value);
|
|
if (action == expectedAction && key == expectedKey && value == expectedValue) {
|
|
return;
|
|
}
|
|
++gFailures;
|
|
std::cerr << "ParseEnvOverride(\"" << entry << "\") gave " << ActionName(action) << " key=["
|
|
<< key << "] value=[" << value << "], expected " << ActionName(expectedAction)
|
|
<< " key=[" << expectedKey << "] value=[" << expectedValue << "]\n";
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main() {
|
|
using mobilegl_trace::EnvOverrideAction;
|
|
|
|
// Splitting.
|
|
ExpectSplit("", {});
|
|
ExpectSplit("MOBILEGL_PIPE_PUSH=1", {"MOBILEGL_PIPE_PUSH=1"});
|
|
ExpectSplit("MOBILEGL_PIPE_PUSH=1;MOBILEGL_PIPE_VERIFY=1",
|
|
{"MOBILEGL_PIPE_PUSH=1", "MOBILEGL_PIPE_VERIFY=1"});
|
|
// A trailing ';' is what a caller that joins a list gets for free, and an empty entry
|
|
// must not be turned into an unsetenv("") - the whole passthrough would then depend on
|
|
// how carefully the shell script trimmed its own string.
|
|
ExpectSplit("A=1;", {"A=1"});
|
|
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"});
|
|
|
|
// Classification.
|
|
ExpectParse("MOBILEGL_PIPE_PUSH=1", EnvOverrideAction::Set, "MOBILEGL_PIPE_PUSH", "1");
|
|
// `K=` is an empty value, NOT an unset: a knob tested with getenv() != nullptr sees
|
|
// those two as opposite answers.
|
|
ExpectParse("MOBILEGL_PIPE_PUSH=", EnvOverrideAction::Set, "MOBILEGL_PIPE_PUSH", "");
|
|
// No '=' means unset - the only way to clear a default the per-knob marshalling set.
|
|
ExpectParse("MOBILEGL_PIPE_PUSH", EnvOverrideAction::Unset, "MOBILEGL_PIPE_PUSH", "");
|
|
// Only the FIRST '=' separates, so a value may contain '='. Anything else would
|
|
// truncate a base64 or a query-string-shaped value without a word of warning.
|
|
ExpectParse("MOBILEGL_A=b=c", EnvOverrideAction::Set, "MOBILEGL_A", "b=c");
|
|
ExpectParse("MOBILEGL_A==", EnvOverrideAction::Set, "MOBILEGL_A", "=");
|
|
// An empty key must never reach setenv/unsetenv, which would be EINVAL at best.
|
|
ExpectParse("", EnvOverrideAction::Ignore, "", "");
|
|
ExpectParse("=1", EnvOverrideAction::Ignore, "", "");
|
|
ExpectParse("=", EnvOverrideAction::Ignore, "", "");
|
|
|
|
if (gFailures != 0) {
|
|
std::cerr << "trace env passthrough: " << gFailures << " check(s) failed\n";
|
|
return 1;
|
|
}
|
|
std::cout << "trace env passthrough: all checks passed\n";
|
|
return 0;
|
|
}
|