- 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
- pipe-gates stops printing gen_pipe_dirty_surface.py --summary and runs --check && --self-test.
--check fails both directions - a scanned mutator with no row in MG_Pipe/DirtySurface.def, and
a row naming a mutator the scan no longer finds - so a deleted mutator cannot leave a stale row
behind claiming coverage. --self-test is what keeps --check honest: a completeness check that
silently stopped checking is indistinguishable from a complete mapping, so two canned negative
controls must both trip. Same shape as gen_pipe.py --self-test next to it.
- integration-verify gains `ctest -L unit`. G6's chunk-table walk and G10's residual assertions
live in MG_Test/Pipe, compiled only under MOBILEGL_PIPE_PUSH, and the `test` job builds the
PULL library - so before this those tests ran in no CI job at all. The artifact already carries
them (the packaging step tars MobileGL/MG_Test whole), so the whole cost is the run: ~14 s for
~1490 entries, measured locally on this tree.
- integration-verify also runs the two always-on negative controls by name. They are labelled
integration-gpu rather than integration-verify - they are about the handle key and the CSO
switch, not the comparator - and this is the only CI job that unpacks a push build, which
CsoContentAddressingScenario needs because both counters and the cso[] bracket are
#if MOBILEGL_PIPE_PUSH.
- build-linux-verify's arming check accepts the per-verb entry point under either of its two
names. P2 renames MGPipeFillForVerb to MGPipeValidateForVerb, and a check that named only the
old one would go red on the rename for a reason unrelated to what it tests. What it tests is
unchanged: the artifact has a per-verb entry point, and it still fails when there is none.
- the MGPipe phases land as a series of pushes and each one needs the full lane; dispatching
by hand after every merge is a step that gets forgotten
- both entries carry a remove-before-merging-to-dev note: dev's trigger set is what ships
- both `nm -D --defined-only ... | grep -q MGPipe...` gates could never pass: the library
is built CXX_VISIBILITY_PRESET hidden in every non-Debug configuration and the MGPipe
entry points carry no export attribute, so the dynamic table holds none of them (0 of
11930 exported symbols on this tree, while `nm --defined-only` finds MGPipeFillForVerb
as a local `t`). build-linux-verify, and with it integration-verify and every
retrace-verify entry, would have been red forever for a reason unrelated to the
comparator. Both now read the static table, name what a miss means, and refuse a
stripped artifact instead of reporting its silence as a missing symbol
- "Every verify process really armed" grepped one shared per-lane log that holds only the
LAST process of 406 - and the last ambient entry is the poison control's parent, which
forks, execve()s and waits without ever issuing a verb, so the step would have red a
healthy lane while proving nothing about the other 405. It now greps the two
VerifyArming. lanes' own logs, requires one per backend, and says what that establishes
- remove-artifact-clutter kept fixtures for failed `retrace (` jobs only, so a failed
`retrace verify (` case lost the fixture needed to reproduce it; both prefixes now count
- the retrace negative control replayed OpenRA into the same case directory, so
"Upload actual image" shipped the deliberately corrupted run's output under the good
run's name. The verified output is put aside and restored before the verdict
- build-linux-verify regains build-linux's "Show installed toolchain" step, and its
deliberate divergence (Release even under ACTIONS_STEP_DEBUG - a Debug build would flip
visibility and arm the poison through a different #if arm) is written down
- the lane's scope is stated where it is run: every integration ENTRY under the
comparator, not every configuration - `integration`'s second
MOBILEGL_ESPRYT_DISABLE_INVALIDATE_FLUSH=1 pass (186 entries here) is not affordable at
the 5-10x the comparator costs, so that tier stays covered unverified by `integration`
- build-linux-verify is a second Release/INFO build with -DMOBILEGL_PIPE_VERIFY=ON,
because the comparator is a compile-time option and does not exist in the shipped
library. It refuses to ship an artifact whose libMobileGL.so does not export
MGPipeVerifyInputs and MGPipeFillForVerb: a typo'd -D is not an error in CMake,
and every lane below would then be green having compared nothing.
- integration-verify runs the suite with the comparator armed and then proves it
armed twice over: --no-tests=error reds a build whose verify entries were never
registered, and a step greps every pipe-verify-*.log for the arming line.
- The two negative controls are steps of that job, not a manual exercise: a gate
that can only be shown to work by someone remembering to break it has already
stopped working. Each passes when ctest FAILS, and each first counts its own
selection - an empty selection also exits non-zero under --no-tests=error, and a
control that passed because it ran nothing would be worse than no control.
- Control B targets PoisonOmissionScenario.WithoutOmissionCompletes, the only
integration entry in the tree that calls glGenerateMipmap at all; the case no
longer skips itself when the omission knob is set, precisely so that the control
has a green entry to turn red.
- retrace-verify replays the eight "verify": true cases against the verify library,
copied over build-linux/libMobileGL.so because build-retrace freezes that absolute
path into every case, with an nm check that the swap happened and an inverted
OpenRA step that must go red under MOBILEGL_PIPE_VERIFY_CORRUPT.
remove-artifact-clutter now waits for it: it deletes the trace fixtures these jobs
download.
- monolith-symbol-report is G1 as a job: two pull builds with identical flags and
LTO off, the baseline named by a workflow_dispatch input, symbol_report.py with
both hard gates, and a refusal of any MG_Remote symbol in the monolith. It is
dispatch-only because its answer is about a baseline, not about this push.
- pipe-gates gains the two --self-test steps. Regenerating and diffing cannot see a
structural check that silently stopped checking; a broken gate and a clean tree
produce the same green. Its dirty-surface comment now says P2, which is where
ROADMAP.md:18 puts the first mapping round.
- Includes.h defines VK_USE_PLATFORM_XLIB_KHR before vulkan.h, so the clang-mode probes and
the negative controls need X11/Xlib.h on the runner; run 34008829271 failed on exactly that
- ROADMAP P0.5 asks for a CI assertion on the include closure of the two headers the
phase extracts, and ROADMAP.md:7 asks every gate to be able to fail for the reason it
exists. `nm --undefined-only` cannot express either: a header that is included but
whose types are never named leaves no symbol behind, and "included at all" is exactly
the coupling P1 and P7 have to sever. The preprocessor's own `-H` transcript can.
- Three probes, coded against the fixed path contract of the P0.5 brief so this package
lands before the headers do: value-header (MG_Pipe/MGPipeValueTypes.h: no MG_State/,
MG_Impl/, MG_Backend/, MG_Remote/), artifacts-header (ProgramArtifacts.h: no
ShaderObject.h, ShaderTranspiler/, Config.h, MG_Backend/, BufferState/, ProgramState/
Shader*, plus a budget of two `glslang::` tokens for the two members D5 keeps
verbatim) and wire-header (ITransport.h must not reach Includes.h - green today, so
the gate has a live probe from its first commit).
- The forbidden sets say nothing about glslang, spirv-cross or vulkan on purpose:
Includes.h pulls all three unconditionally and both new headers are allowed
<Includes.h>, so a textually glslang-free closure is unsatisfiable by construction.
P7 measures that with `nm -D | grep glslang` on the server binary instead.
- Two modes because they check different things. Text mode walks literal #include lines,
needs no compiler and no submodules, and is what the ctest runs (the CI `test` job's
runner has neither); clang mode is the arbiter, and adds a -fsyntax-only pass proving
the header is self-contained. `--mode both` additionally fails on a disagreement
between the two violation sets, so text mode's blindness to `#if` cannot hide a hit.
- -H parsing normalises before matching (today's transcripts contain
TextureState/../SamplerState/SamplerObject.h) and accepts only `^\.+ ` lines, which
discards the "Multiple include guards may be useful for:" paragraph g++ appends.
Both are pinned by a canned-transcript check inside --self-test.
- D9 skip semantics: a probe whose header does not exist prints SKIP and is counted, and
--require-all turns every SKIP into a failure. That is what lets the gate land first
and still stops an all-SKIP run from passing for free once the headers exist; the
integrator flips --require-all on after all three P0.5 packages land.
- --self-test is always on in both the ctest and the CI job: it synthesizes its TUs in a
tempdir (it never touches a tracked file) and requires a negative control that does
not depend on P0.5 at all - MGPipeHandles.h plus RenderState.h checked against the
value-header list - to report RenderState.h as a depth-1 violation in every enabled
mode. Zero trips anywhere is an ::error:: and exit 1, because a gate that cannot go
red is not a gate. Controls 2 and 3 arm themselves as the two headers appear.
- Registered as MobileGLPurity.IncludeClosure with LABELS unit so `ctest -L unit` runs
it, and with no ENVIRONMENT property, which would replace the job env wholesale.
- S-1 PipeStats::OnPresent read each frame accumulator and then store(0)'d it; a Bump from a staging thread landing in between was lost from the Tracy plot and from every frame. Each accumulator is now exchange(0, relaxed) and the exchanged value is what is plotted, so every add lands in exactly one frame.
- T-3 FdPassing without MSG_CMSG_CLOEXEC (macOS, BSD) handed back descriptors that survived exec; every received fd now gets FD_CLOEXEC by hand under !MSG_CMSG_CLOEXEC. MSG_NOSIGNAL is defined to 0 where the platform lacks it (FdPassing.cpp, Doorbell.cpp) and SO_NOSIGPIPE is set on the socketpair and on a SocketDoorbell's descriptor where it exists, so a write to a hung-up peer is EPIPE rather than a fatal signal.
- T-4 the missing-flatbuffers fallback wrote OFF into the cache with FORCE, so a plain re-configure after `git submodule update` stayed OFF silently. It is a normal-variable set now, shadowing the cache for that configure only; verified by hiding flatbuffers.h, configuring with ON (warning, transport off, cache still ON) and re-configuring plainly with the header back (transport ON).
- P-2 three LIVE GLFunctionsTable entries had no carrier: GetGpuTimestampNs (glGetInteger64v(GL_TIMESTAMP), a synchronous server answer), QueryCounterTimestamp (glQueryCounter, a one-shot stamp, not a begin/end pair) and WaitSync (the GPU-side wait FenceWait's client wait does not express). QueryTimestamp (MGPTimestampRequest, kCtxQuery, kReplySlot), QueryCounter (MGPQueryDesc with Kind = GL_TIMESTAMP, kCtxQuery) and FenceWaitServer (MGPFenceWait, kScreen) are APPENDED at the end of PipeCalls.def because the opcode is the position: SetSwapInterval stays 68, the three take 69-71, and PipeCatalogue.LateArrivalsAreAppendedWithoutRenumbering pins that. Header counts 71 (screen 11, query 8); the seven generators regenerated.
- P-3 MGPBufferRange inlined a 32-byte MGHostSpan into every range of every class - dead space on every SSBO, atomic-counter and XFB range, and D-B8 says not to freeze the named-UBO payload before the stage-ubo-named numbers exist. The range is 24 bytes now; the host spans are an optional second var-tail behind the ranges, announced by MGPShaderBuffers::HostSpanCount (0 or Count), with set_shader_buffers keeping its kVarTail|kHostSpan flags. PipeCatalogue.BufferRangeCarriesNoInlineHostSpan pins the sizes, the flags and the comparator's view of the count.
- P-4 QueryEnvUint64 parsed with base 0 (a leading zero meant octal: MOBILEGL_PIPE_PUSH=010 read as 8) and accepted -1 as every bit set; it is decimal or explicit 0x now and a '-' anywhere is refused with the warning (smoke through the integration binary: -1 and 12abc warn, 010 and 0x10 parse). The CI stdio gate's alternation now also catches fprintf(stdout, puts( and std::cout/cerr; it is green over MG_Backend and MG_State. MGPSubData states how the buffer half expresses [offset, size): UnionBox.X / UnionBox.W with Target == Buffer, Y = Z = 0, H = D = 1, one record bounded at a 2^31-1 offset and 2^32-1 size beyond which the emitter splits (the same rule the ring's half-capacity bound already imposes); MGPipeSetSubDataBufferRange / MGPipeSubDataBufferOffset / Size are the only spelling and PipeCatalogue.SubDataBufferRangeRidesInTheUnionBox pins the encoding and its bounds. gen_pipe.py now refuses, in both modes, a call payload named in PipeCalls.def with no field list in PipeFields.def (the four memcmp-fallback member types are the documented exception); shown by dropping P(MGPSwapInterval), which exits 1 naming the payload.
- The MGPPixelPackState size assertion compared sizeof against itself; it asserts the literal 28 PixelStoreParameters measures.
- Verified: ctest -L unit green in both the default and the split configuration, gen_pipe.py --check clean with the generated files committed, nm --defined-only of the default libMobileGL.so has no MG_Remote symbol, and the full integration-gpu suite passes (the *IsActuallyArmedWhenTheEnvironmentPinsItOn family trips under -j 8 as documented and passes serially).
- MobileGL/MG_Test/Wire, 42 gtest cases in five binaries, ctest label `unit`, registered only when MOBILEGL_BUILD_DISAGGREGATED is ON so the default configuration is untouched.
- FdPassingTest is the one the earlier branch could not have had: it forks a child that creates a shared segment, fills 64 KiB with a pattern and hands the descriptor over SCM_RIGHTS; the parent adopts it, maps it read-only and compares every byte. Everything in Feat/CS-Delta-IPC ran in one process, which is why `out->fd = -1` survived unnoticed. It also pins that a too-small sideband buffer is refused before the datagram is consumed (so no descriptor is dropped), that an empty sideband still carries its fd, and that a receive with no offer times out. The `spawn` SocketDoorbell is covered in the same file because it rides the same kind of socket: a parked waiter woken through NotifyIfParked, a clean timeout, and a wakeup that arrives before anyone parks being remembered and then consumed exactly once.
- RingTest: control-page size/alignment/cache-line layout, a non-power-of-two capacity refused, payload alignment, 200 records driven through a 256-byte ring so the wrap filler path runs repeatedly and no record ever straddles the boundary, backpressure (full ring refuses, applied alone frees nothing, retired frees), a record larger than the ring refused, the generation bump refused while records are in flight and accepted once quiesced, a corrupt header refused instead of dispatched, and a 20000-record two-thread producer/consumer run checking order, content and the final cursor equality.
- FramingTest: byte-at-a-time reassembly of two frames, the magic reading "MGLF" on the wire, empty payloads, a bad magic and an oversized length each latching the reader dead (the old code hung silently instead), the send-side cap, and the buffer-too-small contract keeping the message so the retry still finds it.
- InProcessTransportTest: both directions, ordering, buffer-too-small, poll and timeout, shutdown draining queued messages before it closes, a blocked receiver woken by a send and by a shutdown, the frame cap, descriptor hand-off with its sideband, and three condvar doorbell cases - a parked waiter woken through NotifyIfParked, an already-true condition that must never park, and a clean timeout.
- ProtocolSmokeTest: a Hello built and read back through the committed generated header, a Welcome carrying the four segment announcements, the same buffer travelling across the transport unchanged, a truncated buffer failing verification rather than being read, and the CtrlMsg / SegmentKind / LogLevel / FatalCode tag values frozen - they are wire numbers, so if that test has to be edited the schema change was a wire break.
- CI: a `flatc-check` job in test.yml that checks out only the flatbuffers submodule, builds the pinned flatc through scripts/gen_protocol.py into the runner temp directory, regenerates and runs `git diff --exit-code` on protocol_generated.h. It needs no MobileGL build, so it does not depend on build-linux (plan B section 8.1 / earlier section 7.1: the committed header plus a CI regeneration check, and no flatc in the default build graph).
- Verified: `ctest -L unit` is green in both configurations - 1382 cases with the option OFF and 1424 with it ON (the same 1382 plus these 42); the nm gate is 0 matches OFF and 93 ON; regeneration of protocol_generated.h is byte-identical, and the flatc-check gate goes red on a hand-edited header and green again after a clean regeneration.
- Three P0 gates from plan B section 11, in one job that needs no build: a broken build
must not be able to hide a drifted interface.
- pipe-gen-check regenerates G1-G7 and runs git diff --exit-code over
MobileGL/MG_Pipe/generated. Since all seven generators read the same .def files, this is
what makes drift between them impossible rather than merely unlikely.
- The stdio gate refuses fprintf(stderr and printf( under MG_Backend and MG_State. Nothing
there matches today, so it lands with NO whitelist - verified with a negative control
that fprintf(stderr and std::printf both trip it while snprintf does not. Per-draw
instrumentation has been committed by accident before, once inside a mutex critical
section, and MGLOG_D is the channel these trees are allowed to use because it compiles
out in INFO builds.
- scripts/gen_pipe_dirty_surface.py reports the frontend mutation surface corollary 4
needs covered: 926 mutator calls under MG_Impl/GLImpl over 73 distinct mutators, of
which only 92 sit in a function that also reaches the backend. The other 834 are
published by the NEXT verb, which is precisely the population that needs an aggregate
generation. Informational in P0; it becomes a gate in P1 when there is a mapping file to
diff against.
- scripts/check_doc_citations.py resolves every `path:line` citation against a git
revision. It reproduces the failure that motivated it - the plan's first draft cited
SamplerObject.h:468-492 in a 160-line file - and reports 84 unresolved citations out of
1028 in docs/Disaggregated today, which is why CI runs it warning-only until those
documents settle. --strict exits 1, verified in both directions.
Run the lavapipe Program 203 golden test with the same subgroup scratch, derived topology, and missing-barrier repairs as the iterationRP retrace matrix. This keeps the prerequisite integration job from failing before retrace jobs can start.
Program 203 reuses prefixSumCache for a second subgroup reduction before every workgroup invocation has consumed the first result. Add a fingerprint-gated SPIR-V pass that inserts the missing Workgroup acquire-release barrier while preserving native subgroup operations.
Keep the repair opt-in behind MOBILEGL_ITERATIONRP_FIX_BARRIER, cover insertion, pass-through, and idempotence, and enable it together with the existing iterationRP subgroup repairs for the matching Linux and Android CI retraces.
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.
The benchmark job is the only one that brings a real GL context up - DriverBench
dlopens libEGL.so.1 and renders through it - but its apt list only asks for
libegl1, which is glvnd's dispatch layer and nothing more. The vendor library
behind it, libegl-mesa0, has been arriving as a Recommends of libegl1 rather
than because anything asked for it. That is too quiet a dependency for the one
job whose whole purpose is running a driver: a base image change, or
--no-install-recommends turning up anywhere upstream, would leave eglInitialize
with no vendor to dispatch to and fail the job for a reason nothing in the
workflow explains. Name it, next to libgl1-mesa-dri, which is listed for
exactly the same reason.
Verified with a full headless ctest -C Release -L benchmark - no $DISPLAY, no
$EGL_PLATFORM, mesa as the only EGL vendor: SanityBench, ProgramBench,
BufferBench and DriverBench all pass.