mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 20:28:32 +09:00
c574043c1330c6b2ab137245f3062f8fc35c8f4c
26
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c574043c13 |
[Fix] (Pipe): derive the render-state answers of the dirty-surface map from RenderState.cpp instead of believing them, and correct the two rows that named a publisher which does not always fire
- SetCapability named NEW_PIPELINE_STATE, but its ClipDistance0..7 arms write ClipDistanceEnabledMask (dynamic chunk D7) and deliberately do not BumpVersions, so that publisher does not fire at all for glEnable(GL_CLIP_DISTANCE0); SetStencilFunc named it too, while ++m_pipelineStateVersion there is conditional on Func moving, so a ref-only glStencilFunc does not move it either. Both are now NEW_RENDER_STATE, the answer that holds on every path - a row may now carry several publishers joined with '|', which is what lets the 18 setters that call BumpVersions on every path state both counters, and the patch trio state its own bit and the two render counters it also moves - --check no longer validates only row existence and answer vocabulary: it reads RenderState.cpp, derives per setter which of the two counters moves on EVERY path (BumpVersions moves both, a bare ++m_version only the first, a setter with both kinds of path only the first, a delegating setter inherits its callee's) and fails when a row claims a publisher that under-fires or omits one that always fires - two new self-test negative controls, one per direction, both built from the defects that were actually in the file - MarkProgram/MarkProgramPipeline/MarkShaderForDeletion answered kExplicitDestroy, a mechanism D13 does not build for them: Espryt 0b's explicit destroy is scoped to six object kinds that exclude programs, pipelines and shaders. They answer kUnpublishedDestroy now - a recorded hole rather than a mechanism that does not exist |
||
|
|
3302ee82b5 |
[Feat] (Pipe): map every frontend mutator onto the aggregate generation that publishes it, and make the dirty-surface scanner a gate
- MG_Pipe/DirtySurface.def: 73 rows, one per distinct mutator the scanner finds, each answering
"what publishes this". The answer vocabulary is a MGPipeDirty bit name or one of five
non-bit answers, and each of the five is documented in the file's header rather than left to
be inferred: kImmediate, kReverseChannel, kNoBackendRead, kExplicitDestroy and
kPulledEveryVerb. Where a mutator has more than one true answer the row carries the COARSER
one - the one that cannot under-fire.
- gen_pipe_dirty_surface.py --check is the gate and it fails in BOTH directions: an unmapped
mutator renders stale, and a row naming a mutator the scan no longer finds keeps a real hole
looking covered. It also rejects an answer that is neither a documented non-bit answer nor a
bit name read out of Tracker.h's own kMGPipeDirtyNames, so a renamed bit cannot leave a row
silently pointing at nothing.
- --self-test runs three canned negative controls - a withheld mutator, a stale row, a bad
answer - and each must trip; trips == 0 is itself an error, the shape
check_include_closure.py and gen_pipe.py --self-test already use. ROADMAP.md's rule is that
every gate must be able to go red for the reason it exists.
- --summary keeps working unchanged, because the CI file that still calls it belongs to
another package until it lands.
- The human report prints the mapped answer where it printed UNMAPPED.
- FillPoints.def: the verdict on the eight statically over-approximated rows, recorded per
group in the def's own comment. All eight are KEPT and the reason is the same in all three
groups - each row names a concrete backend path (the depth/stencil read emulation's paused
capture, VkClearManager::PreCompensateSrgbClearColor's GL_FRAMEBUFFER_SRGB read, the shader
blit's viewport / provoking vertex / binding-point reads), and the only evidence that could
retire one is dynamic. A corpus that never reaches a path proves nothing about it, and a row
dropped on that basis turns a rare path into Fatal{UnmigratedPipeInput} in a shipped build.
The contract's new FramebufferSrgb storage in fact makes one of the eight MORE load-bearing
than it was, not less: it used to read a compile-time constant.
|
||
|
|
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.
|
||
|
|
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 |
||
|
|
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 |
||
|
|
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.
|
||
|
|
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.
|
||
|
|
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.
|
||
|
|
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
|
||
|
|
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 |
||
|
|
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. |
||
|
|
901d48a678 |
[Fix] (MGPipe, Metrics, Config, CI): exchange the per-frame stats instead of racing a store, carry the three uncarried table entries, drop the inline host span from a buffer range, spell the buffer subdata range, and close the small gate holes
- 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). |
||
|
|
1154f9a00d |
[Fix] (MG_Remote, Transport): give the inproc doorbell a death state so Shutdown can join a parked waiter, and bound a ring record at half the capacity so a refusal can never look like backpressure
- T-1: InProcessChannel::Close rang each CondVarDoorbell once and claimed that unparks a peer mid-frame. It does not. Doorbell::Wait consumes the one ring, re-tests a condition nothing published, finds the bell alive (CondVarDoorbell never overrode Dead(); Doorbell.cpp had no death state at all) and with kWaitForever parks again for good - so Shutdown could never join a server thread sitting in the design's own steady state (spun, set consumerParked, blocked; plan section 8.1 inheriting the earlier plan's 6.2a). CondVarDoorbell now carries an atomic death latch: Kill() sets it under the mutex and notify_all's, Dead() reports it, Park returns false at once on a dead bell (and the wait predicate includes it, so a Kill cannot slip between the test and the wait), and Close kills both bells instead of ringing them. Same shape as SocketDoorbell's EOF latch; Notify stays the ordinary wakeup. - T-2: RingProducer::Reserve refused only total > capacity, but a record with capacity/2 < total <= capacity is unplaceable at every head offset where neither the space to the wrap boundary nor the space before it holds it - even in an EMPTY ring, because a wrap pad costs spaceToEnd bytes on top of the record. Concretely: head offset 16 of an empty 256-byte ring, a 248-byte record; FreeBytes() says 256, Reserve says nullptr, forever, and a producer waiting for FreeBytes() >= 248 stalls with nothing logged. The bound is now capacity/2, which is exact rather than conservative (worst case 2*total-8 <= capacity-8), exposed as MaxRecordBytes() for the emitter to chunk against; the minimum ring is two headers so the smallest record still fits the bound. Ring.h states Capacity()/2 as the chunking bound and the G3 header comment in gen_pipe.py now states the chunking rule plan section 8.2 asks G3 to define (PipeWire.inc regenerated). - Tests, each shown red with only the fix site reverted and green with it: InProcessTransportTest.ShutdownUnparksAWaiterWithNoDeadline (bounded join through a shared_ptr-owned waiter: 5 s red instead of a hung job; reverted it hangs and fails at 5051 ms), RingTest.RecordLargerThanHalfTheRingIsRefused (reverted, the 248-byte record is accepted), RingTest.RecordPlaceabilityDoesNotDependOnTheHeadOffset (the offset-0 vs offset-16 negative control), RingTest.HalfCapacityRecordFitsAtEveryHeadOffset (the positive half: the maximal record at all 32 head offsets of a 256-byte ring) and RingTest.RejectsARingTooSmallForTheSmallestRecord. |
||
|
|
a1e22c26ab |
[Build] (MG_Remote, Protocol): pin the flatbuffers submodule, add the control-plane schema and commit its generated header
- 3rdparty/flatbuffers submodule pinned to the latest release tag v25.12.19 (7e163021). The runtime is header-only, so only 3rdparty/flatbuffers/include is ever used and no library is linked; CMake never calls add_subdirectory on it and flatc is not in the build graph (plan B section 8.1, inheriting the earlier plan's section 7.1).
- MobileGL/MG_Remote/Protocol/protocol.fbs carries the CONTROL PLANE only: SegmentRef, Hello, Welcome, CapsSnapshot, DefaultFramebufferInfo, SurfaceOp, SurfaceReply, ResyncRequest, ResyncDone, AuxRequest, Fatal, LogLine, union CtrlMsg and the CtrlEnvelope root with a file_identifier. Hot-path records are FlatBuffers structs generated from MG_Pipe/PipeCalls.def in a later package and are deliberately absent here, so record numbering never churns.
- Two deviations from the earlier plan's section 7.1 sketch, both deliberate: (a) ProgramReflection is not a union member - plan B ships program artifacts inside the create_shader_state CSO blob (section 8.2), and union tags are wire values that may only ever be appended, so reserving a tag for a message that may never exist is worse than appending one later; (b) maxComputeWorkGroupCount/Size are vectors, not [int:3] - fixed-size arrays are legal only in FlatBuffers structs, never in tables.
- scripts/gen_protocol.py resolves flatc as MOBILEGL_FLATC_EXECUTABLE, otherwise builds the pinned flatc ONCE into <repo>/../flatc-build (override with MOBILEGL_FLATC_BUILD_DIR), outside the project build graph. A flatc found on PATH is deliberately refused and a version mismatch against the pinned runtime is a hard error: the generated header static_asserts FLATBUFFERS_VERSION, so a stray flatc either fails to compile or churns the committed file on every machine. The earlier branch did the opposite - Protocol/CMakeLists.txt:22-38 add_subdirectory'd the FlatBuffers tree with FLATBUFFERS_BUILD_FLATC=ON whenever MOBILEGL_FLATC_EXECUTABLE was unset, which is exactly the NDK trap it claimed to avoid (cross-compile an arm64 flatc, then run it on the host).
- protocol_generated.h is committed with the project source header prepended by the generator, so regeneration is byte-identical: verified by running gen_protocol.py twice and by perturbing the file and regenerating it back.
- Reuse from Feat/CS-Delta-IPC: MobileGL/Protocol/mg_protocol_base.h, kept as the shared C vocabulary (result codes, byte spans, shm region, id typedefs) and keeping the structSize-first versioning discipline that section 14.2 calls the answer to risk B-R10. Dropped from it: MobileGLObjectKind / MobileGLObjectScope / MobileGLObjectHandle - plan B never puts GL object identity on the wire (the frontend allocates {slot, generation} handles in MG_Pipe, section 4.2.1), so a second identity vocabulary would be a drift surface with no reader. Added MOBILEGL_ERR_BUFFER_TOO_SMALL as an append-only code for the receive contract.
|
||
|
|
9bbf71990c |
[Build] (CI): add the pipe-gen check, the stdio-instrumentation gate and the citation lint
- 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. |
||
|
|
9c773182bb |
[Feat] (MGPipe): land the interface skeleton - the complete call catalogue, the payload PODs and the seven generators
- Plan B section 4 makes the frontend/backend boundary explicit and gives the backend its
own state machine. This is the P0 deliverable of section 11: the whole catalogue exists
from day one, placeholders included, because the wire opcode is a call's position in
PipeCalls.def and record numbering must never churn.
- MG_Pipe/PipeCalls.def is the single source of truth: 68 unique calls as
X(Name, Payload, Class, Flags). Its header reconciles that number with the plan's
headline counts (section 4.4, appendix A), which double count - "CSO 15" names
bind_sampler_states and set_sampler_views that the "set_* 17" list also names, "screen
14" tabulates the query family that section 4.3 assigns to the context, and the
"transfer 12" row enumerates 11 calls. Each reconciliation is written down next to the
count rather than resolved silently.
- MGPipeHandles.h: the 8-byte {slot, gen} pair, dense per-kind slots, the reserved null
and default-framebuffer handles, and the ShaderCso composite band (sections 4.2, 5.6.3).
The two generations are documented as strictly separate, with the interface rule that no
call may require the client to know MGGen.
- MGPipeTypes.h: every payload of section 4.5 as a flat POD with explicit padding, a
trivial-copyability assertion and an exact sizeof assertion, because the wire records are
memcpy'd and a field silently changing width is a protocol break no test would see.
MGPCaps embeds DynamicBackendParameters by inclusion so a caps field added there needs no
second edit here; its assertion is stated as a composition because that struct still
carries SizeT. ResidualValueBlock is pinned at MGL_RESIDUAL_BLOCK_SIZE 1248, the ratchet
that only ever goes down and reaches static_assert(... == 0) in P13 (section 6.3).
- MGPipeHostSpan.h keeps the one shape that changes with the transport isolated behind one
predictable branch, with the kFromServerIndexMirror sentinel D-B7 needs.
- MGPipeCallbacks.h names the reverse channel as ten callbacks plus the forward terminator
in the context table, replacing 95 poke sites across 17 methods (section 7.1).
- scripts/gen_pipe.py runs G1-G7 off those .def files. G1 asserts each table is EXACTLY its
call count of function pointers; G3 pads every wire record to the stream's 8-byte
granularity and checks size >= sizeof && size <= remaining && size % 8 == 0 before
dispatch, fatally; G4 compares field by field (padding excluded, floats by bits) because
a comparator with false positives is one nobody reads - DirectGLES.cpp says the same
thing about its own memcmp of RenderStateParameters; G5 turns the accessor list into
per-verb poison generations rather than a written-once bitmap, which is the only version
that can see a field left over from the previous draw (section 6.2.2); G6 joins the 477
read points of the vendored backend_read_inventory.md against Coverage.def and reports
0 UNMAPPED (299 to a call, 167 signatures that become handle parameters, 6 reverse
channel, 5 client-resolved); G7 pins the pipeline subset BY MEMBER NAME from what
VulkanRenderer::ComputePipelineStateHash hashes today, computing no offsets in python.
- The generated files are committed so the build never depends on python; CI regenerates
and diffs them.
|
||
|
|
5938a18131 | [Chore] (Scripts): Upgrade clang-format | ||
|
|
20fbfc2956 | [Chore] (LICENSE): Switch project license to LGPLv3. | ||
|
|
3714e275ab | [Chore] (All): Update copyright year in source code files. | ||
|
|
e7d9da42dc | [Chore] (All): Adjust copyright declaration. | ||
|
|
8ed8efe347 | [Chore] (All): Add standardized source file header and update script. | ||
|
|
2652a12fdc | [Fix] (Scripts): Ensure full bar at 100% in formatting script. | ||
|
|
ea9fc4dbe8 | [Improvement] (MG_Backend/DirectGLES): Remove unused code. | ||
|
|
69fe996e15 | [Feat] (Scripts): Introduce format_code.sh |