Commit Graph
159 Commits
Author SHA1 Message Date
swung0x48 2d690754dd [Tooling] (DeviceBench): verify the two campaign devices' pin profiles and add a pin helper that works on them
- bench.sh's pin_freqs writes /proc/ppm and /proc/gpufreq, which exist on neither 35d0befa
  (SM8750) nor 3B159D009VZ00000 (MT6993 dropped both for /proc/gpufreqv2); the guard the
  PROFILE_VERIFIED key exists for was about to be defeated on the device its own comment
  guessed was safe
- pin_device.sh pins big/little/GPU through the nodes each device actually has (Xiaomi:
  policy6 1958400 / policy0 1555200 / kgsl pwrlevel 0; Oppo: policy4 2000000 + policy7
  2000000 / policy0 1600000 / gpufreqv2 fix_target_opp_index 0), checks only against its own
  pins because ColorOS moves stock maxima by itself, and refuses to call a zero-key read a verdict
- both profiles are PROFILE_VERIFIED=1 on 30 s pinned windows with zero drift; the evidence and
  the two things not verified (Oppo GPU held at DVFS-request level only, no full bench window)
  are in docs/Disaggregated/devices/pin-verification-2026-09-07.md
2026-09-07 23:43:11 -04:00
swung0x48 13d7e32b7b [Fix] (Tooling): restore the executable bit on the two device_bench entry points
- bench.sh and session.sh are invoked as ./tools/device_bench/bench.sh in their own README
  and are the harness for the paired two-device A/B the GO/NO-GO asks for; a previous commit
  in this package dropped the bit and every check invoked them through bash, so nothing saw it
2026-09-07 23:18:10 -04:00
swung0x48 b1c37699b1 [Fix] (Bench, Trace, CI): let only the profile answer for itself, name an unreadable profile, and describe the CI step by the mechanism the tree has
- The verified-profile guard read the process environment as well as the profile: the test ran
  after the source, so PROFILE_VERIFIED=1 exported in an operator's shell re-opened the fail-open
  hole for every profile that says nothing. Both scripts now set PROFILE_VERIFIED=0 immediately
  before sourcing, so the file is the only thing that can answer.
- A --device path that cannot be sourced was diagnosed as an unverified profile, because both
  scripts cd to their own directory first and neither checked readability. The path is now also
  tried relative to the directory the script was invoked from (which is what a repo-root-relative
  --device means), and an unreadable one is reported as unreadable, naming both places tried.
- Verified: exported PROFILE_VERIFIED=1 + an unverified profile -> rc 2; exported 1 + a profile
  with no key -> rc 2; a repo-root-relative path -> resolved, then refused for its own reason;
  a missing file -> "cannot read the device profile"; odinlite.env -> past the guard;
  --allow-unverified-profile -> the three warnings, then proceeds.
- test.yml's new step described a mechanism the tree does not have. G6's and G10's entries are
  registered in the pull build too - they must be, for G2's name-for-name comparison - and skip
  inside their bodies. The step's value is unchanged and its comment now says the true thing: the
  `test` job runs those names as a column of skips, and this is the first CI job that unpacks a
  build which compiled the assertions.
- trace_benchmark takes the wall baseline before the CPU baseline, the order OnFrameBoundary
  already reads them in, so frame 0 stops reporting a CPU delta biased upward against its own
  wall delta; and it includes <time.h> rather than <ctime> for the POSIX names it uses.
2026-09-07 23:18:10 -04:00
swung0x48 af20dba6db [Fix] (Trace, Bench, CI): compute p50 by the device's own median rule, fail the profile guard closed, and give the new control step its sibling's environment
- format_benchmark printed a p50 taken with the nearest-rank rule beside a medianFrameCpuMs the
  device computes as the average of the two middle frames, and documented the two as one rule; on
  an even window they differ (the pre-flight printed p50=8.261ms next to medianCpuMs=271.766).
  p50 now goes through series_median, which is SummarizeSeries' rule transcribed; p95 and p99 stay
  nearest rank, which is the device's rule for p95 and the honest extension of it for the p99 the
  device does not compute at all
- require_verified_profile treated a profile that simply omits PROFILE_VERIFIED as verified, which
  is the fail-open default a profile written by copying another one inherits - exactly the case the
  guard exists for. It defaults to unverified now, odinlite.env carries PROFILE_VERIFIED=1
  explicitly (it is the one profile that earned it), and the refusal says "says 0, or says nothing"
- the two new profiles claimed profile.sh refuses an unverified profile; it has no such check and
  needs none - it records a simpleperf profile and pins nothing. The claim is corrected in both
  profiles and in the README rather than a guard added where there is nothing to guard
- the handle-ABA / CSO control step in test.yml set only MOBILEGL_ITEST_REQUIRE_GPU while its
  sibling verify step sets the three MOBILEGL_MAGMA_* fixes and arms core dumps. It runs the same
  DirectVulkan binary on the same runner, so a crash there left no core; it now carries both
