mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 20:28:32 +09:00
eec92cd221eda13b25b1f374b2021c2c832d112f
2679
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
eec92cd221 |
[Test] (Pipe): walk every RenderState setter and assert the pipeline-subset hash moves exactly when the pipeline version does
- SetterConsistency is G7. It drives every public RenderState setter with a value that
differs from the one stored, and asserts the pipeline-subset hash moves IF AND ONLY IF
m_pipelineStateVersion moves. Every case also asserts m_version moved, which is the
vacuity guard: a setter handed the value it already holds satisfies "neither moved"
trivially and proves nothing.
- The cases that are not just a list: SetStencilFunc twice (a reference-only change must
move the version and NOT the hash, because Ref/ValueMask are dynamic and Func is
pipeline, and RenderState.cpp's pipeline bump is conditional on Func); SetPolygonMode
with only the back face moving (PolygonModeBack is one of the members P2's subset added
over the 24 ComputePipelineStateHash hashed); the eight ClipDistance capabilities (the
one family that moves m_version alone, so the hash must hold); SetScissorBox across the
ScissorBoxWrittenMask transition; all 25 SET_CAPABILITY names including the three that
had no storage before the contract commit; and SetPixelStoreParam, which must move
neither counter and touch no byte of the block.
- Verified red for the right reason: moving the P1/D2 boundary so ColorMasks falls in the
dynamic half - the partition stays complete, so it still compiles - makes
SetterConsistency fail naming SetColorMask and SetColorMaskIndexed, and nothing else.
- ChunkTablePartitionsTheBlock re-states the header's static_assert at run time and adds
the half the hash cannot check for itself: membership. It walks PipeFields.def's
MGP_FIELDS_RenderStateParameters - the same list gen_pipe.py checks against the struct -
and asserts a member is covered by a pipeline chunk exactly when
kMGPipePipelineStateMembers names it, that every other member is wholly dynamic, and
that StencilStates straddles at exactly the sub-member granularity the table intends.
- DerivationMatchesTheFrontendGetters drives 30-odd setters on a live GLContext AFTER the
filler has run, assembles the working block through the real create/bind/set_dynamic_state
path, and compares the derived fields against the frontend getters. The stale fill is the
point: an ASSERT_NE before each apply proves the block disagrees first, so nothing here
can pass by comparing the filler with itself. It runs in THREE verb phases, because the
fill table is the only thing that says what a verb may read: 25 of these fields are
kDraw's, the three clear values are kClear's and GetClampReadColor is kReadback's alone,
and reading a clear value under DrawArrays is Fatal{UnmigratedPipeInput} - correctly, and
the poison caught exactly that in the verify build before this shape.
- GetViewport's rounding is exercised on (1.5, 2.5, 63.5, 32.25) and the expected literal is
std::lround's answer - half away from zero - not the banker's rounding nearbyint gives.
- DynamicChunksCoverMagmasDynamicTailKey checks every GL-state input of DirectVulkan's
ApplyDynamicDrawStateTail against the dynamic half, and records the one exception the
design implies but no document states: ScissorTestEnabledMask is read by DynamicTailKey's
scissorEnabled yet is PIPELINE state, because SetCapability(ScissorTest) calls
BumpVersions(). Harmless - BumpVersions moves m_version too, so MGPDynamicState::Version
still moves and the tail still re-runs - and asserted the other way round so a later
table edit that demotes the mask is loud here.
- Replaces the contract commit's placeholder case, which existed only so the target had a
test before this package filled it in. The four names are the same four in every build:
in a pull build each is a visible SKIP, never a vanishing test.
|
||
|
|
810850b13a |
[Feat] (Pipe): derive every render-state PipeInputs field from the assembled working block instead of pulling it again from GLContext
- 29 of the 47 kDraw PipeInputs fields are pure functions of RenderStateParameters. Once bind_render_state and set_dynamic_state have assembled the working block - which IS PipeInputs::m_renderState - copying those fields out of GLContext a second time is the per-verb pull P2 exists to remove. The applier now derives them after any scatter. - Each line is a transcription of the RenderState getter of the same name; GLContext's accessors are one-line forwards to those, so the derivation and the pull path answer the same question from the same bytes. Two are not field copies and are transcribed exactly: GetViewport (viewport 0 rounded with std::lround, because glGetIntegerv on float state rounds to nearest and truncating a 63.5-wide viewport would hand the backends a rectangle one pixel short), and IsCapabilityEnabled (the 35-way switch, including Blend -> BlendStates[0].Enabled, ScissorTest -> ScissorTestEnabledMask & 1 and the ClipDistance run). The indexed twin is the same for Blend[8] and ScissorTest[16]. - IsCapabilityEnabled could not have been written before the contract commit: DepthClamp, FramebufferSrgb and TextureCubeMapSeamless fell to `default: return false`, so three of the 35 answers were a compile-time constant rather than state. - This departs from P1 brief D4's "no derivation logic is re-implemented in PipeInputs", deliberately: the alternative is to keep pulling those 29 fields per verb. The guard is the oracle P1 built - MOBILEGL_PIPE_VERIFY's compare-at-read re-reads every one of them from the live context at EVERY backend read and compares field-wise, so a transcription error is caught on the first draw that reads it, across 79 retraces and the integration-verify entries. - The body sits in MGPipeApplyAccess, the struct PipeInputs already names as its friend, so no new friend and no new accessor per field. Pull build untouched: PipeApply.cpp compiles only under MOBILEGL_PIPE_PUSH. |
||
|
|
9c6a8a25d8 |
[Feat] (Pipe): land the P2 contract - real storage for the three swallowed capabilities, the render-state chunk table and its subset hash, the in-process applier, the slot allocator, the subsystem bitmask and the residual ratchet down to 8
- FramebufferSrgb, DepthClamp and TextureCubeMapSeamless get real storage. All three fell
to SetCapability's "not supported currently" arm and IsCapabilityEnabled's default:
glEnable was swallowed and glIsEnabled lied, so DirectGLES' sRGB block and the
DirectVulkan read points consumed a constant. The three Bools land in the three
alignment bytes at [581, 584) between ColorMasks and ClearColor, so
sizeof(RenderStateParameters) stays 1168 and NO existing offset moves - Espryt's
kBlendSpanBegin/kBlendSpanEnd (312/536) and the whole chunk table depend on that.
- MGPipeRenderStateSpans.{h,cpp}: the pipeline/dynamic split, written in exactly one place.
The rule is the only rule - a byte is pipeline state iff a public RenderState setter that
calls BumpVersions() writes it - which makes G7's "the subset hash moves iff
m_pipelineStateVersion moves" true by construction. 16 boundaries, all offsetof or
sizeof, alternating dynamic/pipeline: 8 dynamic chunks / 772 bytes and 7 pipeline chunks
/ 396 bytes, partitioning [0, 1168) exactly, asserted at compile time.
MGPipeComputePipelineSubsetHash is XXH64 over the seven pipeline chunks, seeded with a
table version so a chunk-table change invalidates every persisted key.
- The pipeline subset is now a strict SUPERSET of the 24 members ComputePipelineStateHash
hashed: 44 members, adding sample coverage, the front face, the provoking vertex, the
scissor-test mask, the back polygon mode, eleven capability bools the hash never read and
the three above. Demoting those setters to ++m_version instead would have changed
MG_State semantics in the PULL build for the push path's sake. The hash runs only when
m_pipelineStateVersion moves, which is exactly when Magma re-hashed before.
- PipeApply.{h,cpp}: the in-process applier, the server half of the P2 calls. The server's
working RenderStateParameters IS PipeInputs::m_renderState, which is why DirectGLES'
SyncRenderState is not one line changed and why the verify comparator stops being a
tautology. Per-context CSO store indexed by slot, gen-validated; the residual block's
capability bits are compared against the assembled block, so a capability a later call
takes over and forgets to carry is Fatal{PipeResidualDiverged}.
MGPipeDeriveRenderStateFields is a declared STUB - its 29 derivations are commit c1.
- SlotAllocator.{h,cpp}: the client's per-kind {slot, gen} allocator, free list plus
high-water, first allocatable slot 1, gen bumping only on slot REUSE, a debug assert on
gen wrap, the composite ShaderCso band held back, and a lifetimeId -> slot map per kind so
a GL name never enters a key. In the contract because both Track H slices need it.
- ResidualValueBlock 1248 -> 8 bytes, one Uint64 of capability bits.
RenderStateParameters retired to create/bind_render_state and set_dynamic_state, Pack to
set_pixel_pack_state, the patch quintet to set_patch_state. gen_pipe.py now emits the
member-by-member offsetof assertions the ratchet comment always promised.
- gen_pipe.py: PIPELINE_STATE_MEMBERS grows to the 44-member set in declaration order and
PipeSpanTable.inc's "deliberately absent" block records the answers instead of the
questions; Coverage.def gains MGP_COVERAGE_EMITTED_LIST (34 rows) and PipeFilled.inc
gains kMGPipeFieldEmittedBy[], which is what lets the residual fill loop skip a field a
P2 call now supplies. One more --self-test negative control covers the new list.
- MOBILEGL_PIPE_PUSH becomes a per-subsystem bitmask with named bits (0..6 migrated at P2,
bit 63 the CSO-content-addressing negative control), defaulting to 0x7f in a push build
and staying 0 in a pull build. New CMake option MOBILEGL_PIPE_LEGACY_MEMOS, ON, forced ON
when MOBILEGL_PIPE_PUSH=OFF where it is the only arm. New Features.PipeHandleAbaControl
under MOBILEGL_PIPE_PUSH, negative control C for HandleRecycleScenario.
- PipeStats gains CallClass::{RenderStateCsoMints, RenderStateCsoBinds} (csom / csob on the
summary line), and they are PUSH-ONLY: growing the enum in the pull build would resize
the counter arrays, the name table and FormatWindowLine for two counters that could never
leave zero, and G1 admits no such resize.
- Four MG_Test/Pipe stubs plus their CMake registration, so the packages that own their
contents never touch MG_Test/Pipe/CMakeLists.txt.
G1, pull build, symbol_report --threshold 0: 0 added, 0 removed, 0 renamed, 4 resized, and
every resize is attributed:
RenderState::RenderState() 1700 -> 1848 (+148) the three {}
RenderState::SetCapability(CapabilityInput,bool) 850 -> 927 (+77) three switch arms
RenderState::IsCapabilityEnabled(CapabilityInput) 239 -> 268 (+29) three switch arms
_GLOBAL__sub_I_DirectGLES.cpp 1340 -> 1331 (-9) the static
initialiser of DirectGLES.cpp's `static RenderStateParameters
g_syncedRenderStateParameters` re-scheduling around the three new default-initialised
members. A shrink, and the only unforeseen entry; it is a direct consequence of the
struct gaining members and touches no interface.
|
||
|
|
4826806881 |
[CI] (Workflows): run the test and apk lanes on every push to feat/disaggregated
- 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 |
||
|
|
e7a6a72f6a |
[Docs] (Disaggregated): record the P1 landing and what its verify lane found
- README status, the ROADMAP P1 row with the measured site and accessor counts, and the ARCHITECTURE correction from 293 to the 277 arrow sites the tree actually has - MEASUREMENTS gains the P1 scale table, the two classes of finding the verify lane produced (nine missing fill rows, three fields a backend moves inside its own verb) with the push-on-mutation decision and its three hooks, and the acceptance numbers |
||
|
|
62a7786184 |
[Fix] (Pipe, Purity): end a declared verb honestly, and gate the header MG_State now includes
- MGPipeLeaveVerb() bumps the serial and puts the current verb back to none, so a test that drives a backend helper directly stops declaring where it says it stops and a later unguarded read aborts as "<Field>@<none>" instead of naming an unrelated verb - check_include_closure.py gains a fourth probe: F2 put MGP_NOTE_MUTATION into frontend mutators, so MG_State includes MG_Pipe/PipeMutation.h and that header must never reach back into MG_State, MG_Impl or a backend |
||
|
|
ef6227e19b |
[Test] (Pipe): let a test that drives a backend helper directly declare the verb it stands in
- ScopedPipeVerb (MG_Test/ScopedPipeVerb.h): an RAII "as if we were inside verb X" object
that runs the real MGPipeFillForVerb for the verb it names, so the eleven unit entries
that construct a GLContext by hand and call a backend helper with no GL entry point in
between stop reading an empty, unstamped PipeInputs block
- it weakens nothing: it fills exactly that verb class's may-read mask, so a read outside
it is still Fatal{UnmigratedPipeInput} naming the field and the declared verb; leaving
the scope re-arms the poison with a one-field kQuery fill, so one case's declaration
cannot cover a later one when the binary runs as a single process
- placed the way MGP_FILL is placed in production: immediately before the backend call,
after every frontend mutation it is meant to see; a second call after the test moved
state is a second verb (Renew()), a helper of another class gets a nested scope
- no-op in the pull build, no test renamed, no test added, no production source touched
|
||
|
|
9bd6d39403 |
[Test] (Pipe): pin the push-on-mutation shape - a frontend write inside a verb refreshes the pushed field, and only its value
- AFrontendMutationInsideAVerbRefreshesThePushedField: fills DrawArrays, then
does what UniformManager's fallback path does mid-draw (a SamplerObject filter
change) and what a bind reached from inside a verb does
(NoteTextureUnitTouched), and asserts the block still equals the live context
for GetSamplingResolutionGeneration, GetTextureBindGeneration and
GetMaxTouchedTextureUnit.
- AFrontendMutationInsideAVerbDoesNotDivergeAtRead: the lane failure end to end,
under the armed comparator in a forked child - the read after the mutation
must complete and the log must carry no Fatal{.
- TheMutationNoticeRefreshesTheValueButNotTheStamp: the notice must not restamp
a field whose stamp the fill withheld (negative control B), and must not stamp
a field the verb class never fills (the generation under a kQuery verb).
- Falsified: with the notice's body short-circuited, the first two fail (the
read-side one by SIGABRT on Fatal{PipeVerifyDiffer,
"GetSamplingResolutionGeneration@DrawArrays", where=read}) and the third
stays green, which is what a guard case should do.
- The three names also exist as visible GTEST_SKIPs in the pull build, as every
other case in this file does.
|
||
|
|
6b681c4a63 |
[Fix] (Pipe, State): refresh a pushed PipeInputs field when the frontend moves it inside a verb
- The verify lane aborted eight integration entries and two retrace cases with
Fatal{PipeVerifyDiffer, "GetSamplingResolutionGeneration@DrawArrays",
where=read}, always one line after "ResolveSamplerDescriptor: using fallback
texture for unbound sampler". The backends write into frontend objects during
their own verb - Magma synthesises a fallback texture for an unbound sampler
and gives it a shape, materialises a queued clear, overrides a unit's sampler
filter - and every one of those writes moves a counter MGP_FILL already
copied, so the pushed block stops equalling the live context for the rest of
the verb. That is a real divergence, not a harness artefact: the pull build
reads the moved value and the push build reads the boundary one.
- Takes the findings' preferred option, push on mutation, over the volatile-in-
verb class: it keeps the comparator's invariant ("the pushed block equals the
live context at every read") literally true, keeps push semantics equal to
pull, and is the shape P2's tracker needs. The fallback would have had to skip
compare-at-read for the field, which is the one comparator arm that is real in
P1 - it would have blinded the gate on the very field that found the bug.
- MG_Pipe/PipeMutation.h declares MGP_NOTE_MUTATION(Field), a no-op that
includes nothing in the pull build; MG_Impl/Pipe/PipeFill.cpp defines the
notice next to the filler it shares CopyField with. The notice refreshes one
field's value when a context is live, a verb has been filled, and the field is
in that verb class's may-read mask; it never touches the poison stamp, so a
stamp MOBILEGL_PIPE_POISON_OMIT withheld stays withheld and a field the verb
never filled stays Fatal{UnmigratedPipeInput} rather than being healed.
- The enumeration behind the three hook sites: of the ~40 backend->frontend
write sites, only the texture family reaches a pushed value. Every path
through them funnels into TextureState::BumpSamplingResolutionGeneration
(SamplerObject::BumpVersion for the sampler setters,
TextureObjectBase::BumpShapeVersion for AllocateStorage / SetInternalFormat /
TruncateMipmapLevels / SetSamples / SetFixedSampleLocations),
BumpTextureBindGeneration (a default texture becoming defined, delete-unbind,
a unit's sampler object changing) or NoteUnitTouched (which also moves the
touched-unit high-water mark), so the notice sits on the counters rather than
on each writer and covers the whole family including writers added later.
The buffer, program and VAO writes reach no pushed field: their objects are
read back through O-class live references, not copied values.
|
||
|
|
80a6b39003 |
[Fix] (Pipe): fill the capability set on a texture op and a dispatch, and the shader blit's viewport, provoking vertex and buffer bindings
- The verify retrace aborted four cases with Fatal{UnmigratedPipeInput,
"IsCapabilityEnabled@GenerateMipmap"} (x3) and "@DispatchCompute" (x1):
Magma materialises a texture's queued clear inside both verbs
(GenerateMipmap -> MaterializePendingClearForTexture,
DispatchCompute -> PrepareStorageImageTextures -> the same), and the clear
pre-compensates its colour against GL_FRAMEBUFFER_SRGB in
VkClearManager::PreCompensateSrgbClearColor. Neither class named the field.
- Audited every class the same way rather than stopping at those two rows. Two
more helper-program draws sit inside verbs whose class did not name what they
read: GenerateMipmap takes GenerateDepthMipmapWithShader for a depth texture
and BlitFramebuffer takes TryBlitToDefaultFramebufferWithShader for the
default framebuffer. Both bind their helper's descriptors through
BindProgramUniformBuffers, whose sampler resolver reads the draw framebuffer
for its feedback-loop check and whose buffer-block resolvers read the frontend
binding points; the blit additionally sets the dynamic viewport through
ApplyGLViewportState -> ComputeGLViewport and picks its pipeline's provoking
vertex through GetOrCreateBlitPipeline -> SelectProvokingVertexMode.
- kTextureOp gains IsCapabilityEnabled, GetFramebufferBindingSlot and
GetBufferBindingPoint; kDispatch gains IsCapabilityEnabled; kBlitOrCopy gains
GetViewportIndexed, GetDepthRangeIndexed, GetProvokingVertexMode and
GetBufferBindingPoint. Every row carries the path it was derived from.
- Also unfolds the kReadback transform-feedback rows
|
||
|
|
97b997d5da |
[CI] (Pipe): read the verify library's static symtab, grep only the arming lane's log, and stop the two retrace lanes overwriting each other's evidence
- 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` |
||
|
|
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
|
||
|
|
72aa9191b4 |
[Fix] (Tooling): gate symbol_report on all four buckets and on a .text that moved in either direction
- G1 is spelled "added == removed == resized == renamed == 0, .text delta 0", but gate_failures() took only added and removed and fired on text_delta > N: a pull build whose .text SHRANK, or whose functions were resized with zero net delta - the exact shape a null-guard or ternary rewrite produces - walked through `--threshold 0 --fail-on-symbol-set-change --fail-on-added-bytes 0` green - --fail-on-symbol-set-change now covers the four buckets the report prints, and --fail-on-added-bytes 0 means byte-identical rather than "did not grow" (a positive budget keeps the one-sided meaning). --fail-on-text-delta is the explicit spelling for a run that wants no byte budget at all - the gates read the threshold-0 buckets whatever --threshold says: --threshold is a report control, and a gate that read the thresholded resize list would have quietly weakened itself the day someone raised it. The run says so in its output - the self-test drives the three shapes that used to pass (a shrunk .text at budget 0, a resize and a rename at zero net delta) from the same canned transcripts |
||
|
|
1e3a74686f |
[Test] (Pipe): give the arming assertion a lane and a log of its own, and stop the poison child calling a sequence it never ran a success
- the arming case read the ambient lane's MOBILEGL_LOG_FILE_PATH, and that log is opened fopen(path, "w") by every process in the lane: with 406 entries per backend and CI running them -j 4, a whole-file read races a neighbour's bring-up, and the file that survives the lane holds only the LAST writer. Every other log-reading scenario in this suite (UnlocatedIoBlocks, the primgen reroute, the point-size demotion) is registered in a filtered lane with its own log for exactly that reason; PipeVerifyArmingScenario.Armed now follows them, in DirectGLES.VerifyArming. / DirectVulkan.VerifyArming., and skips anywhere MGITEST_PIPE_ARMING_LANE is unset - what that can prove is written down where it is asserted: arming is a property of (this library, this environment) and these two processes share both with their ~400 ambient siblings. A per-process census is not available through a shared log, and a comment that claimed one was the reason CI grepped a file that could not answer - the re-exec'd poison child ran RunSequence() and then _exit(0) unconditionally, so a fatal assertion inside it - the shader failing to compile, say - returned before the draw and the glGenerateMipmap and still reported success: WithoutOmissionCompletes, the one green entry negative control B turns red, passed on a child that ran none of the sequence. It now exits HasFailure() ? 1 : 0, and checks glGetError() after the mipmap so a rejected sequence is part of the answer rather than stderr nobody reads |
||
|
|
0fe7bf82d2 |
[CI] (Pipe): the third CI mode - a verify build, its integration and retrace lanes, and the two negative controls as always-on steps
- 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. |
||
|
|
416cd23c28 |
[Feat] (Tooling): give symbol_report.py the two hard gates G1 needs, with the report written first
- --fail-on-added-bytes was a reserved no-op that printed "this run stays
informational"; it now exits non-zero when .text grew past the budget, and
--fail-on-symbol-set-change joins it for the added/removed buckets. Together they
are the spelling of P1's G1 ("the pull build is byte-identical"): --threshold 0
--fail-on-symbol-set-change --fail-on-added-bytes 0.
- Default behaviour is unchanged: with no gate flag the tool prints its report and
exits 0, which is what every existing caller and the informational
monolith-symbol-report job expect.
- A gate fires AFTER the Markdown and JSON are written, never before: the report is
the diagnosis, and a CI job that failed before uploading its artifact is one
nobody can act on.
- The decision lives in a pure gate_failures(), so --self-test drives it from the
same two canned transcripts as the buckets: each flag fires on the canned
add/remove/+100 delta, each stays quiet when it was not asked for, and a
tolerated budget is tolerated. A gate whose only test is a real build is a gate
nobody re-tests.
|
||
|
|
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.
|
||
|
|
bdf05514c3 |
[Test] (Pipe): the integration-verify lanes and their two always-on negative controls
- ARCHITECTURE.md 13.2-(2) asks for a third CI mode, and a third mode whose only evidence is "ctest was green" proves nothing: MOBILEGL_PIPE_VERIFY=1 against a library that never compiled the comparator in is a silent no-op that looks exactly like a clean pass. Six registrations, all under if (MOBILEGL_PIPE_VERIFY) and all labelled integration-verify, make both halves falsifiable - a mis-configured build registers nothing and --no-tests=error reds the lane, and PipeVerifyArmingScenario.Armed fails a lane whose library never printed its arming line. - PipeVerifyArmingScenario.CorruptedFieldIsReported is negative control A (G4): its lane pins MOBILEGL_PIPE_VERIFY_CORRUPT=GetRenderStateParameters with MOBILEGL_PIPE_VERIFY_FATAL=0 so the process survives its own divergence and can read the report back; the CI step that exports the same knob against the ambient lane, where FATAL keeps its default, asserts the other half - the abort. - PoisonOmissionScenario is negative control B (G5), in two cases that cannot share a process because the knob is process-wide: the omitted (verb, field) pair must abort the glGenerateMipmap and NOT the draw before it, and the same sequence with the knob unset must complete with no Fatal at all. - The sequence runs in a fork()+execve() of this same binary rather than a bare fork(): the fixture has already brought a context up, and a bare fork of a process holding a live Vulkan device inherits the driver's mutexes with no threads to release them - measured here as a 120s wedge on DirectVulkan against a clean pass on DirectGLES. The child gets its own MOBILEGL_LOG_FILE_PATH because the library opens its log with fopen(path, "w") and would otherwise truncate the file the parent is about to read. - No ambient Verify. entry names MOBILEGL_PIPE_VERIFY_CORRUPT or MOBILEGL_PIPE_POISON_OMIT in its ENVIRONMENT property, because a property entry overrides the job environment for the names it lists: the two CI negative-control steps export those knobs into the job environment and must reach the processes. Every list appends MGL_ITEST_COMMON_ENV / MGL_ITEST_VULKAN_ENV for the same reason, so the vendor and ICD pinning survives. |
||
|
|
bf8b39a867 |
[Refactor] (Magma): route every frontend read through MGB_CTX - 164 arrow sites sed'd, 49 non-arrow lines converted (43 asserts keep their meaning as MGB_CTX_LIVE); pull build byte-identical
- Every MG_State::pGLContext-> in the seven DirectVulkan TUs becomes MGB_CTX-> (DirectVulkan.cpp 12, BackendObject_DirectVulkan.cpp 2, UniformManager.cpp 14, VkClearManager.cpp 1, VkRenderPassManager.cpp 3, VkTextureManager.cpp 2, VulkanRenderer.cpp 130 occurrences on 127 lines); each TU includes <MG_Pipe/PipeInputsSwitch.h> right after its MG_State/GLState/Core.h include (VkRenderPassManager.cpp after its include block, it never included Core.h). - The 43 MOBILEGL_ASSERT(pGLContext) / (pGLContext != nullptr) lines become MOBILEGL_ASSERT(MGB_CTX_LIVE, ...): identical in every INFO build (the macro is empty there) and still a null-context assert in a DEBUG build. - The six code-bearing guards are like-for-like pointer tests in the pull arm: if (MGB_CTX_LIVE) at the two InvalidateCompileEnv sites, MGB_CTX_LIVE in the XFB query counter condition and the ternary that snapshots the paused-primitive counter, !MGB_CTX_LIVE in BeginXfbCaptureForDraw, MGB_CTX_LIVE && in the provoking-vertex resolve. No semantic rewrite: under push MGB_CTX_LIVE is simply "a context exists", the real meaning of these guards is P2's business. - VertexInputStateFactory.h's comment stops naming pGLContext so purity gate C (grep -rc pGLContext MobileGL/MG_Backend/DirectVulkan) reads 0 in every file. - The 12 SyncPersistentMappedRange and 3 SyncGpuWrites sites of the D10 table are untouched; PipeStats AddCalls literals unchanged. - Proof on the pull build (Release/INFO, LTO off, vs feat/disaggregated@087685d1): symbol_report --threshold 0 -> 27799 symbols, 0 added / 0 removed / 0 resized / 0 renamed, .text 10792579 -> 10792579 (+0); nm --defined-only name set identical; ctest -N name set identical (2334); unit 1466/1466; DirectVulkan integration lane 427/427 on lavapipe (two ArmedWhenTheEnvironmentPinsItOn entries flake under -j 4 exactly as on the baseline and pass serially). The push build compiles and links. |
||
|
|
d4504e30f8 |
[Refactor] (Espryt): route every frontend read through MGB_CTX - 113 arrow sites sed'd, 9 non-arrow lines converted (the fb-slot cache keys on MGB_CTX_IDENTITY); pull build byte-identical
- P1 package B (BRIEF-P1 C.2): the four DirectGLES TUs include <MG_Pipe/PipeInputsSwitch.h> right after
their MG_State/GLState/Core.h include (Managers.cpp after its Managers.h include) and spell every
frontend read MGB_CTX->Accessor(...). In the pull build MGB_CTX is MG_State::pGLContext, so the
code is the tree before this commit token for token; in the push build it is &gPipeInputs, the block
the frontend fills at every verb boundary.
- 113 arrow occurrences on 113 lines went through the mechanical sed (DirectGLES.cpp 91, Managers.cpp 15,
MultiDraw.cpp 5, Utils.cpp 2). The 9 non-arrow lines follow D9: Managers.cpp's five bare/compound
guards and three ternary conditions become MGB_CTX_LIVE (UniquePtr::operator bool spelled out, so the
pull build does not move); DirectGLES.cpp's GetFramebufferBindingSlotFast keys its static cache on
MGB_CTX_IDENTITY (a const void* compare) instead of pGLContext.get().
- The cache refill loop dereferences MGB_CTX once into a local reference and reads the slots through it,
instead of &MGB_CTX->GetFramebufferBindingSlot(i) per iteration as D9 spells it: a store into
g_fbSlotCache (a pointer) may alias the unique_ptr's own pointer under clang's TBAA, so the per-iteration
spelling re-reads pGLContext inside the loop and grows the three functions the loop is inlined into
(SyncCurrentProgram +16, ForceBindCurrentFBO +9, BlitNamedFramebuffer +1; .text +32). Hoisting the
dereference restores the single load and a zero .text delta.
- grep -rc pGLContext MobileGL/MG_Backend/DirectGLES reports 0 in every file; symbol_report --threshold 0
against the
|
||
|
|
9087f13308 |
[Fix] (Pipe): let a readback fill the transform-feedback state its emulation reads
- the depth/stencil read emulation opens a ScopedEmulationDrawState that pauses an active
capture around its own draw, so ReadPixels/GetTexImage read IsTransformFeedbackActive and
IsTransformFeedbackPaused; without the two kReadback rows every emulated readback aborts
with Fatal{UnmigratedPipeInput, "IsTransformFeedbackActive@ReadPixels"} once the Espryt
sites are converted
|
||
|
|
44ffafb2dc |
[Test] (Pipe): pin the pre-fill window - every stamp of 0 is stale at serial 0, a read before any fill aborts naming "<none>", and the FATAL=0 child leaves through std::exit so the teardown summary is observed
- PipeCatalogue.PipeInputFieldsStartUnfilled asserted "unfilled" only after setting the serial to 1 by hand, stepping around the serial-0 window; it now asserts every field stale on a value-initialised state first (sticky fields included).
- PipeInputsTest.ReadingBeforeAnyFillAbortsNamingNoVerb: a live context, no fill, gPipeInputs.GetLineWidth() in a forked child; the parent expects SIGABRT, exactly Fatal{UnmigratedPipeInput, "GetLineWidth@<none>"}, no PipeVerifyDiffer and no arming line. In a verify build the child sets Features.PipeVerify first, the lane's shape.
- VerifyFatalOffLogsTheDivergenceAndContinues: the child _exit(0)ed, so VerifyState's destructor never ran and the "verify summary" line was covered only by a lane run; std::exit(0) runs it, and the parent now asserts "2 divergence(s) survived MOBILEGL_PIPE_VERIFY_FATAL=0".
|
||
|
|
12b57055b7 |
[Docs] (Pipe): record why the forwarders carry no poison check, the InHook re-entry guard, and the Index slot no FillPoints.def row can fill
- The seven F-class forwarders are the declared exception to D4's "every accessor body": a forward is a live call, not a stored value, and InvalidateCompileEnv is reached from backend initialisation before any verb has filled, where a check would be Fatal{...@<none>} on every start. Their sticky stamp is consulted by no accessor; the tests pin it through MGPipeInputFieldIsFresh directly. Written down so P2's tracker does not "fix" the missing check.
- MGPipeVerifyReadHook: InHook is what keeps a GetProgramForDraw-triggered backend re-entry from recursing into a second hook, and the whole-field re-read is a per-read cost to keep in mind when reading the verify lane's wall time.
- GetBufferBindingSlot(Index) is polymorphic on GLContext (the bound VAO's element-buffer slot) and null in the block by design: a push Fatal there is not a missing row. No backend reads it today.
|
||
|
|
d0ff647581 |
[Fix] (Pipe): re-arm the verify comparator when any of its three knobs changes, not only when PipeVerify does
- ArmVerify latched PipeVerifyFatal and PipeVerifyCorrupt at the moment PipeVerify changed; a later change to either without a PipeVerify toggle was not seen, so 878db2c4's "re-parse when their value changes" held for one knob of three. - The latch now keys on all three Features values. A lane loads Features before its first fill, so it still arms once per process; cost is two Bool compares and one String compare per fill, verify builds only. |
||
|
|
30d72c5b4e |
[Fix] (Pipe): refuse a stamp of 0 as fresh on both branches - a read before the first fill is Fatal{UnmigratedPipeInput, "<Field>@<none>"}, not default storage
- MGPipeInputFieldIsFresh (gen_pipe.py gen_filled, emitted into PipeFilled.inc) compared FilledGen == CurrentVerbSerial for a non-sticky field; before the first MGPipeFillForVerb both are 0, so 55 of the 63 fields read as fresh and served their default-constructed storage silently, with no log line and no abort. Only the four raw-pointer O-class accessors tripped, through their null-base checks.
- D6 says the serial starts at 1 so that FilledGen == 0 means never filled, and names the window "<Field>@<none>"; the predicate now refuses a stamp of 0 before consulting the sticky branch or the serial, so the window is the poison's case as documented. This is the window E's risk table expects the verify lane to find (an init-time read, the first link, anything reached from eglMakeCurrent).
- Reproduced with the reviewer's pre-fill program (a live context with line width 7, no fill, gPipeInputs.GetLineWidth()): stored=0, rc=0 before; rc=134 with exactly Fatal{UnmigratedPipeInput, "GetLineWidth@<none>"} after, with and without Features.PipeVerify set first.
- The compare-at-read hook arms at the first fill and cannot see this window either; a static_assert next to it pins that a verify build always carries the poison, which is what covers the reads before arming.
|
||
|
|
d9f4698d98 |
[Test] (Pipe): give every PipeInputsTest process its own log file, and cover the compare-at-read arm, the three knob parsers and VERIFY_FATAL=0 with forked children
- gtest_discover_tests runs each case as its own process; under ctest -j the five shared one fixed log path, each main() unlinked it and each fork parent re-read it by path, so a sibling's Fatal line or unlink landed in another case's assertion (red 40/40 at -j 8, for a reason unrelated to the poison). The name now carries the pid and the file is removed on the way out; a forked child inherits the path on purpose.
- MutatedFieldIsNamedAtRead: the first falsifier of MGPipeVerifyReadHook - the child arms verify, fills DrawArrays, reads GetLineWidth (completes), mutates the live context's line width and reads again, and the parent expects SIGABRT with Fatal{PipeVerifyDiffer, "GetLineWidth@DrawArrays", verb=<serial>, where=read} and no where=entry.
- PoisonOmitKnobArmsTheOmission / BadPoisonOmitKnobIsFatalNamingTheKnob / VerifyCorruptKnobNamesTheFieldAtEntry / BadVerifyCorruptKnobIsFatalNamingTheKnob / VerifyFatalOffLogsTheDivergenceAndContinues: the knob parsers through MG_Config::Features, their arming lines, the exact Fatal{PipeVerifyBadKnob} text, and a FATAL=0 run that logs two divergences at consecutive serials and exits 0.
- OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale now loops every field: fresh iff in kTextureOp's mask and not the omitted one, so the name is true.
- The fork/waitpid/log-delta shape is one RunInChild helper; every case is still a visible skip where its switch is off.
|
||
|
|
878db2c405 |
[Fix] (Pipe): re-parse the POISON_OMIT and VERIFY knobs when their Features value changes, not once per process
- The two parsers (ParsePoisonOmissionKnob, ArmVerify) latched on the first fill, so the only way to reach them was a lane that loads MG_Config::Features before any fill; no unit test could exercise the parse, the arming lines or Fatal{PipeVerifyBadKnob}.
- The latch now keys on the value: a lane still parses once (Features is loaded before the first fill), while a forked test child that sets Features after its parent filled gets its own parse. An empty omission value never clears an omission armed through MGPipeSetPoisonOmission.
- Re-arming resets the CORRUPT field so a stale corruption cannot outlive the knob that named it.
|
||
|
|
77ecde1524 |
[Fix] (Pipe): refuse an eighth sticky row at compile time - the forwarded count equals the generated sticky count
- kMGPipeForwardedFieldCount = 7 was a hand-written twin of the generated kMGPipeInputStickyFieldCount, tied only by PipeCatalogue.StickyFieldsAreExactlyTheSeven; a static_assert in the header makes a PipeFields.def sticky row without a forwarder a build error instead of a test failure. |
||
|
|
510ecd9293 |
[Fix] (Impl): move the DeleteSync fill of the orphan sweep inside its null-entry guard
- D7 says a verb whose table entry is null never bumps the serial; the sweep's fill sat before `if (backendDeleteSync && syncObject->backendHandle)`, so a backend without DeleteSync, or a sync without a backend handle, bumped once per orphan with nothing reading the fill. - The sibling sweep in GL_Query.cpp (DeleteBackendQuery) already fills inside its guard; this makes the two the same shape and leaves the declared list of guarded-expression sites at nine. - Pull build unchanged: MGP_FILL is ((void)0) there. |
||
|
|
440d3c5253 |
[Test] (Pipe): pin the P1 poison and verify shapes - 63 fields, 69 verbs, the seven sticky fields, an omitted GenerateMipmap field leaves exactly that field stale, a corrupted snapshot names its field, reading an unfilled field aborts with SIGABRT
- PipeCatalogueTest (header-only, every build): VerbTableIsTheFunctionTable (69 verbs, 9 non-empty classes, the seven sticky bits in every class mask, D7's edges), StickyFieldsAreExactlyTheSeven, FloatVectorsCompareBitwise (a NaN FloatVec4 equals itself, -0.0f differs from 0.0f, a differing BlendStates[3] names RenderState then BlendStates), SixValueStructsHaveFieldLists (69 payloads, PixelStoreParameters and MGHostSpan compared member-wise with Pad0 ignored). - PipeInputsTest, a new unit target linking the static library with its own main() that points MOBILEGL_LOG_FILE_PATH at a temp file: a fake GLContext, the real filler and accessors. OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale (G5 layer 1), ReadingAnOmittedFieldAbortsNamingTheVerb (G5 layer 2: fork, the child reads the omitted field after a draw and a sibling read that must not abort, the parent expects SIGABRT and the exact Fatal line and no @DrawArrays), ReadingAFilledFieldCompletes (the sibling without the omission: _exit(0), no Fatal), CorruptedSnapshotFieldIsNamedWithItsSerial (G4 at block level: clean compare true, a corrupted GetRenderStateParameters is named, its stamp is the fill serial, a corruption outside the mask is not seen), EveryVerbFillsItsClassAndNothingElse (after each of the 69 fills a field is fresh iff its class bit is set). - Every PipeInputsTest case is a visible GTEST_SKIP in a pull build and the poison/verify cases skip in a push build without them; the ctest name set is the same in all three configurations (additions only: 9 names). |
||
|
|
83b16561c2 |
[Feat] (Pipe): give the six value structs G4 field lists and assert the lists cover their members - the memcmp fallback is now a compile error, floats in vector types compare bitwise
- PipeFields.def gains MGP_FIELDS_RenderStateParameters (65 members), PixelStoreParameters (8), PerBufferBlendState (7), StencilFaceState (7), DynamicBackendParameters (85) and MGHostSpan (Ptr, Seg, Size, Offset), all appended to MGP_VERIFY_PAYLOAD_LIST: kMGPipeVerifiedPayloadCount 63 -> 69, and ResidualValueBlock / MGPPixelPackState / MGPCaps are now compared field by field all the way down.
- gen_verify: MEMCMP_FALLBACK_TYPES is empty and the generic MGPipeFieldEqual's last branch is static_assert(sizeof(T) == 0) - a struct without a field list is a compile error, not a padding false positive; Array<T, N> gets an element-wise overload, and a VecBase-derived vector (FloatVec4, IntVec4, BoolVec4...) is detected by a probe and compared bitwise over its data, because VecBase::operator== is IEEE == and a derived-to-base overload would lose resolution to the exact-match generic template.
- check_field_lists_cover_struct_members(): for every payload in MGP_VERIFY_PAYLOAD_LIST, parse `struct <Name> {` out of MGPipeTypes.h / MGPipeValueTypes.h / MGPipeHostSpan.h / BackendObject.h (comments and strings masked, statics, functions, nested types and Pad<n> members excluded) and refuse a member without an F(...) or an F(...) that is not a member; runs in both modes, so it is a pipe-gates gate.
- scan_live_accessors(): every MGB_CTX-> / pGLContext-> read under MG_Backend must have a Coverage.def row (rows nobody reads are printed: today only the dead GetBoundTransformFeedbackName).
- --self-test: six negative controls (struct member without F, F without member, payload without struct, verb missing from FillPoints.def, verb outside GLFunctionsTable, field row naming a non-accessor) that must each trip, plus a positive control; zero trips is itself an error.
|
||
|
|
275dd3edb4 |
[Feat] (Pipe): the MOBILEGL_PIPE_VERIFY shadow comparator - a per-verb entry compare over the fill set and a compare-at-read in every accessor, first differing field and verb serial, fatal by default
- Entry compare: at the end of MGPipeFillForVerb a file-static second PipeInputs is filled by SnapshotFromGLContext (the branch that survives P13) over the same class mask, MOBILEGL_PIPE_VERIFY_CORRUPT perturbs one field of that snapshot arm, and MGPipeVerifyInputs compares every field in the mask through MGPipeInputsFieldEqual (V by G4's MGPipeFieldEqual, O by identity, F equal by definition). Both arms come from the same context at the same instant, so this arm is tautological until P2 - the CORRUPT knob keeps it falsifiable.
- Compare-at-read: MGP_INPUT_VERIFY_READ now calls MGPipeVerifyReadHook(*this, field, i0, i1), which re-reads the whole field from the live context into a scratch block and compares it against the stored value on the live block only - a superset of "the same indices"; the indices decorate the report. This is the arm that is real in P1.
- Reporting per D8: MGLOG_F("MGPipe: Fatal{PipeVerifyDiffer, \"Field@Verb\", verb=<serial>, where=entry|read}") then abort unless MOBILEGL_PIPE_VERIFY_FATAL=0, which counts and summarises at teardown with MGLOG_E; arming logs "MGPipe: verify armed - 63 fields, 69 verbs, fatal=N" once, the knobs acknowledge themselves, an unknown field name is Fatal{PipeVerifyBadKnob}; a push build without the comparator answers MOBILEGL_PIPE_VERIFY=1 with one MGLOG_W_ONCE.
- MGPipeVerifyInputs carries default visibility so the retrace-verify job's nm -D probe can prove the verify library was the one swapped in; pointer corruption flips low bits instead of nulling (a null pointer already null was invisible to the compare), a SharedPtr becomes an aliasing pointer with no control block; CurrentVertexAttributeValue gets its own bitwise equality.
|
||
|
|
a196ada4c1 |
[Feat] (Impl): call MGP_FILL before every GLFunctionsTable entry - 83 statements over 69 verbs, a no-op in the pull build
- Every call through gBackendFunctionsTable.GL in the seven MG_Impl TUs (Drawing 36, Framebuffer 11, Texture 8, Getter 3, Program 1, Query 18, Sync 6) is preceded by MGP_FILL(<Verb>); placed after every early return the call sits behind - the conditional-render check, the null-entry guards, the loop bodies - so a verb whose entry is null on this backend never bumps the serial. - Seven calls sit on a continuation line of a guarded expression (GetQueryResult64 x2, BeginXfbPrimitivesQuery, BeginTimeElapsedQuery, QueryCounterTimestamp, IsTimerQuerySupported, GetSyncStatus) and two more fold the null guard into the same expression (IsQueryResultAvailable, ClientWaitSync's guarded return); there the fill precedes the statement, so a null entry bumps the serial once with nothing to read it - harmless for the poison, recorded for the record. - Each TU includes <MG_Impl/Pipe/PipeFill.h> after its last MG_State/MG_Backend include; under MOBILEGL_PIPE_PUSH=OFF the macro is ((void)0) and the pull library is symbol-identical with a .text delta of zero. |
||
|
|
3aa4d8af1f |
[Feat] (Pipe): fill PipeInputs per verb class from GLContext and stamp per-verb generations - a read of a field the verb did not fill is Fatal{UnmigratedPipeInput}
- MGPipeFillForVerb now walks kMGPipeClassFieldMask[kMGPipeVerbClass[verb]] and copies every field in it by calling the GLContext accessor of the same name (MGPipeFillAccess::CopyField, one switch over the 56 stored fields; the seven forwarded fields copy nothing), stamping each with the new serial; the sticky seven get FilledGen = 1 on the first live fill through the same mask walk, since every class mask carries them.
- MOBILEGL_PIPE_POISON_OMIT (<Verb>:<FieldName>) is parsed once on the first fill and MGPipeSetPoisonOmission is the programmatic form for the unit tests; the omitted pair keeps its value copy and loses only its stamp, so the omission is indistinguishable from a forgotten FillPoints.def row. An unknown name is Fatal{PipeVerifyBadKnob}; a non-poison push build acknowledges the knob with one warning because no stamp exists to omit.
- PipeInputs names one friend, struct MGPipeFillAccess, instead of two friend functions, and VisitStorage gains a const overload for the comparator that follows.
|
||
|
|
bf86b1ede6 |
[Feat] (Pipe): land the P1 contract - PipeInputsSwitch.h with MGB_CTX, the 63-field PipeInputs block with type-identical accessors, FillPoints.def and its G5b generator, the MOBILEGL_PIPE_PUSH/VERIFY options and the three verify knobs; pull build unchanged
- MG_Pipe/PipeInputsSwitch.h is the strangler switch (ARCHITECTURE.md 9.2): MGB_CTX is the
live GLContext in the pull build and &gPipeInputs under MOBILEGL_PIPE_PUSH, so the pull
arm's pGLContext spelling stays outside MG_Backend/ and purity gate C's grep.
- MG_Backend/MGPipe/PipeInputs.h holds one struct with every accessor a backend reads (63:
the 61 Coverage.def rows plus GetBoundTransformFeedbackLifetimeId and
HasOpenTransformFeedbackSpan), each keeping its GLContext name, parameters and return
type so the site conversion is type-neutral; V fields are copied values, O fields are
SharedPtr copies or pointers into the context, the seven F fields forward to the live
context from MG_Impl/Pipe/PipeFill.cpp and are the only sticky ones (Coverage.def's
MGP_COVERAGE_STICKY_LIST argues each: argument-keyed lookups and reverse-channel writes,
never a version or generation counter).
- MG_Pipe/FillPoints.def is the verb table: one row per GLFunctionsTable function pointer in
declaration order (69), nine classes and the may-read field rows; gen_pipe.py parses the
struct and refuses a row set that is not exactly its member set, then emits
generated/PipeFillPoints.inc (verb enum, class tables, per-class field masks with the
sticky fields OR'ed in). MGP_FILL(Verb) in MG_Impl/Pipe/PipeFill.h is the fill point;
MGPipeFillForVerb only bumps the serial, records the verb and stamps the sticky fields
here - the per-class copies land in the next commit, the fill points in MG_Impl after.
- MOBILEGL_PIPE_POISON is derived once in PipeInputs.h from MOBILEGL_PIPE_PUSH and the DEBUG
level, MOBILEGL_BUILD_DISAGGREGATED or MOBILEGL_PIPE_VERIFY (the tree has no
MOBILEGL_DEBUG); under it every accessor is a read-side freshness check that aborts with
Fatal{UnmigratedPipeInput, "Field@Verb"}.
- CMake: MOBILEGL_PIPE_PUSH and MOBILEGL_PIPE_VERIFY options (VERIFY forces PUSH on), the two
new sources appended only under PUSH, the compile definitions; Config.h/ConfigLoader.cpp
gain PipeVerifyFatal / PipeVerifyCorrupt / PipePoisonOmit under #if MOBILEGL_PIPE_PUSH so
the pull build's FeaturesTable does not change size.
- Pull build proof: symbol_report.py against the
|
||
|
|
087685d19b |
[CI] (Purity): install libx11-dev for the include-graph-check job
- 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 |
||
|
|
5635e33ffe |
[Docs] (Disaggregated): record the P0.5 landing
- README status, the ROADMAP P0.5 row (measured outcome, the 8-includer correction, the DynamicBackendParameters exception) and the ARCHITECTURE note on which header gate A asserts |
||
|
|
5d99ee435f |
[CI] (Purity): require both P0.5 closure probes now that the headers exist
- the ctest and the include-graph-check job pass --require-all, so a SKIP on MGPipeValueTypes.h or ProgramArtifacts.h is red from here on |
||
|
|
b566bf4db9 |
[Fix] (Purity, Program, Pipe): close the review minors of the three P0.5 packages
- check_include_closure.py keeps the directory of a two-token -isystem, makes --require-all fail on a missing required header even when --probe narrowed the run, and removes its temp dir at exit - ProgramArtifactsTest follows whichever STL branch the header pinned (#ifdef the size macro) instead of re-spelling the libstdc++ condition, and loses its stray executable bit - MGPipeTypes.h's debt comment says what its closure still reaches (TextureEnum.h via BackendObject.h), which is why gate A asserts MGPipeValueTypes.h and not this header |
||
|
|
09d2bb11b7 |
[Feat] (Program): add the reflection-archive field tables and sizeof trip wires to ProgramArtifacts.h
- One VisitFields table per type (ARCHITECTURE.md:259), a free constrained template so the moved struct bodies stay verbatim and one table serves both the const (serialize) and non-const (deserialize) direction. LinkArtifacts::program is deliberately absent: it is null for every archived instance and must never be serialized. - Trip wires: TypeFacts is pinned at 44 bytes on every ABI; the container-bearing four are pinned per standard library - libstdc++ 64-bit here (128/128/1056/88, measured on this build), the libc++ branch left inert for the integrator to pin from the NDK build, MSVC unasserted. A member added without a table entry changes the size and the assertion message sends the author to the table. - ProgramArtifactsTest counts the tables (20/14/11/57/8; const and non-const walks agree, names distinct), proves constness passes through, and records every sizeof as a ctest property so a new toolchain's numbers are readable from any `ctest -V` log. |
||
|
|
fee3902472 |
[Refactor] (Program): stop ProgramObject.h including SpvcSession.h - it names no spirv-cross or SPIRV-Reflect symbol
- The include line was the header's only match for spvc_*/SpvReflect*/SpvcMetadata/ SpvcSession; it only ever forwarded <spirv_reflect.h> and the session type to the eight includers, every one of which builds without it (no consumer needed a direct include added). - One less transpiler header behind ProgramObject.h, on the way to a ProgramArtifacts.h closure that stays clear of MG_Util/ShaderTranspiler/ (P0.5 gate A). |
||
|
|
da249f30e2 |
[Refactor] (Program): extract ProgramArtifacts.h - move TypeFacts, ResourceReflection, XfbVarying, LinkArtifacts and SpirvArtifacts to namespace scope with in-class aliases so every existing spelling compiles unchanged; pure move
- P0.5 of the MGPipe disaggregation (ROADMAP P0.5, ARCHITECTURE.md:260): the five reflection types a link produces now live in a header that includes only <Includes.h> and <set>, so a future server-side consumer can name them without dragging ShaderObject.h / SpvcSession.h / the transpiler behind it. - Struct bodies move verbatim, comments included, re-indented one level; no field is added, removed, reordered or re-typed. The two glslang-typed members (LinkArtifacts::program, uniformInitialValues) stay as they are (B.0 D5); the comment mentions of glslang types are respelled without the scope token so the include-closure gate's "glslang:: exactly twice" limit holds. - kInvalidUniformOffset moves to namespace scope (SpirvArtifacts defaults to it); ProgramObject::kInvalidUniformOffset is defined from it, so the two cannot drift. - ProgramObject keeps in-class aliases (fully qualified on the right-hand side) for all nine names, so none of the 8 includers nor any ProgramObject::X spelling changes. - ProgramArtifactsTest pins the aliases as the same types (is_same_v) and TypeFacts as a 44-byte POD; its first include is the new header, so it is also the proof that the header is self-contained. |
||
|
|
8566a288f8 |
[Refactor] (Pipe, State): extract MGPipeValueTypes.h - move the render-state, sampler and vertex value types and their enums out of MG_State/GLState so MG_Pipe no longer reaches RenderState.h; pure move, member order and namespaces unchanged
- ROADMAP P0.5 / ARCHITECTURE.md value-header manifest: MGPipeTypes.h embedded RenderStateParameters and PixelStoreParameters through RenderState.h, which drags FramebufferObject.h and the whole texture/renderbuffer/sampler chain into MG_Pipe; purity gate A (no MG_State/MG_Impl/MG_Backend/MG_Remote in the closure) could not be armed for anything in MG_Pipe while that include existed. - MGPipeValueTypes.h is a verbatim cut, comments included: the eleven RenderState.h enums (all of them - a split would be the maintenance trap), PixelStoreParameters, PerBufferBlendState, StencilFaceState, RenderStateParameters (member order untouched: DirectGLES' offsetof spans and PipeSpanTable.inc name the members), the six SamplerObject.h enums and SamplerParameters (BorderColorForm stays Uint8, it sets the tail padding), and VertexAttribute / VertexBufferBindingPoint / VertexAttributeVersion, which keep namespace MobileGL::MG_State::GLState with a forward-declared BufferObject so no mangled name changes. - MAX_DRAW_BUFFERS becomes inline constexpr kMGMaxDrawBuffers in namespace MobileGL and FramebufferObject::MAX_DRAW_BUFFERS is defined from it, so the eighty existing spellings keep working and the two cannot drift. No other constant is added. - The four MG_State headers become forwarders (include the value header, keep their class definitions); RenderState.cpp gains a direct FramebufferObject.h include because it spells FramebufferObject::MAX_DRAW_BUFFERS and only ever got that header transitively. No other TU lost a transitive include: the full build (Release, clang, tests + integration tests) passed without touching anything under MG_Backend, MG_Impl or MG_Util. - DynamicBackendParameters does NOT move (SizeT members and TextureTarget-taking member functions make that a type change, not a move); MGPipeTypes.h keeps its BackendObject.h include and the debt comment now says so, which is why gate A asserts MGPipeValueTypes.h rather than MGPipeTypes.h. - New trip wires in the header: trivially-copyable + exact sizeof for PixelStoreParameters/PerBufferBlendState/StencilFaceState (28), SamplerParameters (100), VertexAttributeVersion (6), RenderStateParameters (1168, standard layout, BlendStates before LogicOp, BlendStates sized by kMGMaxDrawBuffers). Their runtime twins ValueTypeLayoutsArePinned and the carrier check ResidualBlockIsExactlyItsTwoValueStructsPlusPatchTail (Pack at 1168, CapabilityBits at 1200) are added to PipeCatalogueTest without a new include. - gen_pipe.py's "field lists of their own in P0.5" comment now says P1 (the comparator needs std::array<struct> support first); PipeVerify.inc regenerated. - Verified: ctest -L unit 1460/1460 and -L integration-gpu green; ctest -N names a superset of feat/disaggregated@6672778b (two added, none lost); one definition per moved type; the -H closure of the new header contains no MG_State/MG_Backend/ MG_Impl/MG_Remote header and the header compiles alone; nm --defined-only -S against the base libMobileGL.so: 0 added / 0 removed / 0 resized, .text byte-identical, the only differing bytes are the build-id and two stamp strings. |
||
|
|
2318f6ae44 |
[Tooling] (Purity): add scripts/symbol_report.py - per-symbol nm/.text attribution between two libMobileGL.so builds
- P0.5's acceptance gate requires every `nm --defined-only -S` delta to be explainable per symbol, and nothing in the tree reads nm or size today. - The problem the tool exists to solve: de-nesting a type renames every mangled name that mentions it, including inside template arguments, so a raw nm diff of a pure move looks catastrophic. --strip-scope 'A::B::C::' rewrites 'A::B::C::X' to 'A::B::X' on the demangled name before comparing, which folds those into a renamed-only bucket - same normalised name, byte-identical size - and leaves the real churn visible. - Buckets sorted by |delta|: removed, added, resized, renamed-only, unchanged, plus .text/.data/.bss/Total from `size --format=sysv`; --only-names narrows the listing, --markdown/--json write the report the integrator pastes into the merge commit. - Always exits 0 (this is informational, ARCHITECTURE.md:507); --fail-on-added-bytes is accepted and documented as reserved for the day it becomes a hard gate. - The docstring carries the guard rails a reader would otherwise supply by assumption: same CMAKE_BUILD_TYPE (the visibility presets differ per configuration), LTO off on both sides, same compiler - and every report prints both paths and their byte sizes. - --self-test runs two canned nm/size transcripts through the same parser and bucketer and pins all five buckets, including that a de-nested member folds to renamed rather than to an added+removed pair. |
||
|
|
fe3dc1dde8 |
[CI] (Purity): add scripts/check_include_closure.py - the -H include-closure gate for the P0.5 headers with an always-on negative control, wired as a unit ctest and the include-graph-check job
- 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. |
||
|
|
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 |
||
|
|
6e0e3df372 |
[Docs] (Disaggregated): point the history note at commit hashes only
- the retired drafts stay in git history; the README no longer names or characterises them |
||
|
|
bee22f9d26 |
[Docs] (Disaggregated): separate the dirty-surface totals from the immediate-publish subset in MEASUREMENTS
- the sentence read as if 836 of the 92 immediate calls were RecordError; 836 is RecordError's share of all 926 calls, and most of the 92 immediate ones are RecordError |
||
|
|
8952b14024 |
[Docs] (Disaggregated): rewrite the MGPipe plan into a design and architecture set - README, ARCHITECTURE, ROADMAP, MEASUREMENTS - and retire PLAN.md and REVIEW.md to git history
- README.md: what MGPipe is, the one-paragraph architecture, the P0 status line, the file and code map, and the commit range where the design competition and the three adversarial review rounds live
- ARCHITECTURE.md: the design as decided, one reason per decision - handles and generations, the 71-call catalogue by class with flags and cap bits, record and payload conventions, the tracker, texture subdata and dirty ownership, shader state and the P0.5 header extraction, the reverse channel, the backend strangler, the server side and the index host mirror, the transport as landed in MG_Remote, the persistent-map tiers as measured, roundtrips, present and threads, process and platform delivery, build shapes and purity gates, the five-part verification gate, and the knob tables marked landed versus planned
- ROADMAP.md: P0..P13 as one table of what lands, the gate and the dependency, the two tracks and milestones, the day-43 GO/NO-GO checklist with both exits, the re-baseline checkpoints, and the questions still open after P0
- MEASUREMENTS.md: spike A on both devices, the spike B tier matrix, the four-trace boundary-counter baselines on both backends, the desktop and corpus facts, and the harness traps with the exact commands
- every file:line kept is verified at
|
||
|
|
458ccde176 |
[Feat] (Metrics, Config): make the boundary-counter summary cadence a knob, because the device harness never reaches the teardown dump
- MOBILEGL_PIPE_STATS_PERIOD (default 120, clamped to [1, 1000000]) sets the frames per summary line; Init() latches it and a zero falls back to the default - the trace APK's replay never tears MobileGL down, so MOBILEGL_PIPE_STATS_FILE never fires on device and a fixture shorter than the period (create-indirect) reported nothing - PipeStatsTest pins the latch and the zero fallback |