mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
ebd73da06cd8b57f0d448b659d4a058aa4f6dca7
2749
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
770f1a0b3a |
[docs] (P7): ID-P7-26/27 - D2 lands with a rework round, and bsl-esc-menu is a memory defect, not an exclusion
D2's fable review says land with fixes: the TUS gate's scatter workload has a union box that covers the whole level (rect=0 is geometry, not the unpack-ring policy), the memo purity script fails open on a renamed family, and the census numbers were not reproducible - all sent back to D2 as one rework round; the excluded TextureViewAlias case gets its §12 row (owner D3). bsl-esc-menu-854 on the spawn arm: monolith passes at 807 MiB peak RSS, the server process dies at ~1.2 GiB in scudo's secondary allocator with 8 GB of system memory free, inproc dies at 1.6 GiB. That is the wire arm holding about twice the bytes (or the mappings) of the monolith, a disaggregation defect under ID-P7-12's rule, so it stays in the gate-3 denominator; M1 attributes it. |
||
|
|
dd6c541af7 |
[docs] (P7): ID-P7-25 - W4 on p7w4: 34/36 match monolith, and OpenRA is a silent draw drop, not a cold cache
Device window 1 W4 (APK p7w4 = 8c5dd6fc): iterationt x2 pass, the 39-case inproc matrix has 34/36 of the denominator within 0.0005 of the monolith reading, bsl-esc-menu still OOMs under inproc (spawn arm pending). ID-P7-19's cold-driver-cache reading of OpenRA is withdrawn: 26 runs show a ~50 % per-run coin flip that ignores cache temperature, IPC_AUDIT and FIF=1, reproduces on the two-process spawn arm, and only ever yields three pictures (golden / 9836 px / 14658 px). The monolith snapshot at the previous swap is the golden and the palette texture is byte-correct at the snapshot, so the wrong pictures are frames with a bounded run of draws silently skipped (nine sprite blobs absent over intact terrain). Gate 3's acceptance for OpenRA is now six run-ahead and six lockstep runs all equal to the golden; the fix is package B3. |
||
|
|
c2cc3a9673 |
[MG_Test] (P7): the subsystem-deps server-gate cases compile in the pull build too
D2's SubsystemDepsTest called the four Resolve*SubsystemArm resolvers, which Managers.h declares only under MOBILEGL_PIPE_PUSH (:664), so build-linux (the pull flavour, ID-P7-11) stopped compiling while the stale libMobileGL.so kept G1 looking green. Cases 3 and 4 now skip in the pull flavour the way case 2 already did; the unit name set is the same in both flavours. Pull build rc 0, .text 0xa52203, symbols 0 added / 0 removed; push flavour 5/5. |
||
|
|
519445f946 |
[MG_Pipe, MG_Impl, MG_Test, docs] (P7): D-K2's dependency rule in one table, with the case that fails when the two readers disagree
P3b/P4b R-5 (and R-3's cheap half), landing under the P7 program (PLAN-PH-P34B-P7.md
1.2, wave 2-D package D2).
The rule had SIX statements and nothing compared them, and two of the four prose ones
were wrong in the same way: MG_Pipe/MGPipe.h and MG_Backend/DirectGLES/Managers.h both
said "THREE OF THEM HAVE A DEPENDENCY" (there are six rows) and both said "the mirror
pairs (10 without 11, ...) are all fine" - when 10-without-11 is precisely D-K2's FOURTH
row (ID-14/ID-15), refused by the server and withheld by the client, with PipeFill.cpp
saying so out loud: "The brief's original 'bit 10 without 11 is fine' is WITHDRAWN for
P4a as built." Neither wrong comment could fail anything; Managers.h's copy sits in the
header of the file that implements the refusal.
The rows, once, in MG_Pipe/SubsystemDeps.def (X-macro, FatalFamilies.def's convention),
expanded by MGPipe.h into kMGPipeSubsystemDependencies[] / MGPipeSubsystemRequires() /
MGPipeSubsystemDependenciesAreSet() with four static_asserts. Six rows: 8->7, 9->10,
10->{7,11}, 11->10, 12->none (a ROW, not an absence), 13->7. Bits 8 and 13 are the two
the old comment did not know about; bit 13's row lived fifteen lines further down in the
same header, which is how "THREE OF THEM" survived two phases.
THE TWO READERS DO NOT YET READ IT, deliberately: MG_Impl/Pipe/PipeFill.cpp is the
contract package's file for the whole phase (TextureEmit.h states that rule) and
MG_Backend/DirectGLES/Managers.cpp belongs to wave 2-D package D1 while it is in flight.
Switching them is two one-line changes for the integrator. The anti-drift property does
not wait: MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp is the first thing in the tree
that calls BOTH readers at the same mask - sweeping the phase constants, the two
documented refusal lanes and one mask per row - and compares each against the .def. It
lives in that binary because that binary links both sides.
Both comparisons are one-directional, each for a measured reason. The server resolvers
also fold in the family's own bit and D-C3's wire-caps refusal, so a resolver may say NO
for its own reasons but never YES where the table refuses. MGPipeP4aFamilyEmits is a
QUADRUPLE and the first version of this case asserted equality and reddened at 0x1fff on
the sampler and program families because a bare unit binary registers no consumer - the
assertion being wrong, not the product, and recorded in the note because the temptation
to assert equality here is strong.
Red-once (R-16), twice: dropping the fourth row from the .def fails at COMPILE time
(MGPipe.h's static_asserts); making the server's ResolveSamplerSubsystemArm stop checking
bit 10 (throwaway, reverted) reds the runtime case and names the mask - 0x9ff, which is
exactly the lane CMakeLists.txt already pins for G12.
R-3's cheap half: CONTRACT-P5.md claimed the image-access, sampler-view-target and
draw-buffer encodings as contract by pointing at package-header LINE RANGES, and all
three had drifted (ImageEmit.h:146-159 no longer holds the encoding at all). All three
rows now state the encoding by enumerator and function name, and the two confusable
siblings are named. The remaining half - MGPSamplerView::Target still has no Count bound,
no ...IsValid and no Fatal for an out-of-range value - is stated in the row rather than
closed, being an MG_Pipe change outside this package's footprint.
Managers.h:690-695's copy of the wrong prose is NOT corrected here: it is package D1's
file, and its two defects are named in notes/p34b/espryt-d2.md so the integrator can take
them in one line.
G1 after this change: pull .text 10822147 = 0xa52203, nm --defined-only identical to
~/w7/p7-before/pull-syms.txt (30570 symbols) - the new inline constexpr table does not
reach the pull build.
|
||
|
|
779ff21b3e |
[MG_Test] (P7): the raw-depth-fetch sampler case runs both arms; the deletion half is P13
P3b/P4b deepening, landing under the P7 program (PLAN-PH-P34B-P7.md 1.2, "raw-depth-fetch sampler 的 monolith 清理,步骤 1+2", wave 2-D package D2). STEP 1 DONE. ContextEpochTest gains a case that drives GetRawDepthFetchSampler() on BOTH arms in one process and asserts they are two different BackendSamplerObjects, which is the observable form of the claim DirectGLES.cpp:334-336 makes in prose - "the legacy pair is not constructed or consulted by the server". A server that had fallen through to g_rawDepthFetchSamplerBackend would hand back the same pointer under both transports. It also asserts the transport arm's cached native sampler survives a round trip through the monolith arm, so neither arm is rebuilding state the other owns. STEP 2 NOT DONE, AND THE PACKAGE'S OWN CONDITION IS WHY. The instruction was to delete what the transport arm makes unreachable "ONLY if it keeps the pull build's .text identical". It does not. Every candidate is UNCONDITIONAL code - the two file-static SharedPtrs (DirectGLES.cpp:82-83), the monolith arm (:361-371), NeedsRawDepthFetchSampler (:393-399) and the pre-handle sampler-pass branch (:8015-8024). None sits behind #if !MOBILEGL_BUILD_DISAGGREGATED; the pull build configures DISAGGREGATED=OFF and PIPE_PUSH=OFF and links all of it, and the monolith arm is the ENTIRE body GetRawDepthFetchSampler has there. Deleting any of it moves pull .text and fails G1's 0/0/0/0 - the rule ARCHITECTURE.md:340 states for this exact class of retired-but-compiled code. Recorded as P13, beside the existing S7 entry in notes/p5f/f0-statics.md, rather than attempted and reverted. G1 on this head: .text 10822147 = 0xa52203, nm --defined-only identical to ~/w7/p7-before/pull-syms.txt (30570 symbols). |
||
|
|
9a69e08365 |
[MG_IntegrationTest, docs] (P7): twelve texture and program scenarios join the split arms, with the census that put them there
P3b/P4b deepening, landing under the P7 program (PLAN-PH-P34B-P7.md 1.2, "门:纹理 /
program 场景 split 覆盖", wave 2-D package D2). Each candidate was run once under
MOBILEGL_TRANSPORT=inproc on build-split and its first blocker recorded; the row per
scenario is in notes/p34b/split-scenario-census.md.
Registered (all green on inproc, spawn and tcp): TextureView,
TextureParamsWithoutASamplerView, SampledSetStaleness, ImageSizeAfterRespec,
ImageTargetKind, IntegerBorderColor, SwizzleAccessRoutine, PostLinkAttach,
RelinkStageSet, UniformInitializer, Glsl420Declaration, IoBlockNameCollision. 71 cases
per arm, 213 new gating entries; the recorded split-arm registration count the tier-2
Magma replay walks goes 33 -> 46.
TextureParamsWithoutASamplerView settles the debt table's 待核 row: all four G9 cases
pass under all three transports, so the P6 inspection-forwarder debt is closed for this
half - and it is now re-taken on every CI run rather than by inspection.
ProgramPipelineScenario beyond *StorageBlock* is NOT registered, and that is the rule
working rather than a blocker to retire: the other nine cases are pure name/state cases
that draw nothing, and an armed DirectGLES.Split. case whose workload produced no record
is red by design (ScenarioFixture.h:84-97 - the client encoder's record ordinal must
move). Registering them would assert "records crossed" about cases with no records to
send.
DirectVulkan arms default to none, deliberately, and the note says why: the
informational tier-2 block already replays every recorded registration on DirectVulkan
under integration-magma-all-{split,spawn,tcp}, so these scenarios do get a Magma x
three-transport reading - just not a gating one. Gating them would assert P7 wave 2's
exit gate 1 from inside a P3b/P4b package, on a backend whose own @P7 refusals are still
being retired in waves A/B/C.
D1 owns PixelStoreSweep, DepthStencilReadbackMatrix, PackedWordReadback and
LayeredTextureReadback; they are not in this census.
|
||
|
|
5617c5c3b3 |
[MG_IntegrationTest, docs] (P7): the texture upload shape becomes a gate, with the view remap inside the workload
P3b/P4b R-11 / D-D4, landing under the P7 program (PLAN-PH-P34B-P7.md 1.2, wave 2-D
package D2). P4a recorded the emission shape rather than gating it because the
per-storage-owner cursor with view/owner remapping was the next phase's and gating an
unfinished shape pins a non-answer. That work landed, so the RecordProperty block is
now EXPECT_EQ against a gold row MEASURED on this cursor, and the workload carries a
third texture written through a glTextureView so the remap is inside the counted
window.
The gold row, identical on monolith / inproc / spawn / tcp:
emit=9 box=9 rect=0 jobs=9, client emitter 9
keyed on the workload constants by static_assert. P4a's emit=6 box=6 rect=0 jobs=6
was a two-texture workload with no client emitter and is not comparable.
The client half is read from MGPipeTextureEmitter::SubDataCount() rather than from
ctu=, and that is a correction: under spawn and tcp the two roles are two processes
whose PipeStats windows close on different Present cadences (measured ctu=13 against
emit=9, and usually not yet written when the case reads), so ctu == emit is not a
well-posed comparison there. The log field is kept as a record and asserted only
where the transport says the two are window-aligned - asking the NUMBER was the first
mistake, because the server publishes a readable ctu=0 under spawn and the comparison
silently became 0 == 9. PipeStatsWindow gains LastFromClientLog/LastFromServerLog.
Split/spawn/tcp arms added as explicit per-arm blocks rather than through
mgl_itest_register_split_arms: the macro hands every arm the shared environment, and
this case needs a lane marker, PipeStats period 1, a push mask and above all a
MOBILEGL_LOG_FILE_PATH private to the arm - the library opens the log "w", so two
entries sharing a path truncate each other and the failure looks exactly like "the
counter was never emitted".
Red-once (R-16): the prescribed flip of summedArea*4 >= unionArea*3 leaves the gate
GREEN, and that is a finding. Two gates downstream dominate it - the server needs
dirtyRectCount >= 2, and UnpackRingAvailable() zeroes the rect list by design ("one
box, one job", the existing Mali-cliff mitigation). So rect=0 is the policy's
intended output, not an accident of the scatter pattern. The red-once that bites
splits the union box in two AND disables the unpack ring: box=6 rect=3 jobs=12
against the gold, while 26 pixel cases stay green. Four attempts and their reasons
are in the note.
The Mali frame-time delta D-D4 asks for beside the gold is NOT schedulable - one
Redmi, an Adreno, no Mali part - and is recorded as a declared deviation in the
scenario header and MEASUREMENTS.md 13.3 rather than modelled.
Also corrects the three places that said the client emitter had not landed: the
scenario header, the CMake probe's comment, and gates-v1.md's re-run table.
TextureEmit.h's EmitOneLevel has been incrementing it.
|
||
|
|
da30c1301e |
[MG_IntegrationTest, docs] (P7): the owner-keyed emission cursor gets the scenario that can see it
P3b/P4b deepening, landing under the P7 program (PLAN-PH-P34B-P7.md 1.2, wave 2-D
package D2). The code half of "按存储属主键控的发射游标与 view 索引重映射" landed in
P4a/P5e; this is the verification half it never had.
TextureViewAliasScenario, four cases: an upload through a layer-restricted view
sampled through the owner and the reverse, each once and again across draw
boundaries so the drain runs three times. Each case asserts on TWO channels at
once - sampled texels AND MGPipeTextureEmitter's SubDataCount / RefusedSubDataCount
/ DrainListSize, read through the new Harness/TextureEmitPeek. The counters are the
load-bearing half: a level that never made it onto the drain list is still uploaded
by the next whole-level respecify, by a glGenerateMipmap or by the monolith sync
path, so the picture comes out right one frame late and a pixel-only case goes green
over the regression on most trees.
Registered in the three GLES split arms, and MOBILEGL_ESPRYT_ENABLE_TEXTURE_VIEW=1
added to MGL_ITEST_GLES_{SPLIT,SPAWN,TCP,TCPDEVICE}_ENVIRONMENT - views are Espryt's
opt-in and without it every case green-skips. The Tcp arm needed the knob on the
TcpServer.Start fixture as well: that lane's server is pre-started rather than
spawned from the client, so it withheld GL_ARB_texture_view and all four cases
SKIPPED while the identical Split and Spawn arms ran.
Red-once (R-16): TextureObjectView::MarkStorageDirtyRegion made to call
PipeNoteLevelDirty on ITSELF instead of forwarding to the storage owner - the
two-cursor shape. TheViewUploadDrainRunsAgainAfterADrawBoundary goes red on both
channels simultaneously (stale owner texels AND SubDataDelta() == 0). The plan
predicted case (a); it is (c) that reds, because the first drain of a texture's life
carries the whole level anyway - recorded in the note.
TheOwnerUploadDrainRunsAgainAfterADrawBoundary is EXCLUDED BY NAME from the split
arms: it is red on all three transports and green under monolith, because an
owner-side resource_subdata moves only the owner's record.Serial and nothing
propagates that to the records viewing it. Cause and the two candidate fix sites are
named in the CMake comment and in notes/p34b/espryt-d2.md. The name stays in every
lane (G14).
|
||
|
|
ba81453f19 |
[MG_Backend, MG_IntegrationTest, docs] (P7): the readback family runs on the split arms, and it was already green
P3b/P4b espryt D1 slice 4, landing under the P7 program (notes/p7/PLAN-PH-P34B-P7.md
§1.2 row "回读 / pack state", §3 wave 2-D).
(a) THE CENSUS, AND IT CAME BACK CLEAN. Twenty-two cases per arm now run on the three split
arms: the two F1 pack-state cases, PixelStoreSweepScenario, FramebufferChurnScenario,
PackedWordReadbackScenario, LayeredTextureReadbackScenario and
DepthStencilReadbackMatrixScenario. The row expected reds and said so ("Expect reds - that
is the point"); the first run was 65/65 before any of this commit's code changes. That is
the finding: the 回读 half was assumed open because nothing had ever run it under a
transport, and the assumption was what was wrong - ScatterTightReadbackIntoPackState and the
server's neutral-pack read were already carrying it. No case needed a fix and no case needed
a named deferral.
DepthStencilReadbackMatrixScenario gets a per-arm block of its own, because the registration
macro cannot carry MOBILEGL_ESPRYT_FORCE_DS_READBACK_EMULATION=1 and without that pin the
scenario is unfalsifiable here: ES has no core depth/stencil readback, Mesa accepts the
reads anyway, and on llvmpipe every case goes green through a path the device does not have.
Its SeparateDepthAndStencilAttachmentsAreBothReadable SKIPS on all three arms and on monolith
too, naming the driver reason itself - a driver fact, not a transport one.
(b) THE TWO std::abort EMULATIONS, checked rather than assumed. copy-image-shadow-mirror
ALREADY cannot abort: MirrorCopyImageIntoDestinationShadow returns under a transport before
reaching MGPipeUnmigratedEmulation, and has since P5b package i1. get-tex-image-shadow is
retired here, and the reason is that its own premise did not hold: P4a wrote "a split server
holds no shadow to convert", but the level storage this converts IS the server's own, fed by
the staged texture store. When MapMipmapData answers, the conversion reads bytes that exist
on this side. So it converts when the shadow is there and DECLINES BY NAME when it is not
(rule I: MGLOG_E_ONCE + GL_INVALID_OPERATION, destination untouched). std::abort inside a
server answering a client's readback takes the session down for a level it happens not to
hold, and bypasses Session::Fail, which is the funnel the census gate watches. The funnel
keeps its teeth for the other four sites - generate-mipmap-storage,
generate-mipmap-cpu-fallback, generate-mipmap-cpu-filter, texture-remint-pull - which really
do reach into a client address space; fatal_census is unchanged at 79 abort sites because
the abort lives in MGPipeUnmigratedEmulation and that function stays. Stated plainly in the
notes: no case in the armed census reaches this site under a transport (the server answers
GetTextureImage with ReadTextureImageWire, never through the backend's GetTexImage slot), so
the change is defence in depth and has no integration red-once of its own.
(c) THE CASE NOTHING COVERED, and this slice's red-once:
PixelStoreSweepScenario.ReadPixelsIntoAPackBufferHonoursPackStateAndLeavesTheGapsAlone -
glReadPixels into a bound GL_PIXEL_PACK_BUFFER at a non-zero buffer offset with
PACK_ALIGNMENT/ROW_LENGTH/SKIP_ROWS/SKIP_PIXELS all non-default, read back with
glMapBufferRange. It is the only thing that drives EmitTables.cpp's `if (pbo)` branch, the
client servicing the pack PBO itself after the reply lands; the existing sweep case reads
into a client pointer, which is the other half and has unit cover. Every number makes a term
matter: ROW_LENGTH 5 at 4 bpp is 20, which ALIGNMENT 8 rounds up to a 24-byte stride;
SKIP_ROWS 1 and SKIP_PIXELS 2 put the first pixel at image byte 32; the read is issued at
buffer offset 16, so it lands at 48. The whole buffer starts 0xCD and the case asserts both
halves - the rows land where the pack state says, and every other byte is still poison.
Red-once (R-16): dropping just the buffer-offset term from EmitTables.cpp's `start` reds the
three split entries with "readback[at] Which is: '\xCD' (205)" - the destination still poison
because the rows landed 16 bytes early - while the ambient monolith entries pass, since the
driver services its own PBO there. EmitTables.cpp is not part of this commit.
(d) SplitReadbackPackBuffer's comment claimed "The client refuses PACK_BUFFER before
emission". It does not: it KEEPS the PBO and services it after the reply, and a refusal would
have been observable as a GL error. The conclusion (the server must see no pack buffer) was
right and the reason was backwards; rewritten to ID-49's actual split of the work.
Gates: integration-split 225/225 (base 186), integration-spawn 141/141 (base 102),
integration-tcp 144/144, ctest -L unit 2408/2408, integration-magma-split 73/73,
spawn_lane_parity / fatal_census (79 sites, 0 unmarked) / link_ratchet (186, unchanged)
green, G1 .text 0xa52203 with both nm lists byte-identical to ~/w7/p7-before.
|
||
|
|
834a8917ff |
[MG_Backend, docs] (P7): a transport with no record arm stops by name instead of dying mid-frame
P3b/P4b espryt D1 slice 5, landing under the P7 program (notes/p7/PLAN-PH-P34B-P7.md
§1.2 row "g_fboTextureSyncList", §3 wave 2-D).
ResolveFramebufferSubsystemArm and its three siblings classify a family's arm as Handles,
Legacy or NoArm and stop only on NoArm. Under an active transport Legacy is not a slower
answer, it is a postponed crash: all four pre-handle arms reach frontend state through
accessors that are BARRIER_PULLED rows (FieldOwnership.def), and under a transport that
state belongs to a client that is no longer parked behind this apply, so the accessor's
honest answer is Fatal{UnmigratedPipeInput, "<field>@<verb>"} on the first verb that reaches
it. That is the audit's own claim about g_fboTextureSyncList - "separation-safe, because the
record arm returns first" - which holds only while the record arm is ARMED, and nothing
checked that.
Each of the four now stops, under #if MOBILEGL_BUILD_DISAGGREGATED, when the transport is
not monolith and the verdict is not Handles, through one shared
StopOnTransportWithoutRecordArm(bit, detail) that formats both causes and hands them to
BufferImpl::StopOnArmlessPipeSubsystem - this file's existing single voice for "the
configuration left no arm to run". A transport with the record arm off IS that condition
with a different cause: the legacy arm is present but unreachable. Reusing the voice keeps
the census's family word and puts the cause in the message rather than in a new word the
census would have to learn.
Said precisely, this is a RESOLVE-TIME stop and not a startup one. The four resolvers are
function-local statics resolved lazily at first use, deliberately - Managers.cpp's own
comment says a stop inside eglMakeCurrent reads to the harness as "no usable GPU" and skips
- so the stop still lands in a scenario body where ctest reports it. What it no longer does
is wait for a verb to dereference the client's state and then blame the accessor.
Red-once (R-16), and it is the safety argument the audit asserted but never ran:
MOBILEGL_PIPE_PUSH=0x1dff (the Handles lane's 0x1fff minus 0x200,
kMGPipeSubsystemFramebuffer) with MOBILEGL_TRANSPORT=inproc, running
ClearThenReadPixelsScenario.ClearWithNoDrawIsVisibleToDefaultFramebufferReadPixels. Both
runs abort; what changes is what the abort says. Without the stop:
Fatal{UnmigratedPipeInput, "GetFramebufferBindingSlot@Clear"} - mid-frame, naming an
accessor and a verb, with nothing pointing at the mask. With it:
Fatal{PipeLegacyMemosDisabled, "MOBILEGL_TRANSPORT is not monolith and
kMGPipeSubsystemFramebuffer (bit 9) is clear (or refused), so the record arm is off and the
legacy arm is unreachable: ..."}.
Gates: integration-split 202/202, integration-spawn 118/118, integration-tcp 121/121,
ctest -L unit 2408/2408, integration-magma-split 73/73, spawn_lane_parity / fatal_census (79
sites, 0 unmarked) / link_ratchet (186, unchanged) green, G1 .text 0xa52203 with both nm
lists byte-identical - the four blocks are inside MOBILEGL_BUILD_DISAGGREGATED and the pull
build compiles none of them.
|
||
|
|
767e6c8084 |
[MG_Pipe, MG_Test, docs] (P7): the reverse channel is nine callbacks, and the prose says what the tree does
P3b/P4b espryt D1 slice 3, landing under the P7 program (notes/p7/PLAN-PH-P34B-P7.md
§1.2 row "XFB scatter" item 3; CONTRACT-P7.md §139 already records the deletion).
Plan B section 7.1 named ten reverse-channel callbacks. The tenth, OnXfbScatterReady,
belonged to a design the tree does not have: plan B put the XFB scatter on the CLIENT - the
server would hand back the packed scratch and the client would run the patch loop over its
own shadow - so something had to tell the client the layout. P5c/P5f went the other way,
and the declaration was left behind with zero producers, zero consumers and no EventKind of
its own, while the static_assert beside kMGPipeCallbackCount made it look load-bearing.
Deleted; kMGPipeCallbackCount 10 -> 9.
Three pieces of prose that still described the old design go with it. MGPipeCallbacks.h's
own header said "ten callbacks", and now says nine and why the tenth went.
MGPipeTypes.h's MGPXfbAccounting comment said the scatter "is a read-modify-write of the
client's shadow and lives there"; it lives on the server's staged shadow, and nothing about
the scatter crosses in that record. ARCHITECTURE.md §8.5, titled "XFB scatter 搬到 client",
is rewritten to what the tree does: the pre-capture bytes are the server's own staged
shadow (rule C, since MappedData() on the apply thread is Fatal{RoleViolation,
"buffer-legacy-arm"}), the reconciled whole range comes back as one OnBufferWriteback
sliced against SEG_EVENT's capacity (slice 2), and an orphaned target is a legal one (slice
1). Its row in §8.1's callback table goes too - leaving it would be a new staleness rather
than an inherited one.
PipeCatalogue.ReverseChannelHasTenCallbacks now asserts 9. The CASE NAME IS DELIBERATELY
NOT RENAMED: G2/G14 say the ctest name set only ever grows, so renaming would delete a name
the gate watches. The count it asserts is what has to be right and the comment carries the
number.
Red-once (R-16): with the member and the count restored the case fails with
"kMGPipeCallbackCount Which is: 10 / 9u Which is: 9", which is what makes the struct's
shrink a measured fact - the static_assert beside the constant only proves the two agree
with each other.
G1 matters for this slice in particular, because MGPipeCallbacks.h is included from
MG_Backend and the struct is NOT behind MOBILEGL_BUILD_DISAGGREGATED, so
sizeof(gMGPipeCallbacks) really does shrink by 8 bytes in the pull build. The shrink is in
.bss and nothing indexes the table by count: .text is still 0xa52203 and both nm lists are
byte-identical to ~/w7/p7-before.
Gates: integration-split 202/202, integration-spawn 118/118, integration-tcp 121/121,
ctest -L unit 2408/2408, spawn_lane_parity / fatal_census / link_ratchet (186, unchanged)
green.
|
||
|
|
96fa569250 |
[MG_Backend, MG_Pipe, MG_Remote, MG_IntegrationTest, docs] (P7): the XFB writeback event is sliced, so a large capture stops killing the server
P3b/P4b espryt D1 slice 2, landing under the P7 program (notes/p7/PLAN-PH-P34B-P7.md
§1.2 row "XFB scatter", §3 wave 2-D).
Both XFB writeback producers posted ONE unbounded OnBufferWriteback covering the whole
bound range - ScatterCapturedRecords from its staged vector, ReadbackCapturedRanges from a
live glMapBufferRange mapping. ServerOnBufferWriteback turns each into one SEG_EVENT record
of sizeof(EventBufferWritebackHead) + bytes, and RingProducer::Reserve refuses any record
above Capacity()/2 outright - not "when full" but always, because above half a capacity a
record is placeable at some head offsets and not at others and waiting for room would be a
hang. EventRingBytes defaults to 256 KiB, so any capture target wider than ~128 KiB was
Fatal{EventRingOverflow} and the SERVER PROCESS DIED, on a buffer size, with nothing else
wrong. The producers' own Fatal text already said what to do: raise MOBILEGL_IPC_EVENT_KB
or slice the event at its producer, "as the writeback path already does" - which was true
of the client's request path and of nothing on this side.
MG_Pipe now carries the one number, published the way gMGPipeSegmentResolver is published:
gMGPipeEventRingCapacityBytes, installed by ServerSession::Accept beside the four reverse
producers and released at Close only if it is still ours, and
MGPipeBufferWritebackSliceBytes() which turns it into a per-record width - a quarter of the
ring with a 4 KiB floor, the same number and the same reasoning as the client-linkage
BufferWritebackSliceBytes one ring over. The backend can reach neither MG_Remote::Client
(wrong linkage) nor ServerSession (wrong layer), so the hook is the seam; 0 means "do not
slice", which is monolith, where the callback is a direct call with no ring under it.
Both producers loop against ONE helper, XfbWritebackSliceStep, because a second spelling of
the width is how one of them ends up still killing the server. The readback site slices
inside its mapping and still unmaps once; the scatter site slices the staged vector and
keeps the single glBufferSubData whole, since the ES store has no ring and cutting it would
change how many backend calls one application call makes. In-order delivery makes the last
slice's landing imply every earlier one, so the client needs nothing new.
Red-once (R-16): XfbAfterClipDistanceScenario.{AScatteredCapture,APlainCapture}
IntoAMegabyteRangeReachesTheApplication - a 1 MiB bound range, about eight times what one
record can ever be, with the same six capture records; glBindBufferBase over the whole
store is what makes the mirrored range a megabyte. Two cases for the two producers. With
the slicing reverted all six split entries go red on Fatal{EventRingOverflow}
("kEventBufferWriteback needs 1048600 bytes and SEG_EVENT caps ONE record at 131072") -
aborting the test process under inproc, losing the peer under spawn and tcp - while both
monolith controls stay green.
XfbAfterClipDistanceScenario and XfbRepeatedCaptureScenario now join the three split arms
in full; slice 1 could register only OrphanedCaptureTarget* because the rest could not
survive the unsliced event. XfbRepeatedCaptureScenario's gl_NextBuffer compacted-target
shape is the first thing outside the census to exercise the archive-served stride and
varyings (ProgramArchive->Link's xfb fields) on the server, and it passed on all three arms
with no further change.
Gates: integration-split 202/202, integration-spawn 118/118, integration-tcp 121/121,
ctest -L unit 2408/2408, the monolith and split XFB families 120/120, spawn_lane_parity /
fatal_census (79 sites, 0 unmarked) / link_ratchet (186, unchanged) green, G1 .text
0xa52203 with both nm lists byte-identical to ~/w7/p7-before.
|
||
|
|
faf0a1f902 |
[MG_Backend, MG_IntegrationTest, docs] (P7): an orphaned XFB capture target keeps its varyings
P3b/P4b espryt D1 slice 1, landing under the P7 program (notes/p7/PLAN-PH-P34B-P7.md
§1.2 row "XFB scatter", §3 wave 2-D).
ScatterCapturedRecords is a read-modify-write of the capture destination's pre-capture
bytes - a gl_SkipComponents hole has to come back holding whatever the application left
there - and under a transport those bytes are the server's staged shadow rather than a
frontend MappedData(). The split arm read a null shadow as "this target has no
pre-capture bytes, discard it". A null shadow is not an error: MGPResourceDesc's
HasDefinedContent == 0 is the store the application ORPHANED with glBufferData(size,
NULL), and Managers.cpp's own M-3 comment says every byte it has not staged since is
undefined by its own declaration. So the continue threw a whole capture away for the
ordinary streaming idiom (orphan, upload part of it, capture into it), leaving the
application's buffer holding its pre-draw bytes under GL_NO_ERROR. The partly uploaded
shape did not even reach the continue: it has a shadow, so it asked RequireStagedCoverage
for the whole bound range and died on Fatal{StageSnapshotTooNarrow,
"xfb_scatter_pre_capture"}.
Monolith has always been right here and is right by construction: `Vector<Uint8>
staged(rangeBytes)` is value-initialised, so scattering over an unreadable source scatters
over zeroes and uploads them, which is byte for byte what a fresh MappedData() hands back.
The split arm now skips only the Memcpy, never the target. Discard-with-log is kept for
splitResource == nullptr alone, which really is impossible and has no backendId to upload
into either. RequireStagedCoverage is guarded by ResourceContentIsDeclared, exactly as
Managers.cpp's pool-reuse ladder guards its own whole-store MGL_SERVER_STAGED_REQUIRE: a
declared store still Fatals on a coverage gap, an orphaned one does not.
ResourceContentIsDeclared moves out of Managers.cpp's anonymous namespace to namespace
scope beside RequireStagedCoverage and is declared in Managers.h. The two callers must not
be able to answer "did the application declare these bytes" differently. Both live inside
#if MOBILEGL_BUILD_DISAGGREGATED, so the pull build compiles neither.
Red-once (R-16): XfbAfterClipDistanceScenario.OrphanedCaptureTarget{WithAPartialUpload,
WithNoUploadAtAll}KeepsItsVaryings, registered on the three split arms under the Xfb.
prefix. With the fix reverted the six split entries go red - the partial-upload three on
Fatal{StageSnapshotTooNarrow, "xfb_scatter_pre_capture"} ([0, 696) of a buffer whose
staged coverage does not include it), the whole-orphan three on a captured varying that is
still zero - while the two ambient monolith entries pass, which is what makes it a
transport fact. Details and the lane numbers in docs/Disaggregated/notes/p34b/espryt-d1.md.
The narrow filter is deliberate: the rest of XfbAfterClipDistanceScenario is a DirectVulkan
allocation-pattern regression that has never run under a transport, and it joins the arms
in slice 2 once the writeback event it posts is sliced.
Gates: integration-split 188/188 (186 + 2), integration-spawn 104/104, integration-tcp
107/107, ctest -L unit 2408/2408, the monolith DirectGLES XFB + readback families 66/66,
spawn_lane_parity / fatal_census / link_ratchet (186, unchanged) all green, G1 .text
0xa52203 with both nm lists byte-identical to ~/w7/p7-before.
|
||
|
|
09ba88e70d | [MG_Remote, docs] (P7): CONTRACT-P7 §3.2 records package B (readback race, copy-image, default blit, depth mip bake, mip diagnostic), ID-P7-23 | ||
|
|
76b7469389 |
[MG_Backend, MG_IntegrationTest, docs] (P7): the wire readback waits for the frame it is reading, instead of inferring who wrote it
Exit gate 3, OpenRA on the Redmi. The mechanism, in two lines of this tree: WireFramebuffer.inc, ReadWirePixels: `if (HasPendingRecordedWork() && !FlushPendingCommands())` - FlushPendingCommands (VulkanRenderer.cpp:13534-13598) puts the frame's draws on the queue under a POOLED FENCE OF ITS OWN and returns without waiting; there is no vkWaitForFences on its success path. The vkCmdCopyImageToBuffer that follows is recorded into a fresh command buffer and becomes a LATER submission. VulkanRenderer.cpp:10707-10748, SubmitReadbackCommandsAndWait: it waited ONE fence, its own, and then called OnSubmitsCompletedUpTo(frame.lastSubmitIndex) - which RECORDED the earlier submission as complete and retired its fence, so afterwards nothing could observe that it had not been waited for. The comment beneath it said "the wait proved every submission complete". That is true of the monolith arm, which never flushes before a readback and therefore has its draws and its copy in one submission under one fence. It was not true of the wire arm. The only thing tying the two submissions together was the pipeline barrier that the readback's TransitionWireImage records - and that barrier derives its SOURCE scope from the image's tracked layout, through GetImageTransitionSourceState (VulkanRenderer.cpp:1784-1825). Two of its arms, VK_IMAGE_LAYOUT_UNDEFINED and VK_IMAGE_LAYOUT_PRESENT_SRC_KHR (:1787-1791), return VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT with no access bits, which is Vulkan's spelling of an EMPTY first scope: wait for nothing. A just-presented default framebuffer is in the second of those layouts. The slower pipeline creation is, the longer the draws' submission runs, and the more of the frame's LAST-drawn objects the copy can miss - which is what the device's diff image shows. Measured on this host (OpenRA, DirectVulkan, inproc), at the moment the copy is recorded: submit=72 completed=71. The submission carrying the whole frame's draws is in flight and unwaited. That is structural, not a coin flip, and it reproduces every run. The fix is two halves, both wire-only: - RecordWireReadbackWriteBarrier (WireFramebuffer.inc, new): the readback records its own ALL_COMMANDS -> TRANSFER, MEMORY_WRITE -> TRANSFER_READ|MEMORY_READ barrier before the copy rather than inferring from a layout who it should wait for. A barrier's first scope is every command earlier in SUBMISSION order on the queue, so it reaches across the submission boundary. - SubmitReadbackCommandsAndWait now waits every in-flight fence at or below its own submission index. Under #if MOBILEGL_BUILD_DISAGGREGATED: the pull build has no wire arm, cannot flush before a readback, and is the image G1 pins - its single-fence wait is unchanged. The cost is nothing a readback does not already pay. THE COORDINATOR'S REPRODUCTION PATH DOES NOT WORK ON THIS HOST, and that is recorded rather than glossed. MOBILEGL_TEST_PIPELINE_CREATE_DELAY_MS is added as asked (split-only, inert unless set, on the pipeline-creation MISS path in GetOrCreatePipelineWithInput) and it does bite - the OpenRA DirectVulkan SPLIT retrace goes from 1.88 s to 4.53 s at 25 ms - but ssim stays 1.000000. The reason is in the probe above: this host's default framebuffer is tracked as VK_IMAGE_LAYOUT_GENERAL at the readback, which takes GetImageTransitionSourceState's `default:` arm (ALL_COMMANDS / MEMORY_WRITE) and orders the copy by accident. The device's is not GENERAL. Red once (executed, reverted) is therefore a COUNT, not a picture, and it is deterministic: instrumenting how many fences the readback actually waits on gives fences=2 upTo=73 completed=71 with the fix (submission 72, the draws, plus its own 73) and fences=1 without it, with completed=71 either way - i.e. 72 provably outstanding and provably unwaited before. Lane: three readback-after-work cases (VertexIdSamplerAndScalarUniformPixels, ComputeImageStoreFramebufferPixels, GenerateMipmapPixels) registered on the three Magma arms under MOBILEGL_TEST_PIPELINE_CREATE_DELAY_MS=25, prefix .PipelineDelay. They are a REGRESSION GATE, not the reproduction: they pin that a readback still returns the draw's pixels when every pipeline creation on the path costs 25 ms. Gates: integration-magma-split 84 (+3), -spawn 63 (+3), -tcp 65 (+3), 0 red; ctest -L unit 2410; informational integration-magma-full-split 516, 0 red, 59 skip; fatal_census green; link_ratchet --assert-monotone unchanged at 186, p7-magma 101; G1 pull .text 0xa52203, defined-symbol name list identical (30570); OpenRA DirectVulkan SPLIT retrace ssim 1.000000 with and without the delay, two runs each. Owed to the device: the three cold-cache repeats. If they still diverge, the next two suspects are named in notes/p7/magma-b.md §5.6 - the eleven silent `return false` drop sites in SetupWireDraw (one of which is `pipeline == VK_NULL_HANDLE`, on the same path as the device log's three "handing vkCreateShaderModule an INVALID module" errors), and SetImageContentDefined being set BEFORE the draw is recorded on the wire arm while the monolith sets it from EndRenderPass, which silences the "no write is recorded" warning exactly when it should fire. Neither is in this commit's scope. |
||
|
|
583e12de76 |
[MG_Backend, MG_IntegrationTest, docs] (P7): the default-framebuffer blit stops refusing shapes the arm beneath it can do, and the 3D bug that hid behind the refusal is fixed
CONTRACT-P7 §3.2's `default-color-blit-shape@P7`, retired in three pieces, because one
Fatal was covering three unrelated situations.
The shader pass it guarded is there for ROTATION and nothing else: it can only SAMPLE
its source - sampler2D, a float result, one of two 2D view types - so an RGBA8UI
attachment, a 3D slice or an array layer made it return false. The refusal then fired
EVEN ON A SURFACE WITH NO ROTATION, where the ordinary vkCmdBlitImage arm twenty lines
below would have done the blit and does handle those shapes.
- identity: fall through to that arm;
- a non-identity rotation with a source the pass cannot sample: move the region into an
owned 2D single-sample image of a sampled-float format in the SAME size-compatibility
class and shader-blit that;
- outside the four rotations (a mirror, or a mirror-rotation): decline by name, saying
which transform. Neither arm can express a reflection and this package has no device
that reports one to check a result against.
Reinterpretation is the point for the integer case and GL agrees: glBlitFramebuffer
between an integer and a normalized format is INVALID_OPERATION (4.6 core 18.3.1), so
there is no defined result to be wrong about - only a shape that used to end the session
and now produces the source's bits. For a float source of the wrong view type the copy
is a plain slice extraction and the result is exactly right.
THE RETIREMENT UNCOVERED A REAL BUG, which is the part worth reading. The ordinary blit
arm addressed a 3D source as `{aspect, level, source.layer, 1}` with z 0..1. A 3D image's
slice rides Z; its only subresource is layer 0 - which is how the copy endpoint's
CopyImageSliceMapping and the multisample resolve arm above already address one. So a
blit from slice 1 of a 3D texture read slice 0, out of a layer the image does not have.
It was invisible because that shape hit the Fatal first and never reached the arm: the
new scenario's first run had the scratch arm correct and the fall-through arm wrong in
all four quadrants, which is how it fell out. Both arms agree now.
The scenario's source is slice 1 of a 3D texture - ordinary GL, and exactly the shape the
shader pass cannot sample - with slice 0 filled in a colour that appears in no quadrant,
so a scratch copy that forgot the z origin would still be reading real texels and had to
be made distinguishable. It is registered twice on each arm: plain, taking the identity
fall-through, and under MGITEST_MAGMA_FORCE_DEFAULT_BLIT_SCRATCH, taking the rotated
arm's scratch path on an identity surface, where it must produce THE SAME PIXELS. That
equality is the assertion, and it is the only way the scratch arm is reachable from a
host lane at all: every surface this lane can create reports
VK_SURFACE_TRANSFORM_IDENTITY, as does the Redmi pbuffer this cluster's evidence comes
from. The knob does not fake a rotation - nothing downstream is told the surface turned.
Red once (executed, reverted): with MagmaWireFatal("default-color-blit-shape@P7")
restored, both entries die by that name; without it, 2/2 green.
Gates: integration-magma-split 81 (+2), -spawn 60 (+2), -tcp 62 (+2), 0 red;
ctest -L unit 2410; informational integration-magma-full-split 516, 0 red, 59 skip;
fatal_census green; link_ratchet --assert-monotone unchanged at 186, p7-magma 101;
G1 pull .text 0xa52203, defined-symbol name list identical (30570).
Owed to a device window: the scratch arm's geometry under a REAL ROTATE_90/180/270. The
knob proves it produces the fall-through's pixels on an identity surface, which exercises
every line of it except the rotation constant the vertex shader applies.
WireColorBlit.inc and VulkanRenderer.h are edited outside this package's named file list.
Both belong to this cluster (the colour blit's implementation, and four private
declarations beside the existing blit ones); flagged for the integrator.
|
||
|
|
4f857aa5c6 |
[MG_Backend, MG_IntegrationTest, docs] (P7): a same-image copy takes one GENERAL transition instead of the session
CONTRACT-P7 §3.2's `copy-image-in-place@P7`, retired.
glCopyImageSubData between two subresources of ONE texture is ordinary, defined GL -
4.6 core 18.3.2 spells out that a texture and a view of it are two objects over one
image and may be copied between. The wire arm took the session down for it, and the
reason it gave was true of the code beneath it and of nothing else: the
TRANSFER_SRC/TRANSFER_DST pair cannot describe one image, because both endpoints share
one tracked VkImageLayout and the second transition undoes the first.
VK_IMAGE_LAYOUT_GENERAL can describe it - both sides of vkCmdCopyImage accept it. So a
same-image copy now takes ONE transition of the whole image to GENERAL (the source level
and the destination level are both inside it), GENERAL on both sides of the command, and
ONE restore, with an access mask carrying both directions because that one barrier is
the edge for both. The monolith arm still declines every in-place copy a few lines up,
so the wire arm is the one that performs this - which is what §3.2 asks for rather than
parity. `inPlace` is constexpr false in the pull build so every branch it guards folds:
this function is in the monolith image and G1 pins that image's .text.
The one shape that stays a decline is same image + same level + overlapping slices +
overlapping rectangle, all three at once. GL leaves that undefined, and GENERAL makes
the command legal to RECORD rather than meaningful to execute - vkCmdCopyImage orders
nothing between its own reads and writes inside a region, so a tiler produces whichever
half of each texel it reached first. Level 0 to level 1, disjoint layers, and disjoint
rectangles of one level are all defined, and all performed.
Two split-only cases on the three Magma arms, over one 8x8x2 two-level array texture
whose every touched subresource is a different colour - a flat fill would let "copied to
the wrong level", "copied to the wrong layer" and "did not copy" all read as correct.
CopyImageInPlaceAcrossLevelsAndLayers does level 0 -> level 1 and then layer 0 -> layer 1
and checks the untouched subresources too; CopyImageInPlaceOverlapDeclinesAndLeavesTheLevelAlone
copies (0,0)-(4,4) onto (2,2) and pins every texel unchanged - an executed copy would
turn the texel at (4,4) from yellow to red, so the assertion can tell.
Red once (executed, reverted): with MagmaWireFatal("copy-image-in-place@P7") restored,
both entries die with Fatal{UnmigratedVerb, "Magma:copy-image-in-place@P7"}; without it,
2/2 green.
Gates: integration-magma-split 79 (+2), -spawn 58 (+2), -tcp 60 (+2), 0 red;
ctest -L unit 2410; informational integration-magma-full-split 515, 0 red, 59 skip;
fatal_census green (79 sites / 43 words / 0 unmarked); link_ratchet --assert-monotone
unchanged at 186, p7-magma 101; G1 pull .text 0xa52203 and the defined-symbol name list
identical (30570) - the constexpr-false branches fold as intended.
This tree's integration TCP endpoint moved to 127.0.0.1:40913: a sibling worktree's
libMobileGLServer was already listening on pipe's default 40613 and took the whole tcp
lane down at TcpServer.Start. It is a local configure option, not a lane semantic.
|
||
|
|
fdd611e83d |
[MG_Backend, MG_Test, MG_IntegrationTest, scripts, docs] (P7): the depth mip chain is baked, and every baked header in the tree now has to match its source
CONTRACT-P7 §5.1's (A) and (D), and (D) is what makes (A) safe to have.
(A) WireDepthMipmap.{vert,frag,inc}. VK_FORMAT_FEATURE_BLIT_DST is OPTIONAL for depth
formats, so a depth chain through a shader is the ordinary case and not the exotic
one - the monolith arm has carried such a fallback since before the split. The wire
arm had none, so a depth texture the device would not blit died on the first
disjunction of `mipmap-shader-format-or-shape`, and a DEPTH|STENCIL one died a guard
earlier as `depth-stencil-mipmap@P7` whether or not it could be blitted.
The monolith's fallback is not reusable, which is the whole of (A): it builds a GL
ProgramObject, two ShaderObjects and a GL sampler, binds through the uniform manager's
global UBO and writes uSrcTexelSize as a default-block uniform - four frontend objects
and a GL compiler on the server's apply thread. The baked program is the same shader
with the same arithmetic on a push constant and its own descriptor set. The fragment
stage is a PORT and not a rewrite: §3.2 retires a refusal by making the wire arm
observably the monolith's, so the 2x2 box lands where the monolith's lands, including
the half texel its ivec2(texCoord * srcTexelSize) sits off the destination centre.
`depth-stencil-mipmap@P7` therefore splits. DEPTH-only retires into the baked program.
DEPTH|STENCIL declines - MGLOG_E_ONCE and return, before the chain is grown, so a level
this verb will not fill is not created and then recorded as defined. A decline rather
than a Fatal because the monolith does no better: its fallback asserts depth-only, and
an assert is nothing in the release build both arms ship as. Nothing in GL promises a
stencil mip either, and this pass writes gl_FragDepth - it would leave the stencil
aspect of the level it just produced undefined.
(D) MOBILEGL_BAKED_INTERNAL_SHADERS. A baked header is a compiler output committed with
its input beside it and nothing connecting the two: edit the source and the build stays
green while the shader that runs is the one from before the edit. MG_Test's
BakedInternalShadersTest recompiles all eight arrays - WireColorBlit x2,
WireDepthMipmap x2, the iterationRP witness, the three probe stages - from their
committed GLSL with the in-tree glslang and compares word for word.
THE GATE IS THE BAKER. scripts/bake_internal_shaders.py compiles nothing; it runs this
binary with MOBILEGL_BAKE_INTERNAL_SHADERS and splices the words it emits. A gate that
used the glslang LIBRARY while the baker shelled out to a glslangValidator BINARY would
be two compilers with no rule about which is right, and the day their defaults diverge
the gate reds on a header nobody can regenerate.
What the first run measured, which is the one thing nobody could know beforehand:
WireColorBlitSpirv.h and all three arrays of PrimitivesGeneratedNoXfbProbeSpv.h were
already fresh - the hand-run glslangValidator words reproduce exactly. The iterationRP
witness was stale, as its own prose said; it is regenerated here (2142 -> 2143 words),
and the witness magic 0x50323033 is still there because it is a constant in the .comp
rather than a property of the words. All three provenance comments now point at the
script and the gate instead of at a tool on somebody's PATH.
Scenarios, on the three Magma arms: GenerateMipmapDepthWithoutNativeBlitPixels runs
DEPTH_COMPONENT24 and DEPTH_COMPONENT32F under MGITEST_MAGMA_FORCE_SHADER_MIPMAP=1 and
reads level one back through a real sampled attachment, asserting per half and leaving
row 3 - the straddle band - unpinned, because pinning it would assert the half-texel
offset rather than the mip. GenerateMipmapDepthStencilDeclinesAndKeepsTheSession takes
no knob (the decline is on the aspect, before blit support is asked about) and pins that
the session survives and level zero still holds what was cleared into it.
Red once, three of them, executed and reverted:
- a one-character edit to WireDepthMipmap.frag (no rebuild - the gate reads the source
at runtime) reds the WireDepthMipmapFragment row at word 279;
- with the depth arm disabled, the DepthMip entries die with Fatal{UnmigratedVerb,
"Magma:mipmap-shader-format-or-shape format=125 ... aspect=0x2 ...
missing=COLOR_ATTACHMENT"} - format 125 is X8_D24_UNORM_PACK32, which slice 1's
diagnostic names for us;
- with the Fatal restored, the decline case dies as
Fatal{UnmigratedVerb, "Magma:depth-stencil-mipmap@P7"}.
Gates: integration-magma-split 77 (+2), -spawn 56 (+2), -tcp 58 (+2), 0 red;
ctest -L unit 2410 (+8, the gate's rows); informational integration-magma-full-split
513, 0 red, 59 skip (base 510/0/57); fatal_census green (79 abort sites, 43 words, 0
unmarked); link_ratchet --assert-monotone unchanged at 186, p7-magma 101.
G1 holds and it was not obvious: the witness header is compiled into the pull build
too, and its word count changed. Pull .text is still 0xa52203 and the defined-symbol
name list is identical to ~/w7/p7-before/pull-syms.txt line for line (30570) - the
change lands in .rodata and widens no instruction encoding.
VulkanRenderer.h is edited outside this package's file list: the new pass needs four
private declarations next to the colour blit's. Flagged for the integrator.
|
||
|
|
c4be38005a |
[MG_Backend, MG_IntegrationTest, docs] (P7): the shader mip arm names the format it refused and renders through scratch when the image cannot be attached
Two Iris cases died on the Redmi (Adreno 830v2, DirectVulkan inproc) with
Fatal{UnmigratedVerb, "Magma:mipmap-shader-format-or-shape"}, deterministically and
under both run-ahead and lockstep, while the monolith arm and the whole host lane
stayed green. That one line was the entire evidence, and five disjunctions share the
name, so the one thing the device could have told us - which format, at what shape,
missing which feature bit - was the one thing it did not.
The name stays first and unprefixed, because the census, the retrace refusal tally
and every log grep key on the leading word; the driver's own answer follows it:
format enum, the view's storage format when it reinterprets, extent, levels, aspect,
view type, usage bits, layer range, and the names of the optimalTilingFeatures bits
that are ABSENT. The next device run reads a line instead of re-deriving it.
The refusal also loses half its territory. The shader arm rendered STRAIGHT INTO the
next level, which needs the image to carry COLOR_ATTACHMENT usage - and a texture
whose format the device declines as a colour attachment never gets that bit, so every
sampled-only texture without BLIT support fell into the refusal rather than into a
fallback. When the FORMAT can be rendered and only the IMAGE cannot, an owned scratch
attachment of the destination level's size takes the pass and vkCmdCopyImage moves the
mip in: a raw texel move, no conversion, texels still from GPU contents rather than a
staged shadow, and the scratch retires with the submit that consumes it - the shape the
multisample resolve arm above it already uses.
Neither shader arm had ever executed on the gate machine. lavapipe reports
BLIT_SRC|BLIT_DST for every colour format this binary can create, so GenerateWireMipmap
takes its native arm for all of them; GenerateMipmapPackedFloatPixels next door looks
like it covers the shader arm and does not, because R11F_G11F_B10F blits natively here
too. MGITEST_MAGMA_FORCE_SHADER_MIPMAP drops the native arm instead, one tier per shape
(1 = render into the level, 2 = render into scratch and copy back), read once and
compiled only into the disaggregated build.
F1WireScenario.GenerateMipmapWithoutNativeBlitPixels paints two different halves rather
than one flat colour - a pass that loses GL's row order averages to the same number
everywhere, and a single-colour assertion would call that correct - and asserts level
one per half. It is registered on the three Magma arms twice, ONE ENVIRONMENT EACH: the
knob changes how every mipmap in the process is generated, so a lane-wide flip would
silently retarget the four green mipmap entries beside it.
Red once (executed, reverted): with the scratch arm forced off, the tier-2 entry dies
with Fatal{UnmigratedVerb, "Magma:mipmap-shader-format-or-shape format=37 extent=8x8x1
levels=4 aspect=0x1 view=1 usage=0x17 layers=0+1/1 missing=DEPTH_STENCIL_ATTACHMENT"},
by name and with the shape in it; restored, the same filter is 8/8 green.
Gates: integration-magma-split 75 (+2), -spawn 54 (+2), -tcp 56 (+2), 0 red;
ctest -L unit 2402/2402; informational integration-magma-full-split 511, 0 red, 58 skip
(the new case skips where the knob is unset); fatal_census green (79 abort sites, 43
family words, 0 unmarked - no site added, the funnel is unchanged); link_ratchet
--assert-monotone unchanged at 186, p7-magma bucket 101.
|
||
|
|
5937b4fcf2 | [MG_Remote, docs] (P7): CONTRACT-P7 §3.2 records package A (byte-tail retired, ranges clamped, alignment declines), ID-P7-21/22 | ||
|
|
2207b6d9ee |
[docs, MG_Backend, MG_IntegrationTest] (P7): package A's eight refusals are on record, and only real sites still carry @P7
notes/p7/magma-a.md: the per-site table (retired / declined / kept-fatal with the
reason), every red-once with its captured output, the gate readings, and what is
handed on.
The three places package A's own prose still named `uniform-buffer-byte-tail@P7`
lose the marker. They are comments, not refusal sites, and leaving the marker in
would have made a tree-wide `grep @P7` count them against packages B and C. The
census now reads 15 -> 7, and all seven are real: WireFramebuffer.inc x4 and
VulkanRenderer.cpp x1 (package B), WireDraw.inc x2 (package C).
Three findings worth the integrator's attention, all in the note:
1. Most of this cluster was unreachable, and that is provable rather than
assumed. The frontend validates buffer-range offsets against the very caps the
backend then re-checks - GL_{UNIFORM,SHADER_STORAGE,TEXTURE}_BUFFER_OFFSET_ALIGNMENT
and GL_MAX_UNIFORM_BLOCK_SIZE are all published from the same Vulkan limits -
so five of the eight sites could only ever have fired on a malformed peer. The
two that CAN fire are `uniform-buffer-byte-tail` (GL constrains a range's
offset but not its size) and `unaligned-atomic-buffer-range` (GL gives
GL_ATOMIC_COUNTER_BUFFER no queryable alignment, so the frontend only enforces
offset % 4 while the backend needs minStorageBufferOffsetAlignment).
2. fatal_census does not move for any of this, and no re-baseline is due. Wave 0
already routed these funnels through MGPipeSessionFailHook, so the abort lives
in FatalFunnel.cpp and retiring a WireDescriptorFatal call site changes no
census number: 79 abort sites / 43 family words, unchanged, RC=0. The brief
expected the count to fall because the sites were bare aborts before wave 0.
3. DirectVulkan.Split.Full.IterationRPProgram203Scenario.FixedCompleteInputProducesFixedCompleteGoldenOutput
is red on this worktree's build-split and is NOT ours: it reproduces 3/3 with
package A's backend diff fully reverted, and the mismatch bits are identical
to the S1 exposure texel in magma-two-process-first-run.md. The contract books
that one against the tcp arm, and ~/w7/pipe's own build-split passes it, so
whether it is a split-arm regression between 460a3a72 and 610fa281 or a
configure-dependent S1 needs the integrator.
Gates on the finished branch: integration-magma-split 75/75, -spawn 54/54, -tcp
56/56, -buffers 21/21, full-split 512 entries 454 PASS / 57 SKIP / 1 red (the
pre-existing one above; base was 453/57), ctest -L unit 2408/2408,
spawn_lane_parity RC=0, link_ratchet unchanged at 186, fatal_census RC=0, and G1
pull .text = a52203 with nm --defined-only identical to p7-before/pull-syms.txt
(added=0 removed=0).
|
||
|
|
a2d4bc9db6 |
[MG_Backend] (P7): an unsupported texel-buffer format reads zeros instead of killing the session
`texel-buffer-native-format@P7` becomes a decline (rule I (a)), and with it the
last refusal site in UniformManager.cpp is gone. GL's buffer-texture format list
is wider than what Vulkan mandates for texel buffers - GL_RGB32{F,I,UI} and the
16-bit UNORM members are the realistic misses on a real driver - so this is a
device capability gap, not anything the application did wrong and nothing it
could have sent differently.
The placeholder is the right answer here where it was not for the unaligned range
in 7c373d60, and the difference is whether a correct answer exists at all. There
the bytes are addressable and only the descriptor cannot name them, so a
placeholder would have hidden a fixable gap behind silently dropped writes. Here
the device cannot represent this format as a texel buffer in any form, so the
honest observable is monolith's for a missing view: the fetch reads zeros and the
draw survives. AcquireUnboundTexelBufferView already falls back to the R32 member
of the shader's numeric class, whose texel-buffer support is mandatory, so the
fallback cannot itself fail for want of device features.
red-once (R-16). lavapipe supports every format the buffer-texture cases use, so
the site is not reachable on the host - the red is the same case with the device
support check forced to fail, both shapes built and run back to back:
DirectVulkan.{Split,Spawn}.Full.BufferTextureScenario.VertexStageTexelFetchReadsTheBufferAndTracksItsUpdates
unforced 2/2 Passed (the format IS supported here)
before (7c373d60 + forced) Subprocess aborted -
Fatal{UnmigratedVerb, "Magma:texel-buffer-native-format@P7"}
after (this commit + forced) ***Failed on the texel data and NOT on a Fatal:
the fetch read the zero placeholder, the session lived
restored 2/2 Passed, no force markers left
Which format actually trips this is a device question, not a host one. Adreno is
where GL_RGB32F buffer textures are expected to miss, so the decline's real
observable wants a device pass; notes/p7/magma-a.md lists it.
Lanes: integration-magma-split 75/75, -spawn 54/54, -tcp 56/56,
integration-magma-buffers 21/21. `grep -n '@P7'` on this file now returns one
line, and it is the comment that records the A.1 retirement - no refusal sites
remain.
|
||
|
|
ae57d01168 |
[MG_Backend, MG_IntegrationTest] (P7): an unaligned counter range declines its binding instead of killing the session
`unaligned-atomic-buffer-range@P7`, `unaligned-storage-buffer-range@P7` and
`unaligned-texel-buffer-range@P7` become named declines (rule I (a)). The two
offset-overflow guards next to them keep their named Fatal under their own words:
those are arithmetic that cannot happen, not shapes an application can bind.
The atomic-counter one is the only member of this cluster an application can
actually reach, and it is a real live gap, not a hypothetical. GL 4.6 core 6.1.1
gives GL_ATOMIC_COUNTER_BUFFER no queryable offset alignment, so
ValidateBufferRangeOffsetAndSize only enforces offset % 4 on it - while glslang
lowers every atomic_uint onto a synthesized storage block whose descriptor offset
must be a multiple of minStorageBufferOffsetAlignment (16 on lavapipe, 64 on
Adreno). `glBindBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0, buf, 4, 4)` is a legal
GL bind that this backend cannot express, and it used to take the session with
it. The plain SSBO and texel halves are unreachable by comparison:
GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT and GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT
are published from the same Vulkan limits, so the frontend already answers
GL_INVALID_VALUE.
DECLINED RATHER THAN COPIED, and this is the package's one real design choice.
Copying the window into an aligned transient slice reads correctly and then
silently DROPS every shader write. This site cannot tell a writable storage block
from a read-only one - there is no readonly bit in the storage-block reflection,
an atomic counter is written by definition, and MarkWireBufferGpuWritten right
below already assumes any binding here may be written. A correct copy needs a
post-dispatch copy-back from the transient slice to the unaligned source, ordered
with a barrier, and the place to record that is the draw/dispatch tail in
VulkanRenderer.cpp / WireDraw.inc - wave-2 packages B and C, not this file.
Returning stale counters with no error is worse than losing the dispatch with a
named line in the role log. notes/p7/magma-a.md carries the recipe for whoever
holds those files.
The texel half keeps the draw where it safely can: a read-only buffer texture
falls back to the same placeholder view an unbound one gets (zeros, which is what
a missing view gives in monolith), while a writable imageBuffer declines - the
placeholder is shared by every unbound binding, so absorbing real stores into it
would alias other bindings as well as lose the writes. Copying is not available
there either: the transient arena carries no {UNIFORM,STORAGE}_TEXEL_BUFFER
usage, so no VkBufferView can be made over it, and widening a shared per-frame
allocation to buy an unreachable path is the wrong trade.
red-once (R-16), a reachable case this time, all three Magma two-process arms:
DirectVulkan.{Split,Spawn,Tcp}.Buffers.F1WireScenario.UnalignedAtomicCounterRangeDeclinesAndTheSessionLives
before Subprocess aborted - Fatal{UnmigratedVerb, "Magma:unaligned-atomic-buffer-range@P7"}
after 3/3 Passed, and the role log carries
"block 'gl_AtomicCounterBlock_0' is bound at offset 4, which is not a
multiple of this device's minStorageBufferOffsetAlignment (16);
atomic-counter ranges cannot be expressed as a descriptor ... DECLINED
(unaligned-atomic-buffer-range)"
The case pins the decline's whole observable: the bind raises no GL error, the
dispatch does not run, every byte of the counter buffer is unchanged, and the
context still serves ordinary work afterwards. A future copy-back turns one
EXPECT into seed[1] + 1 and leaves the rest standing.
Lanes: integration-magma-split 75/75, -spawn 54/54, -tcp 56/56,
integration-magma-buffers 21/21.
|
||
|
|
de049f9ea0 |
[MG_Backend] (P7): a range past the device's native limit is clamped, not refused
`uniform-block-native-range@P7` and `storage-buffer-native-range@P7` both become
clamps with an MGLOG_E_ONCE (rule I (a)). Neither is reachable from a conformant
program: GL_MAX_UNIFORM_BLOCK_SIZE is published straight from the same
maxUniformBufferRange and GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT /
GL_MAX_SHADER_STORAGE_BLOCK_SIZE from the same storage limits
(BackendLoaders/Vulkan/Loader.cpp), so a block this big fails to link long before
a draw. Refusing was a Fatal guarding a shape nothing can produce.
The monolith arm is the argument. It binds whatever the GL range asked for and
never consults maxStorageBufferRange at all (ResolveStorageBufferDescriptor's
resident path), so the wire arm was inventing a death the other backend does not
have. If the two limits ever disagree, the honest answer is the bytes the device
CAN name: everything past the clamp is outside the descriptor and reads back as
zero under robustBufferAccess, which is inside GL's "undefined" for a block the
implementation never promised to hold. Clamping keeps the range constant across
draws, so the descriptor-set reuse hash is unaffected.
red-once (R-16). Neither site is reachable from a test, so the red is the same
two cases with the device limit forced down to 16 bytes, both shapes built and
run back to back on the Split and Spawn arms:
uniform-block-native-range (reflected block 32, limit forced to 16)
before Subprocess aborted - Fatal{UnmigratedVerb, "Magma:uniform-block-native-range@P7"}
after BOTH ARMS PASSED. The clamp keeps every byte the descriptor can name,
so the case's own pixel is unchanged, and the role log carries
"reflected uniform block 0 is 32 bytes, past this device's
maxUniformBufferRange of 16; the binding is clamped to the limit
and the remainder reads zero"
storage-buffer-native-range (block 'Output' 24 bytes, limit forced to 16)
before Subprocess aborted - Fatal{UnmigratedVerb, "Magma:storage-buffer-native-range@P7"}
after ***Failed on the data and NOT on a Fatal - the session lives, 8 of the
24 bytes are genuinely past what the forced limit can name, and the
role log carries "block 'Output' bound 24 bytes, past this device's
maxStorageBufferRange of 16; the binding is clamped to the limit"
restored (this commit as committed): 4/4 green, no force markers left.
Lanes: integration-magma-split 74/74, -spawn 53/53, -tcp 55/55,
integration-magma-buffers 20/20.
|
||
|
|
362b27df8c |
[MG_Backend] (P7): a transient ring past 4 GiB declines its uniform binding instead of killing the session
`uniform-buffer-dynamic-offset@P7` becomes a decline (rule I (a)). A Vulkan
dynamic offset is a uint32_t, so a transient uniform ring that grew past 4 GiB
inside one frame has nowhere to put the padded block - which is our ring's
problem, not a protocol fault, and nothing the peer could have sent differently.
Killing the session for it was the wrong shape twice over.
The monolith arm has the same ceiling and truncates silently on the way out
(ResolveDynamicUboDescriptor's static_cast<Uint32>), i.e. it binds a WRONG window
rather than none. Declining is the strictly better observable of the two: one
binding is skipped, the draw is lost, the session keeps running, and the next
frame's rewound ring resolves it.
red-once (R-16). The site is not reachable from a test - a 4 GiB transient arena
inside one frame is not something an integration case can build - so the red is
the same case with the ceiling condition forced true, both shapes built and run
back to back on the Split and Spawn arms:
before (fb25ed6b + forced condition)
DirectVulkan.Split.Buffers.F1WireScenario.ShortUniformBufferRangePadsMissingBytes
DirectVulkan.Spawn.Buffers.F1WireScenario.ShortUniformBufferRangePadsMissingBytes
Subprocess aborted - MGPipe: Fatal{UnmigratedVerb, "Magma:uniform-buffer-dynamic-offset@P7"}
after (this commit + forced condition)
both ***Failed on the pixel, NO Fatal and NO abort, and the role log carries
ResolveWireUniformBufferPayload: the transient uniform ring reached offset 0,
past the 4 GiB a Vulkan dynamic offset can name; uniform binding point 3 is
declined for this draw
restored (this commit as committed): both green, no force markers left.
Lanes: integration-magma-split 74/74, -spawn 53/53, integration-magma-buffers
20/20. -tcp not read this round: port 40613 is held by a concurrently running
lane in another worktree, which is a fixture collision and not a result.
|
||
|
|
450d30759e |
[MG_Backend, MG_IntegrationTest] (P7): a uniform range that ends mid-word is copied, not refused
`uniform-buffer-byte-tail@P7` is retired. It was the live boundary the Redmi Minecraft run reported (notes/p5f/magma-inproc-fix.md #5) and, since wave 0, a named Fatal that took the session with it. Only the SIZE of a uniform range can land off a word. glBindBufferRange already forces the offset onto GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT and records GL_INVALID_VALUE otherwise (GL_Buffer.cpp ValidateBufferRangeOffsetAndSize), and every device reports that as a multiple of four - but GL 4.6 core 6.1.1 puts no such rule on size, so a 19-byte bind is legal and 19 % 4 is not 0. The sub-word start is only reachable protocol-side; the new arm handles it anyway rather than leaving a second shape to trip over later. CopyWireBufferSubWordRangeToSlice rounds the read of the APPLICATION's store OUT to whole words, clamped to the store's end so the widened window can never touch a byte the application does not own, and lands it in a transient staging slice. Only the staging -> padded-block shift is sub-word, and that happens inside our own arena; vkCmdCopyBuffer places no alignment rule on a region's offsets or size (unlike vkCmdUpdateBuffer / vkCmdFillBuffer), so the shift is a plain legal copy. The block still starts at the padded slice's own offset, which is what lets the descriptor keep a minUniformBufferOffsetAlignment-aligned dynamic offset. red-once (R-16), all three Magma two-process arms, before the backend half: DirectVulkan.Split.Buffers.F1WireScenario.SubWordUniformBufferRangePadsMissingBytes DirectVulkan.Spawn.Buffers.F1WireScenario.SubWordUniformBufferRangePadsMissingBytes DirectVulkan.Tcp.Buffers.F1WireScenario.SubWordUniformBufferRangePadsMissingBytes Subprocess aborted - MGPipe: Fatal{UnmigratedVerb, "Magma:uniform-buffer-byte-tail@P7"} and green after it. The case is the sibling of ShortUniformBufferRangePadsMissingBytes with a 19-byte range; the byte just past the range is poisoned, so a rounding that leaks it reads tail.x as 0.5f and the red channel comes back 127 instead of 0. Lanes: integration-magma-split 74 (was 73), -spawn 53 (52), -tcp 55 (54), integration-magma-buffers 20 (19), all green. fatal_census unchanged at 79 abort sites / 43 family words - these refusals stopped being bare aborts in wave 0, so a retirement moves no census number and needs no re-baseline. link_ratchet unchanged at 186. |
||
|
|
cf7ca59fab | [MG_Remote, docs] (P7): CONTRACT-P7 records package C - vertex-layout split (seven masked reasons), the death-ops debt retired, the opcode-49 white-box deferred to P11, ID-P7-20 | ||
|
|
0acfa9aab9 |
[MG_State, MG_Backend, MG_Test, docs] (P7): the reflection archive publishes the storage-block index space, and both magma consumers read it
CONTRACT-P7 §5.3, OQ-8. MagmaProgramSource::EnsureStorageBlocks re-ran SPIRV-Reflect over
every stage module of the program PER DRAW: the class is a borrowed view that
WireDraw.inc's WireProgramSource constructs afresh for each wire draw, so its `mutable`
memo was cold every time it was read. The list it rebuilt is a pure function of the linked
program, so it is now built once at link time and travels in the archive.
LinkArtifacts gains storageBlocks{name, binding, dataSize} with its own VisitFields table,
the codec version goes 2 -> 3, and the schema fingerprint and wireFingerprint move with
it - which is expected, and is what makes a mixed-version pair refuse at the handshake
instead of at the first program.
FILLED IN PHASE A, and that is not where it looks like it belongs. The only function that
sees both finished halves is ProgramSpirvTask's L1 cache insert, but its writes reach the
TRANSLATION CACHE only: phase A's artifacts have already been moved into the ProgramObject
by then. A field filled there would be present on a cache hit and absent on every cold
link. So the fill is glslang-derived, and the equivalence is MEASURED rather than assumed:
ProgramTest.TheArchivesStorageBlockOrderIsTheOneSpirvReflectProduces runs the old
consumer's own algorithm over the real modules and compares element for element, on a
multi-stage program with an arrayed SSBO and an atomic-counter block - the three cases
that could have diverged (the instance array collapses to one entry, the synthesized
counter block IS in the backend's space although GL's enumeration excludes it, and the
cross-stage dedupe walks stages in linked order).
MEASURED, same binary and same window (frames=1 draws=1 acc=18, identical bytes),
DirectVulkan x inproc over the SSBO scenarios: spvReflectCreateShaderModule on the wire
draw path 4 -> 0. The 0 is structural: the call site is gone and MagmaProgramSource.h no
longer includes spirv_reflect.h at all, which is the gate a future edit breaks on purpose.
THE MONOLITH CONSUMER IS SWITCHED TOO, under #if symmetry, so the disaggregated build
retires g_programResourceCaches, ProgramResourceCache, StorageBlockResource,
BufferVariableResource, AddBufferVariablesRecursive, NormalizeDescriptorName,
GetProgramResourceCache and the rehash hazard - the open-addressed-map dangling reference
that was a reproducible segfault in ProgramPipelineScenario's two storage-block cases.
Nothing outside DirectVulkan.cpp read any of it (measured; GL_BUFFER_VARIABLE and
GL_ACTIVE_VARIABLES are answered by ProgramInterface.cpp out of a different index space),
so the two arms differ in exactly the two functions their callers use.
THE PULL BUILD KEEPS ALL OF IT, and the integrator should see why: the archive member is
MOBILEGL_BUILD_DISAGGREGATED-only because one more Vector in LinkArtifacts changes its
size, its implicit destructor and its move constructor, and G1 pins the pull build's .text
byte for byte. So §5.3's deletion is achieved in the disaggregated build and not in the
pull one; retiring the pull half is a one-line change the day that pin is retired or the
member moves. Filed as P13 in notes/p7/magma-c.md.
One improvement rides along: the monolith binding getter now asks the program for a
rebind on every read, over an immutable list, where it used to read a binding patched into
a mutable cache - so a rebind can no longer be lost to a cache rebuild that raced a
state-version bump.
G1: .text 0xa52203 unchanged, 27837 -> 27837 defined symbols, 0 added / 0 removed / 0
resized / 0 renamed, file byte-identical. An unguarded call site caught the guard rule
once mid-slice by failing the pull compile, which is the loud version.
RED-ONCE: publishing nothing reds the order-equivalence case (with the reference list
printed) and 25 of 60 storage-block scenarios - the MONOLITH DirectVulkan arm beside all
three transports, which is the evidence that the monolith consumer really was switched.
Restored: 61/61.
|
||
|
|
f1b04edfc6 |
[MG_Backend, MG_IntegrationTest, docs] (P7): the magma vertex-layout refusal splits into one protocol fatal and seven masked attributes
CONTRACT-P7 §3.2's `vertex-layout` and `vertex-format-conversion` rows.
BuildWireVertexInput answered eight different conditions with one `false`, and the single
caller answered that with a P7-marked MagmaWireFatal. So a Magma split session that met a
GL_FIXED array, a dvec on a device with native fp64, or any format without
VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT did not decline the draw: it killed the process. The
monolith arm on the same device has always masked the attribute out and then declined the
draw loudly and once.
The split is by what the reason is ABOUT, not by severity. `buffer-window` is a statement
about the RECORD - an enabled attribute the program reads whose slot lies outside the
window set_vertex_buffers published - so it stays a named Fatal through the hook; it is
what caught the Redmi VAO hash collision fixed in
|
||
|
|
1255caa2fb |
[MG_Backend, MG_Impl, MG_Util, MG_Test, MG_IntegrationTest, docs] (P7): the server publishes kCapResidentSubData off its own wire resource table
CONTRACT-P7 §5.4, OQ-10. InitServerRoleCommon computes the whole capability mask and
never ORed this bit, so MGPipeResourceOpsHaveSubDataResident answered false under every
transport and BOTH backends were pinned to the ordered in-place host memcpy - which made
Magma's SubDataResident arm (VkBufferManager.cpp's g_vulkanWireResourceOps) dead code on
the wire even though the arm has been there since P5f.
The bit is derived from g_resourceOpsAtStep2->SubDataResident, i.e. from the pointer this
function already pinned at step 2 and that step 5 already refuses to see swapped - never
from ActiveBackendType. That is ID-39's lesson and not a style preference: a mask is a
statement about THIS backend's registered table, and a bit derived from the enum would
keep claiming the capability for a build, a flavour or a future backend whose table does
not carry the arm.
`rsd=` joins the emit[] bracket on the PipeStats summary line, counted at the emitter once
per buffer_subdata_resident record rather than once per call, because a write past the
content chunk cap is cut into several records. It is the only observable that separates
the resident arm from the memcpy beside it: the bytes in the buffer are identical either
way, which is exactly how the capability stayed unpublished for a phase with every lane
green.
WHAT THIS DOES NOT YET PROVE, and R-6 is the reason rather than an omission. §5.4 asks for
a white-box case showing opcode 49 on the wire. None can exist at this tree: the resident
arm is reached only from a GPU-RESIDENT store, residency comes only from
AdoptPersistentMap, and every persistent-map acquisition declines under split by R-6
(PipeApply.cpp:2331 "A SPLIT BUILD RUNS AT TIER T2 AND DECLINES EVERY ACQUISITION,
ALWAYS"; T0/T1 are P11's and AdoptTierIsEmulate is a named Fatal for them). So the
capability is wired AHEAD of its consumer, deliberately, and `rsd=` is what will show the
records the day P11 lands a tier that adopts. Filed for the integrator in
notes/p7/magma-c.md.
What IS proved is both halves of the bit itself. RemoteClientTest's Magma bootstrap case
now asserts the published bit EQUALS `ops->SubDataResident != nullptr` - the two compared
to each other rather than both to true, so the case also covers a backend without the arm
on the day one exists - and a new CtWireScenario case reads the bit out of the client's
own caps mirror, through the same accessor the frontend reads, on both backends and all
three transports.
RED-ONCE, both levels. Deleting the OR reds
CapsMirrorTest.MagmaTransportPublishesRealBufferConsumersWithoutRunAhead
(RemoteClientTest.cpp:692, "Which is: false / Which is: true") and all SIX arms of
DirectGLES/DirectVulkan x {Split,Spawn,Tcp} Ct.TheServerPublishesTheResidentSubData
CapabilityFromItsOwnTable ("the client's caps mirror did not receive
kCapResidentSubData"). Restored: green.
Also carries the P4a death-helper comment from the previous commit's sweep: it no longer
says Magma installs no death table, because it now does and still frees no slot.
|
||
|
|
dce9953d36 |
[MG_Backend, MG_State, MG_Pipe, MG_Remote, MG_Impl, MG_IntegrationTest, docs] (P7): magma installs a state-object death table, and the inproc arm stops losing every object_death
CONTRACT-P7 §5.5. DirectVulkan had no StateObjectDeathOps at all, so under a Magma
inproc session NotifyStateObjectDestroyed reached nobody and the server was never told
that a frontend object had died. Package L measured the shape: with the WireTables
install condition made backend-agnostic, CtWireScenario's two death cases PASS on Magma
spawn and tcp - the remote client has no local backend, so the client-side emitter
installed there answers the notice - and FAIL on inproc, where the install condition
deliberately stands aside for "the backend's own dispatcher" and Magma had none.
g_magmaStateObjectDeathOps is that dispatcher. It mirrors g_glesStateObjectDeathOps
(DirectGLES/Managers.cpp:335) with ONE arm rather than two: Espryt's other arm destroys
a backend twin keyed by lifetime id, and Magma keeps no such twin - its two
client-minted kinds are keyed on {slot, gen} by MagmaPipeIdentityTables and the other
six are still reached from their frontend objects - so what is left is exactly the emit
of object_death off the apply thread. Installed from
BackendObject_DirectVulkan::Initialize(), i.e. ServerLoop::CreateBackend, step 1 of
InitServerRoleCommon: the one place BOTH Magma server roles pass through, and before any
client session exists. #if MOBILEGL_BUILD_DISAGGREGATED && MOBILEGL_PIPE_PUSH, so the
pull build compiles nothing new.
The two skip guards in CtWireScenario go with it. Every other "Magma installs none" site
the audit found is comment text whose ARGUMENT survives, and each is reworded rather than
deleted: the table emits the wire record and frees no client slot, so P4a's
backend-neutral NotifyAndFree is still the only thing that returns one, HandleRecycle's
C-1 case still measures the only protection the allocator has, and MagmaPipeArms' age
sweep is still the only thing that retires an identity slot.
The framebuffer death case now reads the client's allocator beside the server's tally.
That correction matters: the plan called this a slot leak provable with PipeSlotPeek, and
it is not - it has been backend-neutral since P4a. What leaked was the SERVER's twin, and
a framebuffer has no wire delete opcode at all, so object_death is its only delivery.
Two installers cannot race although the global is last-writer-wins with no stacking:
Magma's runs only in a process that owns a DirectVulkan backend, and
InstallClientWireTables' arm runs only under Transport == Spawn, which is by construction
the process that owns none.
RED-ONCE: `if (false) InstallStateObjectDeathOps();` reds the two Split cases alone
(`Expected: (afterCounters.deaths) > (deathsBefore), actual: 0 vs 0`, CtWireScenario.cpp:264)
and leaves spawn and tcp green - the exact split of responsibility the comment claims.
Restored: 8/8. Recorded in docs/Disaggregated/notes/p7/magma-c.md.
|
||
|
|
22c7361c5f |
[MG_Test] (P7): the placeholder caps cases assert the refusal and the wait instead of the answer
`CapsMirrorTest.APlaceholderMirrorConsumesNothing` asserted the defect F1 closes -
"with no snapshot the mask is zero, every family answers no consumer, the client
emits nothing and the legacy pull path runs" - and it was green for five packages
because nothing on the client ever checked whether that pull path exists. It does
not. Four cases replace it, and each one is the control for a different way the
new gate could go wrong:
* `APlaceholderMirrorRefusesToAnswerAndNamesTheFamily` forks, asks a placeholder
mirror about TextureResources with no session anywhere, and requires the death
to carry `Fatal{CapsBeforeFirstSnapshot, "TextureResources"}` AND `0x400` -
R-16's rule, because "something aborted" is satisfied by any of the other 43
family words.
* `ThePlaceholderWaitsForASnapshotAnotherThreadIsFetching` drives the PRODUCTION
bring-up (`MG_Backend::Init`) with `MOBILEGL_TEST_DELAY_FIRST_CAPS_MS=50` and
asks from a second thread. The wait must return the server's real answer, not
the placeholder's false and not a death. It is the only shape in which "block
with a bounded wait" is a correct answer to F1.
* `ARealSnapshotThatWithholdsAFamilyStillAnswersFalse` is what keeps this from
being a callMask defaulted to all-ones: a server that really consumes nothing
is still answered "no", counted and named. Only the PLACEHOLDER is refused.
* `TheReadAccessorsStillAnswerFromThePlaceholder` pins the half of P5's ruling
that F1 does NOT narrow.
`ResetCapsMirrorForTest()` is new and test-only: a fork child inherits whatever the
previous case adopted, so without it a case about the placeholder would be a case
about that snapshot.
`ServerLoopTest`'s main gains no code, only the sentence that explains why it has
none: it sets `MG_Config::Transport` by hand and never runs `MG_ConfigLoader::Init`,
which is exactly what keeps the client-side gate disarmed for a binary that builds
server sessions and the server's own frontend GLContext and never a client at all.
|
||
|
|
df17c416c0 |
[MobileGL, MG_Backend, MG_Remote, scripts] (P7): the split wire comes up before the first frontend object, and the caps mirror stops deciding a family's fate from a placeholder
F1. `MG_State::Init()` runs before `MG_Backend::Init()`, and the second is what
starts the session - so `GLContext`'s `TextureState` constructor materialised its
eleven default texture objects, each of them emitted `resource_create` through
`FamilyIsLive`, and `CapsMirror::ServerConsumes(0x400)` answered every one of them
from a generation-0 placeholder with `callMask=0`. All eleven records were dropped
and the whole event was one WARN line:
MG_Remote client: the server does not consume MGPipe subsystem 0x400 - this
family emits NOTHING and the legacy pull path runs for it (R-8). callMask=0x0,
caps generation 0
It is NOT a race. The line reproduces byte-for-byte on lavapipe and on the Adreno
830, because the two Init calls are two statements on one thread. And "the legacy
pull path runs for it" is false on the client under split: there is no pull path
on this side of the wire, so withholding is not a fallback, it is a drop that only
a later respecify's self-healing create ever undoes.
Two of the three options the brief offered do not apply, and both facts are worth
recording. (b) "extend the P6.5 fix to inproc" is already the tree: `FinishStartup`
step 7 is the one funnel every transport reaches and it will not return without a
real snapshot. (a) "a bounded wait in Consumes()" cannot work alone and would
deadlock: at `MG_State::Init()` no handshake is in flight, and the one that will
land is started later BY THE SAME THREAD.
So the fix is the ORDER, with the rule behind it as the gate:
* `MobileGL::Initialize` calls `MG_Backend::InitSplitRolesBeforeState()` before
`MG_State::Init()`. `Init()`'s split arm becomes one body, `InitSplitBackend()`,
shared with the direct callers that still reach `Init()`; the monolith arm keeps
its place after `MG_State::Init()`, because moving it would move pull `.text`.
* `CapsMirror::ServerConsumes()` refuses to answer from a placeholder. It waits,
bounded, when a bring-up is genuinely in flight on ANOTHER thread, and otherwise
dies as `Fatal{CapsBeforeFirstSnapshot, "<family>"}` through `SessionFail` -
a new family word, one row in `FatalFamilies.def`, no new abort site.
* Two arms are exempt and say why in place: the server arm (`MGPipeServerArm()` -
the apply thread and the spawn server, where the client's mirror is the wrong
object rather than a late one), and a process whose own configuration never
asked for a split transport (`MG_Config::SplitTransportRequestedByConfig`, set
by `MG_ConfigLoader::InitTransport`, which is what tells a real run apart from a
fixture that assigns `MG_Config::Transport` by hand).
The read accessors keep P5's ruling untouched: `LogBackendInfo` reads
`GetRendererInfo()` during `MG_Backend::Init()` and an abort there is a process that
cannot start. Only the DECISION changed. The warning was not made quieter and the
callMask was not defaulted to all-ones.
`MOBILEGL_TEST_DELAY_FIRST_CAPS_MS` (server side, disaggregated builds, inert unless
set) holds back the FIRST CapsSnapshot. On spawn/tcp that is a genuinely late
snapshot in another process and it exercises the client's step-7 wait; on inproc it
only makes `Start` slower, which is itself the proof that the only window a client
can read a placeholder in is the one before `Start` is called.
G1: `.text` 0xa52203 and the 30570-symbol list identical to p7-before/pull-syms.txt.
Census: 79 abort sites unchanged, 44 family words (was 43). Link ratchet: 186,
unchanged.
|
||
|
|
5c4235d7ba | [docs, MG_Remote] (P7): ID-P7-12..17 - gate 3 per case against monolith, photon excluded, wave-0 naming ratified, X1 on the critical path, gate 5 is five blocks at GL46, E2/E0b folded into the F1 re-test | ||
|
|
4cbf78c269 |
[MG_Remote, MG_Test, docs] (P7): the persistent-map tracker answers faults in the kernel's address space, and a map hands back a range it can write
Wave 2 package X1. Three CI split-subset traces died on the Redmi under inproc and
passed under monolith on the same device and APK, on the client thread, in the
replayer's memcpy into a pointer MobileGL had handed it: SIGSEGV / SEGV_ACCERR on the
destination's FIRST byte, destination page-aligned.
The tombstone names the same address twice, in two different address spaces:
x0 b4000073ac9a5000 ... x2 0000000001800000
signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x00000073ac9a5000
x0 is the pointer the application writes through, carrying bionic's 0xb4 heap tag
(the same tombstone reports tagged_addr_ctrl PR_TAGGED_ADDR_ENABLE). fault addr is
si_addr, which the kernel reports UNTAGGED.
TrackWriteMap took shadowBase from MappedData() - a tagged heap pointer - kept the tag
through the page masking, mprotect()ed the mapped range PROT_READ and published the
TAGGED base/end into the slot. The handler compared si_addr against it:
if (base <= kSlotSettingUp || address < base) continue;
0x73ac9a5000 < 0xb4000073ac9a5000 is true for every fault, forever. Ownership test (2)
never matched, and every fault on a page the tracker had itself protected was chained
to debuggerd. mprotect was fine throughout - do_mprotect_pkey untags its own argument -
so the right pages really were read-only; only the recognition was broken. x86-64 has
no top-byte tag, which is why every host lane has always been green.
The fix is two rows and one ordering:
* UntagAddress(), applied to the shadow base at registration, to the shadow base in
PushBlocksFor (so trackedBase - shadowBase stays in one space) and to si_addr in
the handler. Written WITHOUT an #if defined(__aarch64__): masking bits 56-63 is
the identity for every valid userspace address off arm64, and an arch guard would
have put the only gate that can catch this on the one platform CI never runs.
Dereferences keep the tagged pointer they came from.
* SlotOwnsFault(), the ownership row the handler and the unit case both read.
* REGISTRATION ARMS NOTHING. AcquireMemoryRange registered and only then returned
Bytes() + range.start, so glMapBufferRange handed the application a PROT_READ
pointer whose writability depended on a handler reaching the writing thread - and
the application is free to write a mapped arena from a worker that blocks every
signal. Nothing is lost by waiting: every page bit is already set at registration,
so the first push ships the whole interior regardless, and it is that push which
arms the pages. 6144 pointless faults per 24 MB arena go with it, and the
invariant the file rests on (bit set <=> page writable) is now true from
registration onward instead of only from the first push.
Diagnostic, independent of the fix: one MGLOG_I_ONCE at the first registration naming
the mapping the way the device run needed and did not have (allocator, pointer, ITS
TAG, extent, range, published span, pages, alignment arm), and a decline record in the
handler - relaxed atomic stores plus one async-signal-safe write(2) - readable through
DeclinedFaults(). A non-zero decline count whose si_addr lies inside nearest-tracked
once the top byte is masked off is this defect's signature and nothing else's.
Not the mechanism, and checked: libsigchain (the tombstone's "F libc : Fatal signal 11"
is bionic's own handler, so the chain ran), the 16 KiB-page refusal (the device is a
4 KiB kernel and the protection demonstrably took), RefaultOfANonWriteAccess (matched
is false long before it is consulted), and the P5d shadow allocator (correct - it is
what makes the destination page-aligned in the tombstone).
Red-once, two and orthogonal (MG_Test/Wire/PersistentMapFaultTest.cpp, 6 cases):
dropping the two UntagAddress calls in SlotOwnsFault reds only
AKernelsUntaggedFaultAddressIsOwnedByATaggedShadowsSlot, which runs the device's own
two addresses through the real predicate on an x86-64 host; restoring the mprotect at
the end of TrackWriteMap reds only AMapHandsBackARangeWritableWithoutTheFaultHandler
(the child dies by SIGSEGV) and RegistrationLeavesEveryPageOfTheRangeReadWrite.
AnArmedPageIsStillAnsweredByTheHandlerAndMadeWritable is the anti-vacuity case.
Gates: ctest -L unit 2408/2408; integration-split 186/186; integration-spawn 102/102;
integration-magma-split 73/73 (PersistentCoherentMapScenario green in the Split, Spawn,
PersistentMapArm and SmallRing variants); fatal_census.py 0 unmarked. G1 holds - pull
.text 10822147 = 0xa52203 unchanged, symbol report 0 added / 0 removed / 0 resized /
0 renamed, nm --defined-only identical at 30570 and nm -D at 11933. Every line is under
MOBILEGL_BUILD_DISAGGREGATED (CMakeLists.txt:592-659), so the pull build sees none of it.
Evidence and the device experiment: docs/Disaggregated/notes/p7/x1-persistent-map-segv.md
|
||
|
|
4f2d4c4e83 |
[MG_Remote, docs] (P7): CONTRACT-P7 carries wave 0's numbers, and device window #1a is on record
CONTRACT-P7: the lane counts (magma-split 73 / spawn 52 / tcp 54; full-suite 510 x 3 with one StreamLink x Magma red), the census baseline (79 abort sites over 20 files, 43 family words, 0 unmarked), the ratchet baseline (186 = p7-magma 101 / espryt 14 / both 60 / core 11; espryt's clearable count is 9) and the @P7 count (15 sites / 4 files) replace the 待填 markers. Device window #1a (Redmi 2f7cbe2e, APK p7w1-d260f110, reboot-clean): E0a monolith 39/39 deterministic, three below threshold on monolith itself (create-indirect, 1.21.11 main-menu, iris-photon - Adreno refuses its pipelines with VK_ERROR_UNKNOWN); E0a inproc: OpenRA 0.976494 (run-ahead) / E1 lockstep 0.988998 - both historical numbers were OpenRA, and its client log shows a TextureResources verb answered from caps generation 0 (package F1); 26.3 / create-* SIGSEGV in the replayer's memcpy on BOTH backends and with the persistent-map push off (package X1); iterationt x2 die by name on Magma:mipmap-shader-format-or-shape (package B); bsl-esc-menu hits scudo OOM under inproc. Gate 3's denominator is 36; run-ahead and present credit are off the suspect list. |
||
|
|
8bb6309a1c |
[scripts, CI, docs] (P7): the 184-symbol link-closure ratchet lands as a gate
CONTRACT-P6.md 12.1 has carried "a ratchet recomputes the 184 in CI and asserts it only
ever falls" as an obligation since P6. The roadmap handed it to P6.5; the P6.5 line never
received it, and scripts/ci/ has never had anything that computes this number. ID-P7-7
gives it to P7 wave 0. This is it.
scripts/link_ratchet.py mechanises a6's hand experiment: partition the per-object output
of the single MobileGL target into SERVER / FRONTEND / SHARED, then compute
(SERVER undefined) - (SERVER defined) - (SHARED defined) intersect (FRONTEND defined),
which is the undefined-symbol list a real link of a server-only image would print. The
partition is an explicit table with a rationale per row, because it IS the module boundary
the build system does not have (SOURCE_FILES is one flat list; boundaries are P13's work).
An object matching no row is a hard failure, as is a build whose CMakeCache lacks the three
disaggregated options, or an empty role set - every way this gate could go green without
having measured anything is an explicit exit 2.
Measured on
|
||
|
|
1de8f02ebe |
[MG_IntegrationTest, docs] (P7): the whole integration binary runs under Magma split, and it is one red
Tier 2 replays the split-arm registrations - ~100 scenarios, the set Espryt's disaggregation work
has carried since P5 - and it measured green on Magma across all three transports. That is a real
result but it is NOT exit gate 1. The gate says the INTEGRATION BINARY is green under Magma split,
and the set that names is the one the monolith DirectVulkan. arm runs: the whole suite minus
MGL_ITEST_MONOLITH_MECHANISM_FILTER. 510 cases against tier 2's 102, and the readback matrices,
the same-texture copy shapes and the odd-offset uniform ranges are in it and nowhere else in the
disaggregated registry.
integration-magma-full-{split,spawn,tcp}, in no gating step, private log per entry through a list
of its own. THE RESULT: 453/0/57 on inproc, 453/0/57 on spawn, 454/1/57 on tcp - and not one of
the 1530 entries reaches any of the tree's fifteen @P7 named refusals. The single red is
IterationRPProgram203Scenario.FixedCompleteInputProducesFixedCompleteGoldenOutput on the TCP arm:
one texel of 262656, texel 0, which is the case's own exposure value
(IterationRPProgram203Scenario.cpp:371, actual half bits (0x357a, 0x4b65) against golden
(0x3a74, 0x4821)). Deterministic 5/5 on tcp; 5/5 GREEN on inproc, on spawn and on the monolith
control. spawn and tcp differ only in MOBILEGL_IPC_DATA, so the suspicion is the STREAM data path
and not "two processes"; the GLES tcp lane is green in the same run, so it is not the fixture.
Not fixed here - it is what the census is for.
TWO ENVIRONMENT DIFFERENCES FROM THE GATED TIERS, both measured rather than assumed:
* NO MGITEST_SPLIT_LANE=1. That marker arms ScenarioFixture's destructor assertion - a case in
an armed split lane must have moved the client encoder's record ordinal - which is right for
a curated lane and wrong for a whole-binary census. With it set, the first run produced 13
reds, all `EmitSeq was 20 at SetUp and is 20 now`: AdvertisedLimitsScenario (8),
SpirvShaderBinaryScenario (4) and ProgramPipelineScenario.AReservedName - a limits query is
answered from the client's CapsMirror and a shader-binary error surface never reaches a verb.
Those 13 would red on a DirectGLES census too. What it gives up is stated in the comment: this
tier does not prove its records crossed, and the tcp topology proof is inert in it.
* DualBlockScenario.* filtered out. Its green REQUIRES MOBILEGL_IPC_ROLE_SPLIT_STATE=1 and a
census must not set that knob; without it the case is red by name, which is its documented
negative-control behaviour. The monolith arm skips it for the same class of reason.
CtWireScenario gets MOBILEGL_TEST_SERVER_COUNTERS=1 in this tier - a switch that makes a counter
readable, not an oracle that asserts - because without it the spawn arm's ApplierReset case reds
on "server control-record telemetry missing", which says nothing about Magma. The strict-arming,
pmap and persistent-map-arm markers stay out.
The inproc-only exclusion in spawn_lane_parity.py is now PER TIER: in the gated tier the two
scenarios are registered on the inproc arm alone, in the census they are on every arm and simply
skip, so excluding them there would manufacture a spurious "extra".
Measured after the change: every pre-existing lane unchanged and green (magma-split 73,
spawn 52, tcp 54, all-* 102/102/104, GLES 186/102/104, buffers 19, runahead 14, caches 7), the
dual-block census 257 green / 2 skipped / 0 red with an empty ratchet, both new CI steps green,
parity RC=0 across all three tiers, G14 4155 -> 6095 with removed=0.
The note now carries the grouped red list, the 57 named skips by reason, the four pieces of
evidence that the tcp red is a defect and not plumbing, and six findings - the first being that
wave 2's clusters cannot be scheduled from this lane's red list, because on lavapipe there is
essentially none.
|
||
|
|
a7425d95e0 |
[MG_IntegrationTest] (P7): every Magma lane pins the ICD the monolith arm has always pinned
Integrator ruling on the asymmetry package L reported. The six hand-laid Magma lanes (Fm,
Buffers, RunAhead, Caches, NamedBlit, P5fRsp) appended ${MGL_ITEST_COMMON_ENV}, so the monolith
DirectVulkan. arm ran with VK_ICD_FILENAMES pinned and the three lavapipe iterationRP repairs
forced on, while the 73 integration-magma-split entries ran against whatever ICD the loader
found first with those repairs at their device auto-detection default.
That is a latent flake rather than a semantic: the day a runner or a workstation grows a second
ICD, the lane measures a different driver from the gate it mirrors and nothing in its output
says so. Consistency wins - all six now append ${MGL_ITEST_VULKAN_ENV}, the same list the new
MGL_ITEST_VULKAN_{SPLIT,SPAWN,TCP}_ENVIRONMENT use.
The ICD pin follows the BACKEND, not the lane, in the two blocks that register both: NamedBlit
and P5fRsp loop over DirectGLES and DirectVulkan, and handing VK_ICD_FILENAMES to the DirectGLES
half would pin an ICD for a lane that never opens Vulkan. Verified in the discovery: all 73
integration-magma-split entries carry VK_ICD_FILENAMES and all three repair variables, and the
two DirectGLES.Split.NamedBlit. entries carry neither.
Re-run after the change, as the ruling asks: integration-magma-split 71 PASS / 0 FAIL / 2 SKIP
out of 73, unchanged - the two skips are the same ClipDistance pair with the same reason.
|
||
|
|
e7e3265c47 |
[MG_Remote] (P7): the wire client's death notice is installed for any backend, not just Espryt
WireTables.cpp:740 installed the client-side object_death emitter only when
ActiveBackendType == DirectGLES. P6.5 wrote that because DirectGLES was the only backend with a
two-process lane, so the condition read as "the case this can happen in" rather than as a policy
- and under a Magma spawn or tcp client it is a silent hole of exactly the shape P6.5 closed for
Espryt: no notice installed, NotifyAndFree drops every object_death before freeing the slot, and
CtWireScenario's two death cases report ObjectDeaths=0 and prove nothing.
The two remaining conditions carry P6.5's intent, both halves of it: Transport == Spawn (this is
the remote client, the one with no local twin table; under inproc the backend's own dispatcher
is the right one) and GetStateObjectDeathOps() == nullptr (whoever installed first keeps the
notice).
RED-ONCE: with the install site short-circuited, both DirectGLES spawn death cases red on
CtWireScenario.cpp:187/:246 with
Expected: (afterCounters.deaths) > (deathsBefore), actual: 0 vs 0
which is the silent zero this closes. Restored and green.
WHAT IT DOES NOT DO is give Magma a death table - that is wave 2 package C. But it already does
more than the plan predicted: with CtWireScenario's own BackendName() != "DirectGLES" skip guard
temporarily lifted (a probe, not committed), both DirectVulkan SPAWN death cases PASS, while the
DirectVulkan inproc arm still fails. So the two-process Magma death path works today and the
remaining gap is the inproc arm alone. Recorded in the package note; narrowing that skip guard
is package C's call and its file.
|
||
|
|
3d9a38a979 |
[MG_IntegrationTest, CI] (P7): the split arms take a backend, so Magma has two-process lanes at last
P7 exit gate 1 asks for integration green "under Magma push and split". Until now it was not
merely unmet, it was UNMEASURABLE: every arm of mgl_itest_register_split_arms hardcoded
MOBILEGL_BACKEND_TYPE=DirectGLES (:2002/:2025/:2031) and MGL_ITEST_MAGMA_WIRE_ENVIRONMENT
hardcoded MOBILEGL_TRANSPORT=inproc (:2456), so the shared integration-split / -spawn / -tcp
labels held ZERO DirectVulkan entries and the 73 integration-magma-split cases only ever ran in
one process.
The registration macro now takes the backend as its first argument; the old name is a thin
wrapper, so all 32 call sites are untouched and every DirectGLES name, label and private log
path is byte-for-byte what it was. Two tiers come out of it, and the CMake comment states both:
* GATED - integration-magma-{split,spawn,tcp}. The six hand-laid Magma lanes (Fm, Buffers,
RunAhead, Caches, NamedBlit, P5fRsp) grow arms through one shared shape macro; the inproc
arm keeps its names, its labels and its log paths, so the CI steps and the two magma_*
checker scripts that name them by hand keep working unchanged.
* INFORMATIONAL - integration-magma-all-{split,spawn,tcp}, in no gating step. Every scenario
the split arms run, on Magma, on all three transports, generated by REPLAYING the macro's
recorded call list - so a scenario added to the split arms tomorrow joins the Magma census
the same day. Its job is to be able to go red while CI stays green; a case moves up into the
gated tier with the commit that retires its blocker.
The label spellings are chosen against ctest -L's regex (ID-131 again): the tier word goes in
the MIDDLE, because integration-magma-spawn-all WOULD have been selected by
-L integration-magma-spawn and pulled ~100 expected-red entries into a gated lane. Sub-labels
mirror it: the inproc arm keeps integration-magma-buffers, the others put the arm word first.
MagmaRunAheadScenario and MagmaWireCacheScenario stay on the inproc arm alone, and that is
MEASURED rather than assumed: with all three arms registered, all 21 entries red on spawn and
tcp with no Fatal in either role log - the first arms its hold through
ServerLoopInstance().SetBeforeRetireHookForTesting, a hook on THIS process's server loop, and
the second asserts transportName == "inproc" in its SetUp. Both are named in the CMakeLists and
in spawn_lane_parity.py rather than allowed to widen a tolerance.
split_log_paths.py's is_split prefixes gain DirectVulkan.Spawn. / .Tcp. - on those arms two
processes append to one path, so a shared log does not merely blur attribution, it interleaves
two sessions. spawn_lane_parity.py gains the Magma dimension, normalising only the ARM segment:
RunAhead.Credit1. and .Credit3. are two entries of one case and a tail-stripping normaliser
would fold them together and then report an equal-sized set that had lost one.
Measured on this tree (lavapipe): magma-split 73 unchanged and green, magma-spawn 52/52,
magma-tcp 52/52, the three informational lanes 102 each, GLES split 186 / spawn 102 / tcp 104
all unchanged and green. G14 against ~/w7/p7-before: 4155 to 4565, removed 0, added 410.
|
||
|
|
9e8fdacd21 |
[MG_Remote] (P7): gate 3 is judged per case against the monolith arm, and its denominator names what monolith already fails
Device window #1 E0a: monolith x DirectVulkan on the Redmi is 0.996-1.000 against the desktop goldens (only OpenRA is 1.0), so "monolith 1.0" was an OpenRA fact, not a rule. Gate 3 now reads: per case, inproc and spawn bit-identical over three repeats, within the case threshold, and within 0.0005 of the same-session monolith reading. The denominator excludes what monolith itself fails - create-indirect (OQ-17), the 1.21.11 main-menu trace artifact, and iris-photon, whose pipelines Adreno 830 refuses with VK_ERROR_UNKNOWN (PipelineFactory.cpp:660) - which leaves 36. Gate 5 records the real block sources: UBO lives only in the GTF module, SSBO joins, gl33 is the fallback. |
||
|
|
484a56a5ae |
[scripts, MG_Pipe, MG_Backend, MG_Test] (P7): the census docstring's triage follows what the same wave retired, and the comments stop joining the @P7 census
Two corrections to the two commits before this one. The widening's docstring was written against the 83 sites it found, and the commit that routed five of them through Session::Fail did not revisit the prose. It now describes the 79-site floor the baseline actually records, says which five went and where, and states that the enforceable artefact is the baseline's per-file map rather than the counts in the prose - so a reader who finds the two disagreeing knows which one to believe. The three buckets are restated too: `(ii)` is no longer "owned by a Ph / P7 row", because with the Magma wire arms routed what is left there is the peer-reachable deaths in the applier and the two backends, which is exactly the set ID-P7-1 scopes to Ph's fuzz arm 2 AFTER P7. And the routing commit's own comments spelled the at-sign refusal marker while explaining what routes through them, which put SEVEN comment lines into the retirement census: `grep @P7` went from 15 sites in 4 files to 22 in 6, and that grep is how wave 2 will measure which refusals it has retired. The prose says the word instead, and the count is 15/4 again. |
||
|
|
a5dcbb444f |
[MG_Remote, scripts, MG_IntegrationTest] (P7): the supervisor names a faulted session, and the smoke that proves it is finally a ctest entry
Ph slice (1) (ID-P7-1). Three `::waitpid(active, nullptr, WNOHANG)` calls in the `--serve` loop threw the status away, so a session child that died of SIGABRT inside SessionFail and one that returned from its control loop and _exit(0)'d were the SAME EVENT to the supervisor: `active = -1`, no line, nothing to count. ID-P7-1 declines the literal 98-site latch translation because P6.5's fork-per-session already delivers "the next connection is served as usual"; what was missing is that anyone could TELL a faulted session from a finished one, which is what an operator, a fuzz arm and a flake triage all actually need. One `ReapActiveSession()` where there were three call sites, asking WIFEXITED / WIFSIGNALED / WTERMSIG - the same three questions ServerSpawn.cpp:289 asks of the same kind of child - and logging `pid=<n> reaped exit=<c>` or `signal=<n>` with the supervisor's lifetime `sessionsFaulted` beside it. A refused handshake exits 0 and is NOT a fault, which is right: protocol.fbs:86 says a rejection is a normal session outcome. The running total is on every reap line as well as on the summary, because the fixtures end a supervisor with SIGTERM and a figure only printed at a chosen exit would never be seen. THE IDENTIFIER ROW'S PREMISE IS WRONG, MEASURED. The plan asks for CtrlEnvelopeBufferHasIdentifier at ServerMain.cpp:193 because ServerSession::ParseEnvelope asks it and this loop did not - but the generated VerifyCtrlEnvelopeBuffer already IS `VerifyBuffer<CtrlEnvelope>(CtrlEnvelopeIdentifier())` (protocol_generated.h:2378), so a wrong identifier was refused here and ParseEnvelope's second call is redundant too. Recorded for the integrator. What was real is that both refusals were SILENT: a peer whose post-Welcome frame was garbage had its session ended with no line in the log, and a fuzz arm that cannot tell "refused, by name" from "the child went away" measures nothing. So the identifier is now asked first - it is the specific diagnosis where the verifier's is the general one - with the length guard that asking it first requires, and both say so. scripts/ci/tcp_supervisor_smoke.py has been in the tree since P6.5 asserting four supervisor controls and was referenced by NOTHING - no ctest entry, no workflow step - so nobody could have said whether those four were green. It is now `TcpLane.SupervisorProtocolControls` beside the TCP lane's own fixtures, labelled integration-tcp, under the lane's resource lock (a second supervisor's child would answer the other's peers with Busy). It needs flatc, which is deliberately not in the build graph, so it resolves one gen_protocol.py's way and exits 77 - ctest SKIP - when there is none, rather than passing on a run that did not happen. Two cases added to it: a session child killed with SIGABRT must be named by pid and `signal=6` with `sessionsFaulted` one higher than before, and the next Hello must still be welcomed; and a steady-state frame carrying the wrong file identifier must be named rather than silently dropped. Red-once, both ways, against the real image: making the reap discard the status again reds with "the supervisor did not name session pid=N as killed by signal=6", and deleting the identifier branch reds with "read a frame with the wrong file identifier without saying so". |
||
|
|
e1ec69341a |
[MG_Pipe, MG_Backend, MG_Remote, MG_Test, scripts] (P7): the Magma wire fatals and the two Staged aborts go through Session::Fail
Five deaths in the split server image raised their own std::abort(). Three of them are funnels - MagmaWireFatal (Renderer/WireFramebuffer.inc), WireDescriptorFatal (Renderer/UniformManager.cpp) and WireBufferLegacyFatal (Renderer/WireDraw.inc) - and the fifteen `@P7` refusals on the tree (8 in UniformManager.cpp, 4 in WireFramebuffer.inc, 2 in WireDraw.inc, 1 in VulkanRenderer.cpp; plan section 1.3's count confirmed) all die through two of them. Every one of those deaths published NO SessionFault frame, so the peer read a bare EOF; bumped NO SessionFaultCount(), so exit gate S8 could neither confirm nor falsify it; and carried a hand-typed family word with no path to FatalFamilies.def's projection. MG_Backend may not name an MG_Remote symbol on its own account, so the route is a seam in MG_Pipe with MGP_TRIP_WIRE_REPORT's shape (PipeApply.cpp:66, and the new code sits beside it): MG_Pipe/PipeSessionFail.h declares MGPipeSessionFail, PipeApply.cpp defines it inside `#if MOBILEGL_BUILD_DISAGGREGATED`, the DEFAULT behaviour is byte-identical to what the three funnels did on their own, and MG_Remote::InstallPipeSessionFailHook() points it at SessionFail. The install is in InitServerRoleCommon (MG_Backend/Init.cpp), step 0, because that is the one function BOTH server roles run - the inproc role and the spawn/TCP session child - so there is no per-transport install to get wrong. MGPipeFatalFamily is a two-value boundary enum, NOT MGFatalFamily cast to an integer: the .def's enumerators are positional and a row inserted in its middle would silently re-point every hook call at a different family. FatalFunnel.cpp's adapter is the one place the two vocabularies meet, as a switch with a named default. The family word stays UnmigratedVerb rather than becoming a new MagmaWireRefusal. Three reasons: the message strings are required to stay byte-identical (5.2), and the retrace refusal censuses recorded since P5b count these deaths by that word - a rename invalidates every `@P7` measurement taken against them; `Magma:` in the detail already separates the Magma arm from the GLES UnmigratedVerb deaths, so a new word adds no discrimination a grep does not have; and its wire projection would be ProtocolCorruption, exactly UnmigratedVerb's. Recorded for the integrator. Ph slice (2) in the same commit: Server/StagedShadow.h's RequireCovered and Server/StagedTextureStore.h's RequireLevelBytes were MGLOG_F + bare abort. Both are inside MG_Remote already, so they call SessionFail directly, message verbatim; check_include_closure.py's four probes do not reach either header and the gate is green. Census: 83 abort sites -> 79 over 20 files (five sites retired, one gained for the seam's no-hook default, which is argued into FUNNEL_SITES), baseline rewritten in this commit - the downward ratchet's notice is what asked for it. Two gate bugs the widening exposed and this commit fixes: the SessionFail call regex also matched `MGPipeSessionFail(` (a lookbehind now), and a funnel's anchor needed a wider window than a marker's (kFunnelAnchorWindow). Red-once, three ways. Pointing MagmaWireFatal back at its own abort reds the census naming WireFramebuffer.inc 0 -> 1. Making the seam skip the hook reds both new death cases - `TheMagmaWireVerbDeathReachesSessionFailAndBumpsTheFaultCount` on the exit code, because it reads SessionFaultCount() out of a SIGABRT handler and gets 0 instead of 1, and `TheSeamHandsTheInstalledHookItsFamilyAndItsLineVerbatim` on the line the hook echoed. |
||
|
|
f141918aa1 |
[scripts, MG_Remote, MG_Impl, MG_Test] (P7): the fatal census gate widened to the split server image
`scripts/ci/fatal_census.py` scanned `MG_Remote/` and saw four abort sites. CONTRACT-P6
§12.1 already says what the server image is - "P6's server image is the whole
libMobileGL.so" - and the server process was executing seventy-nine more deaths the gate
could not see: 15 in DirectGLES.cpp, 12 in Managers.cpp, 11 in VkBufferManager.cpp, 7 in
PipeRoute.cpp. SCAN_ROOTS is now MG_Remote / MG_Pipe / MG_Backend / MG_Impl/Pipe /
MG_State, `.inc` joins `.cpp`/`.h` (the Magma wire arms and the generated wire records are
.inc files, and 20 sites are reachable only through one), and MG_Benchmark /
MG_IntegrationTest join MG_Test in the exclusion.
Four rules now, where there were two:
1. an abort names its family within twelve lines, OR it is argued into FUNNEL_FILES /
the new FUNNEL_SITES table. FUNNEL_SITES exempts an individual funnel inside a file
that has ordinary named deaths around it, and only when one of the file's anchors
appears in the same window - so the exemption follows the funnel if it moves, expires
if it is deleted, and does not cover an unrelated abort added to the same file. Two
entries, one line of reason each: PipeApply.cpp's MGP_TRIP_WIRE_REPORT and
PipeWire.inc's MGPipeWireProtocolFatal.
2. unchanged: a WireLogFatal / SessionFail call carries a `Fatal{` in its string.
3. NEW: every family word found in the perimeter has a row in FatalFamilies.def. Eleven
words did not, and now do (see below).
4. NEW: every `Refuse{Word}` names a RefuseCode enumerator. This is what catches
`Refuse{AuthenticationRequired}` (SocketTransport.cpp:161), a word protocol.fbs has
never had - PH-7 (2). Renamed to the enum's own `Authentication`, and
SocketTransportTest pins the spelling to `EnumNameRefuseCode` so the two cannot
drift again.
`abort_sites` was recorded and never compared. It is now a DOWNWARD ratchet with a
per-file map beside it, so "83 became 84" reports as "VkBufferManager.cpp went 11 -> 12,
here are its lines"; fewer sites - the direction `@P7` retirement moves the number - asks
for a re-baseline rather than blocking one.
Eleven new `.def` rows for the eleven rowless words. Ten are ProtocolCorruption for the
reason that bucket already states. ResourceUnavailable is ServerCrashed, not SegmentMismatch:
SegmentMismatch means a shared SEGMENT was the wrong size, and telling a peer that when a
VkBuffer allocation failed would be a lie about whose geometry was wrong. A twelfth word was
collapsed rather than given a row - `InitialBytesNotCarried` (PipeFill.cpp) said the same
thing as `UncarriedInitialBytes` (WireTables.cpp) about the same record, and two words for
one family is the drift a6 censused.
Red-once, four ways: an unnamed `std::abort()` appended under MG_State reds naming the file
and the line; deleting the `Fatal{` from PipeRoute.cpp's ResidualBlockSize reds; putting
`Refuse{AuthenticationRequired}` back reds rule 4; a typo'd family word reds rule 3 and the
vocabulary ratchet together.
|
||
|
|
e19095269b |
[docs, MG_Remote] (P7): the roadmap rows say what the tree says, and CONTRACT-P7 is drafted
ROADMAP.md: the Ph / P3b-P4b / P7 rows carry the audit's verdicts and the factual
corrections it found - the @P7 refusal list is 15 sites in 4 files (the row printed 8),
the 29-commit DirectVulkan shape wave was never recorded, the device divergence is two
observations with no per-case attribution and no lockstep control, SetupDrawSnapshot /
content-addressed CSO are declined for this stage, "XFB scatter to client" is retired
in favour of the two real holes, readback/pack state splits into closed and open
halves, the NDK size pin is obsolete, PACK-PBO readback landed in
|
||
|
|
e8b2c4bd05 |
[docs, MG_Test] (P7): the Ph / P3b-P4b / P7 program plan, and the pull flavour builds its tests again
Plan for driving the roadmap to P7 close-out, from a per-item audit of the three
rows against
|
||
|
|
fe28bdb5f3 | [MG_Remote, MG_Pipe, MG_Test, CI, tools, docs] (P6.5): the first wave completed and reviewed - archive v2, publication boundary, half-close, AcceptPair/role fixes, device re-validation | ||
|
|
018fab0f5b | [WIP] (MG_Remote): implement P6.5 TCP and stream transport |