2026-09-07 23:18:09 -04:00
swung0x48 b9c137e146 [Feat] (Bench, Pipe): run the blend-toggle case in CI, give G7 a negative control, and record the two campaign devices
- DriverBenchStateToggle runs mc_state_toggle as its own ctest entry. The case has been in
  kBenchCases since P0 and nothing executed it, so nothing would have noticed it rotting - and it
  is the exact enable/draw/disable/draw shape the microbenchmark P2 owes the GO/NO-GO measures.
  About 1.2 s inside an existing three-minute job.
- scripts/g7_negative_control.sh breaks the pipeline/dynamic split on purpose: it inserts two
  boundaries so ColorMasks becomes a dynamic chunk of its own, which keeps the partition sorted,
  non-overlapping and complete - so it still COMPILES - while making glColorMask bump
  m_pipelineStateVersion without moving the pipeline-subset hash. A non-zero ctest is the pass.
- Everything that could make that control lie is refused rather than reported: a missing
  SetterConsistency test exits 2 instead of reading "no tests matched" as a failure; a tree that
  is already red or already broken exits 2; a patched table that does not compile exits 2, since
  a build break would prove the static_asserts work rather than that the test still checks; and
  the restore is from byte-for-byte copies (never from git, so a dirty tree is given back
  intact), followed by a rebuild and a re-run that must be green. --verify-patch-only exercises
  the mechanism where the test does not exist yet and says explicitly that it is not a pass.
- Profiles for the two campaign devices, and the guard that stops them being trusted early. Both
  carry PROFILE_VERIFIED=0 and every device-specific field is TODO_VERIFY_ON_DEVICE rather than a
  guess: the harness pins through MediaTek nodes and 35d0befa is a Qualcomm part, where
  `su -c 'echo ... > /proc/ppm/...'` fails with a zero exit and the run would report numbers it
  believes were pinned. bench.sh and session.sh now refuse an unverified profile unless
  --allow-unverified-profile is passed, which warns that the run is not comparable with a pinned
  one. The README records what earns PROFILE_VERIFIED=1.
2026-09-07 23:18:09 -04:00
swung0x48 e9499d38bd [Feat] (Trace): record per-frame thread CPU time beside wall time so a paired A/B can be read as CPU cost
- The metric the disaggregation GO/NO-GO hangs on is per-thread CPU p50/p99, and the tree had no
  first-party collector for it: no CLOCK_THREAD_CPUTIME_ID, no getrusage, no RUSAGE_THREAD, no
  /proc/self/task anywhere under MobileGL/, tools/, android-plugin/ or scripts/.
- The retrace loop is the cheapest honest place to take it. Retrace runs --singlethread and
  trace_benchmark states that Begin/OnFrameBoundary/End are only ever reached from that one
  thread, so that thread's CPU time IS the client-side CPU cost: one extra clock_gettime per
  frame, no root, no profiler, no debuggable build, no sampling - and no instrumentation
  committed to a hot path inside the library.
- The CPU reading is taken before the wall reading at each boundary, so the syscall lands in the
  wall delta rather than hiding inside the CPU delta. An inflated wall number is visible; a
  deflated CPU number would not be.
- Report::frameCpuMs is the same length as frameMs or it is EMPTY. A clock that started failing
  mid-run would otherwise be silently re-indexed and put frame N next to frame N+k, and an empty
  series and a series of zeroes are different claims about the platform.
- SummarizeBenchmark is split into SummarizeSeries and reused verbatim for the CPU series rather
  than duplicated: same tail window, same even-count median rule, same nearest-rank p95, or the
  delta between the two series stops meaning anything.
- benchmark.json gains meanFrameCpuMs / medianFrameCpuMs / p95FrameCpuMs and the WHOLE
  frameCpuTimesMs[] array beside frameTimesMs[]; result.json and the completion line gain the
  three headline numbers. p99 therefore needs no device change - it is a host-side reduction over
  an artefact that already exists, and run_android_retrace_local.py prints p50/p95/p99 off the
  same trailing window the device summarised.
- Pre-flighted on the desktop CLI, which shares the same core: 2-frame run, cpu series aligned
  with the wall series and strictly below it (wall 1290.787/20.225 ms, cpu 535.271/8.261 ms).
2026-09-07 23:18:09 -04:00
swung0x48 7d80c9678e [Test] (Retrace): scan a verify retrace's log for the third MGPipe Fatal too
- the block looked for Fatal{PipeVerifyDiffer and Fatal{UnmigratedPipeInput only. A
  misspelt MOBILEGL_PIPE_VERIFY_CORRUPT / MOBILEGL_PIPE_POISON_OMIT reports
  Fatal{PipeVerifyBadKnob, and it was caught only because D2 makes that one abort the
  process - which is precisely what MOBILEGL_PIPE_VERIFY_FATAL=0, the supported triage
  configuration, takes away. A typo'd knob would have left a negative-control run
  looking healthy
- the FATAL_ERROR text now says what each of the three means and where the two
  vocabularies live, because that message is the whole diagnosis a `cmake -P` step gets
2026-09-06 04:41:28 -04:00
swung0x48 5f8e8db1b9 [Test] (Retrace): make a retrace under MOBILEGL_PIPE_VERIFY prove it armed, and give the lane a label
- A retrace that exports MOBILEGL_PIPE_VERIFY=1 at a library configured without
  -DMOBILEGL_PIPE_VERIFY=ON is a no-op: the frames still match their goldens and the
  case reports green having compared nothing. run_trace_case.cmake now demands the
  evidence whenever the variable is set to anything but 0/false - mobilegl.log must
  exist, must carry "MGPipe: verify armed", and must carry neither
  Fatal{PipeVerifyDiffer nor Fatal{UnmigratedPipeInput. With the variable unset the
  script is byte-for-byte the old one.
- The Fatal scan is not redundant with the replay's exit status:
  MOBILEGL_PIPE_VERIFY_FATAL=0 is the supported triage configuration, and there a
  divergence is logged and counted rather than aborted, so the run would finish 0
  with its own report sitting unread in the log.
- LABELS retrace on both registrations: the lane was selectable only by regex, so
  `ctest -L retrace --no-tests=error` - the spelling that reds a lane which
  registered nothing - could not be written at all.
- "verify": true on eight cases (the five 180s cases plus minecraft-1.21.4-in-world,
  minecraft-1.21.4-fabric-sodium-in-world and improved-transparency-minecraft-26.3),
  and --format github-verify-matrix over that subset: 16 entries, against the full
  lane's 77. The verify build compares at every verb boundary and again at every
  accessor read, which the design budgets at 5-10x, so the per-push job runs the
  subset and the full sweep is a phase-exit / workflow_dispatch run.
- The flag is validated in the manifest loader, not at the matrix, so a non-boolean
  or a verify case excluded from CI is a loud error in every consumer instead of a
  subset that is quietly one case short.
2026-09-06 04:41:28 -04:00
swung0x48 6672778b80 [Merge] (dev): merge dev@50fb1343 into feat/disaggregated
- brings the write-map landing into GPU-resident stores, the open-ended fp64 storage block flattening, the idle-based CTS chunk timeout and the MSVC /WHOLEARCHIVE test link
- verified on the merged tree: unit 1458, integration 868, Wire 54, retrace 79/79
2026-09-05 22:13:21 -04:00
swung0x48 7ef7c7e543 [Fix] (Spikes): answer the tier question for DirectGLES too, make an OK mean bytes round-tripped through a real GPU access, and exercise T3 in the direction that makes it a tier
- plan-B §8.3 asks which tier `AcquirePersistentMap` lands in, but the probe only
  asked Vulkan. DirectGLES ("Espryt") reaches a persistent map through
  `glBufferStorageEXT` + `glMapBufferRange(PERSISTENT|COHERENT)`, not a
  `VkDeviceMemory` map, so a Vulkan-only answer decides nothing for that backend.
  Add a GLES leg to T1: the exported fd imported with `glCreateMemoryObjectsEXT`
  + `glImportMemoryFdEXT` + `glBufferStorageMemEXT`, then mapped
  PERSISTENT|COHERENT -- in-process first (isolates "GL can import this fd" from
  "the fd survives a process boundary"), then cross-process (new `t1gl` child).
  Drivers disagree about how the import must be phrased, so each attempt walks a
  ladder over {dedicated flag} x {import size = memory requirement or the fd's
  own size} x {buffer size} and reports the rung the driver accepted plus every
  rejected rung with its GL error -- a driver *preference* must never be reported
  as a missing capability. A driver that backs the storage but refuses
  PERSISTENT|COHERENT is reported separately from one that refuses the storage:
  that distinction is exactly T1 vs T2 for DirectGLES. The T0 GLES leg
  (`EGL_ANDROID_get_native_client_buffer` + `glBufferStorageExternalEXT` +
  persistent map, verified by `AHardwareBuffer_lock` on the client side) now
  reports every step's GL enum and requires the persistent flags for OK.
- the verdict was unfalsifiable: T1 reported PARTIAL when neither leg had moved a
  byte. Replace it with an explicit decisive-leg model -- OK only when every
  decisive leg round-tripped in both directions, PARTIAL when at least one did,
  FAIL otherwise with the failing step and its driver error named in `why:`.
  Every row now opens with a per-leg trace (`vkimport[D]=rt gpu[D]=rt`). The raw
  `mmap` leg is informational for opaque-fd (Vulkan forbids interpreting that
  payload outside the driver, so a refusal is conformant) and decisive for
  dma-buf, where a CPU mapping is the point of the handle type.
- T3 never ran the direction that would make it a tier: both ends were the
  importing process. Add `T3-client-memfd-server-import` (new `t3c` child) -- the
  client creates and writes the memfd, the server mmaps the received fd, imports
  the client's host pointer into a `VkDeviceMemory`, reads what the client wrote,
  writes back, and takes a GPU access on the client's memory, which the client
  then verifies through its own mapping.
- no route touched the GPU, so an OK proved only that a map call returned a
  pointer. Every tier row now takes a real GPU access before it can be OK:
  `vkCmdCopyBuffer` out of the shared allocation into private staging (mismatch =
  the GPU could not read what the peer wrote) plus `vkCmdFillBuffer` into it,
  queue-idle and an explicit host-read barrier, with the peer checking the filled
  region through its own mapping. VkCtx grows a queue and command pool for it.
- the device run executes in the `shell` SELinux domain, not the `untrusted_app`
  domain MobileGL runs in, and the two do not share dmabuf/gralloc rules. Print
  uid/pid/`/proc/self/attr/current` in a run-context header, repeat the caveat in
  the summary, and document in README.md how to answer it for the real domain
  later (exec the same binary from the trace app's spike hook, spike-A package)
  without implementing that here.
- `vkStr()` returned a pointer into one static buffer while several results
  routinely appear in one format call, so all of them showed the last one; it
  returns std::string now, `memFlagStr` likewise, and `fmt`/`pr` carry
  `format(printf)` so a missed `.c_str()` is a compile error rather than UB.
- `advertisedExportable` decided the status at the allocate site but not at the
  `vkGetMemoryFdKHR` site. One rule at every export failure now
  (`exportFailStatus`): advertised EXPORTABLE and then declining is FAIL, never
  advertised is UNSUPPORTED. Export + map + fd is factored into `exportHostVisible`.
- `T0-ahb-blob-transfer` was recorded OK on the socket handoff alone. The handoff
  keeps its own informational row; the tier row is now composed at the end from
  the full import+map+compare+writeback chain over the Vulkan, GL and GPU legs.
- `mmapErrno` kept the first attempt's errno after the second-chance mmap
  succeeded, so a working mapping carried a failure code; it is cleared on
  success and the first errno moves into the note.
- a failed `glImportMemoryFdEXT` no longer closes the fd: EXT_memory_object_fd
  does not say whether ownership still transfers on failure and Mesa closes it
  either way, so closing risks a double close landing on the socket. Leaking a
  handful of dups in a short-lived probe is the safe side of that trade.
- validated end to end on the host harness (lavapipe + llvmpipe,
  `VK_DRIVER_FILES=lvp_icd.json EGL_PLATFORM=surfaceless`): T1-opaque-fd OK,
  T3-external-memory-host OK, T3-memfd-cross-process OK,
  T3-client-memfd-server-import OK, T1-dma-buf UNSUPPORTED (not advertised
  exportable). The two T1-gles rows FAIL there with GL_OUT_OF_MEMORY on every
  ladder rung although GL_DEVICE_UUID_EXT matches the Vulkan deviceUUID --
  llvmpipe's GL does not implement importing a lavapipe opaque-fd allocation,
  a Mesa interop gap recorded in README.md so a device FAIL stays attributable.
  Rebuilt for arm64-v8a with NDK r27d (PIE, android-30); the device run is
  pending, both device locks are held by another campaign.
2026-09-05 20:50:05 -04:00
swung0x48 6c7ad0a1bf [Feat] (Spikes): add the standalone external-memory probe that decides the persistent-map tier
- Plan B §11 P0 requires spike B ("external memory 导出,两台设备") to run before the
  persistent-map decision of §8.3 can be taken: T0 (server imports a client
  allocation), T1 (server exports its own HOST_VISIBLE|HOST_COHERENT allocation)
  or T2 (AcquirePersistentMap returns nullptr, making the §5.10 client-side block
  push mandatory). §8.3 says the answer must be measured on the two campaign
  devices, and that a platform unknown must not block the interface work.
- tools/spikes/extmem_probe/ is a self-contained NDK command-line program: it
  links vulkan/EGL/GLESv3/android/log and nothing from MobileGL, is configured by
  its own CMakeLists with the android toolchain file, and is deliberately absent
  from the project's build graph (the root CMakeLists only pulls in
  tools/trace_replay), so the default ALL target is untouched.
- Phase A enumerates VK_KHR_external_memory_fd, VK_EXT_external_memory_dma_buf,
  VK_EXT_external_memory_host, VK_ANDROID_external_memory_android_hardware_buffer
  and, through a headless EGL pbuffer context, GL_EXT_memory_object{,_fd},
  GL_EXT_external_buffer, GL_EXT_buffer_storage, GL_OES_EGL_image_external{,_essl3}
  and EGL_ANDROID_get_native_client_buffer, plus the memory-type table and the
  vkGetPhysicalDeviceExternalBufferProperties verdict per handle type for the exact
  buffer usage set MobileGL needs.
- Route T1 allocates a HOST_VISIBLE|HOST_COHERENT buffer memory with
  VkExportMemoryAllocateInfo, writes a pattern through vkMapMemory, exports an fd
  with vkGetMemoryFdKHR (opaque-fd and, where advertised, dma-buf), hands it to a
  second process over SCM_RIGHTS, and has that process both mmap() the fd and
  import it into its own VkDeviceMemory + vkMapMemory. Both sides write and both
  sides compare, so a copy-on-import or one-directional mapping is reported as
  PARTIAL rather than as success.
- Route T0 has the second process allocate an AHardwareBuffer BLOB
  (CPU_READ_OFTEN|CPU_WRITE_OFTEN|GPU_DATA_BUFFER), send it with
  AHardwareBuffer_sendHandleToUnixSocket, and the first process import it three
  ways -- AHardwareBuffer_lock, VkDeviceMemory via
  VK_ANDROID_external_memory_android_hardware_buffer, and a GL buffer via
  eglGetNativeClientBufferANDROID + glBufferStorageExternalEXT mapped
  persistent/coherent -- with a write-back leg the allocating process verifies.
- Route T3 covers VK_EXT_external_memory_host: a memfd-backed mmap region aligned
  to minImportedHostPointerAlignment, imported through
  VkImportMemoryHostPointerInfoEXT, plus the same memfd handed to another process.
- The second process is /proc/self/exe re-exec'd with --child=<route> and one end
  of a socketpair on fd 3. A bare fork() is not usable: neither side's Vulkan
  driver survives fork, and both sides need live Vulkan. It is also the topology
  the transport actually has (§8.1, inheriting PLAN.md §11.1-§11.6: the client
  spawns the server), so the probe measures the arrangement that would ship.
- The probe also builds for the host with T0 compiled out. That is not scope
  creep: a negative device result is only worth something if the harness is known
  to report a working route as working. Running it on lavapipe did that, and paid
  for itself immediately by exposing two harness bugs that would have produced
  false negatives on the devices -- (a) the child wrote through its plain mmap
  before reading through the Vulkan import, so on a driver whose exported fd maps
  at an offset the probe overwrote the very payload the second read compares
  (lavapipe reports payloadAt=4096); reads through both mappings now precede
  writes through either, and the offset is searched for and reported; (b) an
  export failure on a handle type the driver never advertised as EXPORTABLE was
  classified FAIL instead of UNSUPPORTED (lavapipe's dma-buf answer).
- Output is a RESULT/summary table carrying the raw driver verdicts (VkResult
  names, errno, GL enums) because those codes -- not a pass/fail bit -- are what
  §8.3 needs in order to pick the tier.
- Built with NDK 27.3.13750724 for arm64-v8a / android-30, RelWithDebInfo, PIE,
  warning-clean; host build clang/RelWithDebInfo, warning-clean.
2026-09-05 20:50:04 -04:00
swung0x48 38d4c2372c [Feat] (TraceApp, CI): pass arbitrary env vars through the retrace lane
- 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.
2026-09-05 20:49:57 -04:00
swung0x48 8a239177ac [Feat] (Build, TraceApp): ship and exec a second native binary on android
- PLAN-B.md §11 P0 lists spike A (the Android delivery chain) as a P0 deliverable,
  inherited verbatim from PLAN.md §15 P0; §8.1 inherits PLAN.md §11.1-§11.6, whose
  Android path needs a second process. Android gives an application no writable
  exec-able directory, so the only supported route is to name the binary lib*.so, let
  the packager put it in lib/<abi>/, and exec it out of
  getApplicationInfo().nativeLibraryDir. This builds that route end to end so the
  spike can be answered with evidence instead of folklore.
- New root option MOBILEGL_BUILD_SERVER_SPIKE (OFF, ANDROID-only) adds the
  MobileGLServer target from tools/spikes/server_stub/main.cpp with PREFIX "lib" /
  SUFFIX ".so" and -fPIE/-pie: an .so name does not exempt the file from Android's
  PIE requirement. Its RUNTIME_OUTPUT_DIRECTORY is pointed at
  CMAKE_LIBRARY_OUTPUT_DIRECTORY, because AGP packages what lands in the per-ABI
  library output directory and CMake would otherwise put an executable elsewhere.
- The option is opt-in on both sides. The plugin flavour cannot turn it on at all,
  and the trace flavour builds it only when asked, with
  `-Pmobilegl.buildServerSpike=ON` or MOBILEGL_BUILD_SERVER_SPIKE=ON in the
  environment; a flavour that silently carries an executable nothing loads is the
  kind of thing nobody notices until it ships. Verified both ways:
  assembleTraceDebug -Pmobilegl.buildServerSpike=ON packages
  lib/arm64-v8a/libMobileGLServer.so and `file` reports "ELF 64-bit LSB pie
  executable, ARM aarch64 ... interpreter /system/bin/linker64, for Android 26";
  the same task with no property packages only libMobileGL.so and
  libtrace_replay_runner.so.
- The stub prints one line to stdout and writes the same line to the file named by
  argv[1], then exits 0. The line carries pid/ppid/uid/gid and, decisively, the
  child's own /proc/self/attr/current: only `u:r:untrusted_app:...` proves an
  ordinary app process did the exec. An `adb run-as` shell runs in a different
  SELinux domain, so a success there would prove nothing.
- RunSpawnSpike() starts the stub with argv [serverPath, markerPath], redirects the
  child's stdout/stderr into a captured file (an app process has stdout on
  /dev/null, so a printed line would otherwise vanish), waits for it, and reports
  exit status, signal, the exec errno, the parent's own SELinux context, the marker
  content and the captured stdout - to logcat, to the returned string, and to a
  <marker>.report file, because the Activity finishes immediately afterwards.
- The child reports the errno of a REFUSED execve through a close-on-exec pipe.
  Without it the one datum the spike exists to produce is lost: the parent only ever
  sees a wait status, in which every reason has already been flattened into one exit
  code, and EACCES (SELinux, or a noexec mount) versus ENOEXEC (a packager that
  mangled the file) are opposite verdicts for the design. A successful exec closes
  the write end for free, so the parent reads EOF and reports execErrno=0.
- fork/execve only. The earlier draft also carried a posix_spawn arm behind
  `__ANDROID_API__ >= 28`, which was dead code in every configuration this repo can
  build - bionic declares posix_spawn from API 28 and the root CMakeLists.txt pins
  MOBILEGL_ANDROID_API_LEVEL to 26 and refuses to configure lower - and would have
  silently become the production path, untested, on a minSdk bump. Keeping the arm
  that actually ships means the spike measures the code the server would really use.
  Nothing happens between fork and execve except open/dup2/execve/write/_exit, all
  async-signal-safe, because the parent is a multi-threaded JVM process.
- The spike lives in its own TU, spawn_spike.cpp/.hpp, listed only by the trace
  APK's CMakeLists. Its sibling trace_replay_core.cpp is compiled verbatim by the
  DESKTOP mobilegl_trace_replay runner (tools/trace_replay/CMakeLists.txt names the
  same file), where <android/log.h> does not exist, so nothing Android-only may live
  there; spawn_spike.cpp carries an #error for anyone who adds it to that list.
- The Activity runs the spike, and nothing else, when launched with the
  `mobilegl_spike_spawn` intent extra; that mode needs no trace, no golden and no
  render surface. It is a separate JNI entry point rather than another parameter on
  the 30-argument replay call, which it shares nothing with.
- Not yet run on a device: both device locks are held by another campaign. The
  on-device verdict is the coordinator's step.
2026-09-05 20:49:57 -04:00
swung0x48 d4f8adcf6d [Tools, Test] (tools/cts, MG_Test): make the CTS runner's chunk timeout idle-based so a healthy 20-minute invocation is no longer killed and its in-flight case mis-recorded as a crash, and link MSVC test executables with /WHOLEARCHIVE so the dllimport gl* references in GetProcAddress.cpp resolve 2026-09-05 05:36:50 -04:00
swung0x48 5d51372c44 [Chore] (Config): triage backend-scoped env toggles under MOBILEGL_ESPRYT_ and MOBILEGL_MAGMA_ prefixes 2026-08-28 00:38:17 -04:00
swung0x48 c136384f97 [CI] (TraceReplay): keep the rd12-odinlite perf fixture out of the CI matrices until its archive is published 2026-08-27 08:23:04 -04:00
swung0x48 0d2fceab0e [Docs] (DeviceBench): note session.sh leaves frequency pins active by design 2026-08-26 05:52:34 -04:00
swung0x48 82decded58 [Test] (TraceReplay): minecraft-1.21.4-rd12-odinlite-in-world perf fixture (campaign benchmark scene) 2026-08-26 05:52:33 -04:00
swung0x48 d04a3394de [Feat] (DeviceBench): launch retry loop (signal-34 JVM flake), session.sh runner, MSYS path-conversion guards 2026-08-26 05:52:32 -04:00
swung0x48 c1d6a3c908 [Feat] (TraceReplay): benchmark mode with per-frame timing for device fixtures 2026-08-26 02:17:35 -04:00
swung0x48 386bd7e461 [Feat] (DeviceBench): scripted in-game FPS benchmark harness (FCLFPS logcat sampling, MTK ppm frequency pinning, thermal gate, GPU busy telemetry) 2026-08-26 01:51:04 -04:00
swung0x48 7480bf4490 [Perf] (CTS-Harness): add a --cpu-mask switch and pin glcts to the big cluster by default 2026-08-20 21:03:54 -04:00
swung0x48 0944925679 [Fix] (CTS-Harness): pin device glcts surface to 256^2 rgba8888d24s8, sync qpa, classify no-log reboots as hangs 2026-08-20 03:48:53 -04:00
swung0x48 eadf7bc474 [Fix] (TraceReplay): import iterationRP repair flags in the desktop CLI
Initialize the desktop replay Request from the three iterationRP environment flags before loading MobileGL. Without this, the Request defaults caused the replay core to unset CI's exported flags, leaving all repairs disabled despite the workflow configuration.
2026-08-20 00:42:34 -04:00
swung0x48 d8576a2ed3 [Fix] (DirectVulkan, ShaderTranspiler, MG_IntegrationTest, SelfTest, TraceReplay): use native subgroups and patch iterationRP's under-declared scratch
iterationRP's Program 203 declares shared vec2 prefixSumCache[32] for a
512-invocation workgroup indexed by gl_SubgroupID; any device narrower
than 16 lanes partitions into more than 32 subgroups and the pack writes
shared memory out of bounds (heap corruption on lavapipe's CPU
rasterizer, ssim 0.028 on the CI retrace). Fix it where the fault lies -
in the fixture - and keep the GL contract sound everywhere else:

- FixIterationRPSubgroupScratchPass: fingerprint-gated SPIR-V pass that
  grows exactly that array to ceil(invocations/width) entries on sub-16-lane devices; every other module passes through byte-identical.
- DeriveNumSubgroupsPass stays default-on for the Adreno topology bug
  and is made spec-sound: pipelines request REQUIRE_FULL_SUBGROUPS
  whenever the workgroup shape makes the flag legal (computeFullSubgroups
  enabled, local_size_x a multiple of the native width, subgroup count
  within maxComputeWorkgroupSubgroups).
- EmulateSubgroupsPass: 32-lane virtual-subgroup lowering kept in-tree
  as a last resort, enabled only by MOBILEGL_MAGMA_EMULATE_SUBGROUP=1 on
  devices with no native subgroup support; fails closed on extended
  subgroup instructions and on modules whose added scratch would exceed
  maxComputeSharedMemorySize.
- IterationRPFirstReductionScenario skips gracefully outside the pack's
  16..256-lane source domain; the new IterationRPScratchFixScenario runs
  the fixture-shaped reduction on any width and asserts the exact
  width-independent total. DriverPost keeps reporting FAIL on
  out-of-domain devices.
- Program203 -> IterationRP rename throughout; the per-trace
  num_subgroups_quirk plumbing is removed from the trace replayer, JNI
  chain, and CI workflows.
2026-08-19 09:48:11 -04:00
swung0x48 2b6c2b561c [Fix, Test] (DirectVulkan, ShaderTranspiler, TraceReplay): derive NumSubgroups behind opt-in quirk 2026-08-18 22:31:57 -04:00
swung0x48andClaude Fable 5 12c94111b5 [Fix, Test] (DirectVulkan, SelfTest, MG_IntegrationTest, TraceReplay): snapshot sampler/image feedback and add Program 203 diagnostics
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-18 03:04:09 -04:00
swung0x48 7769156cfc [Fix, Test] (ShaderTranspiler, WGL, TraceReplay): remove subgroup pack quirks and tune iterationRP 2026-08-17 01:55:17 -04:00
swung0x48 6df5a6137f [CI] (trace-replay): run iterationRP only on DirectVulkan 2026-08-16 11:09:56 -04:00
swung0x48 0deff52a1b [Fix, Test] (DirectVulkan, trace-replay): replay quarter-turn surfaces correctly 2026-08-13 06:45:28 -04:00
swung0x48 e005c8b6cb [Test] (tools/trace_replay): drop the minecraft-26.2-main-menu fixture 2026-08-12 11:07:38 -04:00
swung0x48 205d837942 [Fix, Test] (MG_Backend/DirectGLES, MG_Util, CI): keep an explicit LOD constant under a new avoid flag - folding the bias uniform into it kills the ANGLE llvmpipe JIT 2026-08-12 01:42:55 -04:00
swung0x48 faa7b17da3 [Test] (tools/trace_replay): drop the minecraft-26.2-in-world fixture 2026-08-12 00:03:03 -04:00
swung0x48 c4e6ea1f23 [Docs] (tools/trace_replay): strip investigation logs from the README - usage and procedures only 2026-08-11 23:25:20 -04:00
swung0x48 28390667d7 [Docs] (tools/trace_replay): sundial-lite's sampler state is identical on both stacks - the crash is a driver defect, not a dangling binding 2026-08-11 23:04:25 -04:00
swung0x48 90dd9bec77 [Docs] (tools/trace_replay): sundial-lite crashes in llvmpipe's JIT on a NULL per-texture-unit descriptor 2026-08-11 22:54:50 -04:00
swung0x48 7994ca31d3 [Docs] (tools/trace_replay): sundial-lite has no output-array indexing - separate cause from improved-transparency, and it reproduces locally 2026-08-11 22:48:51 -04:00
swung0x48 5705e05156 [Docs] (tools/trace_replay): sundial-lite kills the emulator via a host llvmpipe segfault, not OOM 2026-08-11 22:38:14 -04:00
swung0x48 ab62f81545 [Docs] (tools/trace_replay): the local farm ANGLE is not the CI ANGLE - re-attribute the improved-transparency causes 2026-08-11 22:32:14 -04:00
swung0x48 f6cf04d6d7 [Docs] (tools/trace_replay): name the two generated-ESSL defects that empty the improved-transparency lane on ANGLE 2026-08-11 22:25:57 -04:00
swung0x48 38eb9589f9 [Test] (tools/trace_replay): dump every live framebuffer attachment at a call boundary 2026-08-11 22:25:57 -04:00
swung0x48 22859b0958 [Docs] (tools/trace_replay): localise the improved-transparency ANGLE divergence to the OIT composite 2026-08-11 21:55:40 -04:00
swung0x48 7aa958fbc9 [Docs] (tools/trace_replay): reproduce the Android DirectGLES lane on Linux, and pin improved-transparency below MobileGL 2026-08-11 21:43:56 -04:00
BZLZHH 0f394fa46f [Test] (tools/trace_replay): pin the Iris glyph-death bug with a BSL pause-menu fixture
minecraft-1.21.4-fabric-iris-bsl-esc-menu-854: Minecraft 1.21.4 Fabric +
Sodium + Iris + BSL 10.1.3 through Espryt on Mali-G77, paused at the ESC
menu over the BSL-blurred world, captured at 854x480 through the FCL
trace-capture skill. The trimmed trace keeps the whole session preamble
deliberately: the 107669b3 bug class is triggered while Iris BUILDS the
pipeline (a by-name texture call swapping the active unit's binding under
the sync memo), and the pause menu afterwards is where its damage is
legible - every glyph alpha-discards against the zeroed lightmap while
the button frames survive.

Ablation-proven as a net before landing: against a build with both layers
of 107669b3 reverted the case fails at ssim 0.949284 and the diff image
is text and only text - menu title, all button labels, the tutorial
toast; against the fixed library it matches the golden exactly, and
Magma passes the same golden at 0.998402 with no alternate needed.
Replays in about a minute per backend on llvmpipe/lavapipe (the 1.3M-call
preamble; well under the timeout and smaller than several existing
fixtures). Existing fixtures spot-checked green.
2026-08-09 12:49:10 -04:00
BZLZHH b5565ae503 [Docs] (tools/cts): refresh the DSA reference tables after the multisample storage fix 2026-08-05 02:50:22 +00:00
BZLZHH 3311e6034a [Fix] (MG_Impl): report a buffer texture as the wrong object, not the wrong token
glGetTextureParameter* resolve the texture by name and then hand the work to the
target-based getter, which validates the target it was given. For a buffer texture that
is GL_TEXTURE_BUFFER, and the target form correctly calls that an unaccepted token -
INVALID_ENUM.

By name there is no token to blame. The application named an object that carries none of
the sampler or level state the query reports, which is INVALID_OPERATION (GL 4.6 core
8.11). The four by-name getters check the resolved object before delegating, so the error
describes what the caller actually got wrong.

Fixes direct_state_access.textures_parameter_errors on both backends, taking the group to
74.93% on Espryt and 73.32% on Magma.
2026-08-05 02:36:31 +00:00
BZLZHH 027c1bd4ab [Docs] (tools/cts): add the desktop Linux CTS skill
The Android and Windows paths each have a skill; the desktop Linux one had only
a runner script and a README section, so it was the least discoverable of the
three despite being the one to reach for while iterating - it needs no device
and no GPU, and a single test group takes seconds rather than hours.

Records what the other two skills cannot: that the toolchain has to be GCC 13+
or Clang 20+ (Clang 18 reports __cpp_concepts as 201907L, which switches
libstdc++'s <expected> off and breaks the shader transpiler), that
EGL_PLATFORM=surfaceless is mandatory for DirectGLES and why the symptom points
at the wrong call, and which of this environment's results are MobileGL's own
versus artefacts of software rendering.

Also states the rule the other skills only imply: report Espryt and Magma
separately. They fail different cases, and one combined number hides which
backend a change moved.
2026-08-05 02:31:46 +00:00
BZLZHH da52cc3906 [Docs] (tools/cts): refresh the DSA reference table for the fixes in this branch 2026-08-05 02:29:42 +00:00
BZLZHH 35ad1ae7fc [Docs] (tools/cts): document the desktop Linux CTS path and the DSA baseline
run_cts_local.py and the mobilegl-desktop VK-GL-CTS target were both in the tree
with nothing describing how to reach them, so the only documented ways to run the
suite needed either an Android device or a Windows box with a GPU. The desktop
Linux path needs neither: lavapipe gives DirectVulkan a headless surface and
Mesa's surfaceless EGL gives DirectGLES a context, so a single test group can be
measured in seconds while working on it.

Records the two things that cost time to find. EGL_PLATFORM=surfaceless is
mandatory for DirectGLES - without a /dev/dri node Mesa fails eglInitialize on
the default display, and MobileGL surfaces that as EGL_BAD_ALLOC from
eglCreatePbufferSurface, which points at the wrong call entirely. And
DirectVulkan's default-framebuffer readback returns zeros here exactly as it does
on Adreno, so that defect is MobileGL's and reproducible without a phone.

The direct_state_access reference table is the measured baseline for the fixes in
this branch, so a later change has something to be compared against.
2026-08-05 02:22:09 +00:00
BZLZHH 1c5f6c0986 [Chore] (tools/cts): pick a run config the suite does not contradict
Two harness settings were producing failures that say nothing about the backend:

- dEQP's FboRenderContext picks the first entry of its own depth/stencil format
  list, GL_DEPTH32F_STENCIL8, when the config leaves the bit counts DONT_CARE.
  framebuffer_blit meanwhile hardcodes GL_DEPTH24_STENCIL8 for its own buffers
  as soon as it detects an FBO surface, and then blits depth between the two -
  which the spec forbids for mismatched formats, so a conformant driver has no
  choice but to fail it. Default to --deqp-gl-config-name=rgba8888d24s8 so the
  wrapper framebuffer and the test agree.

- --deqp-watchdog aborts the whole process when one case exceeds a hardcoded 30
  seconds (framework/common/tcuApp.hpp). That is not a hang on a CPU rasterizer:
  several texture_swizzle cases take ~17s each standalone and cross the limit
  once the process is warm, which came back as ten spurious Timeouts. dEQP's own
  default is off, and --chunk-timeout is what actually rescues a genuinely
  wedged case, so default it off too and leave it selectable.
2026-08-01 13:48:58 -04:00