- The doors: RefuseFramebufferBindingSlotRead raises
Fatal{UnmigratedPipeInput, "GetFramebufferBindingSlot@<verb>"} where a decline used
to fall through to the pre-handle arm. Under a transport with the framebuffer bit
set the fallback is not a safe default - it would read a row belonging to a client
this apply is no longer synchronous with - so SyncCurrentFBO's and BindCurrentFBO's
declines become the detector CONTRACT-P5E §5.4 asks for.
- The two attachment texture lists are the package's core: the one unconditional
per-draw live read of this family, taken whether the memo hit or missed. They are
now keyed {Fbo, ContentHash, ContextSerial, g_backendContextGeneration} with entries
{Res, twin} that borrow nothing, and the dedupe asks the two BOUND HANDLES rather
than two pointers. Three frontend reads go in one move: both binding slots, the
FramebufferObject the entries borrowed their attachment slots from, and
GetTextureContextId.
- SyncCurrentFBOByRecord drives the twin through SyncToBackendByHandle /
SyncReadBufferToBackendByHandle; NoteStateForHandle is gone, which was the server's
last cross-record hold on a frontend framebuffer.
- FramebufferRecordMatchesBinding is monolith-only: the record was resolved from the
applier's own bound handle, so there is no second answer left to corroborate.
- ForceBindCurrentFBO reads the binding slot INSIDE the else instead of on both sides
of its own handle arm (S3 finding #7); the three legacy stamps go with it, which is
safe for the reason the serial memo is invalidated rather than stamped there.
- SyncAndBindFramebufferByHandle configures from the record, so a DSA framebuffer that
was never bound is configured correctly instead of being named and bound unconfigured.
- Both blit arms take two const MGPFramebufferState&: draw buffers from DrawBuffers[],
the read buffer by matching ReadSurface against Color[], kind/level/layer/layered/
format from the surface, Samples from the record, the textures by handle.
- The detach walk uses the reverse index and the records instead of ForEachLive +
StateForHandle + the frontend attachment array. The frontend-keyed scope stays at
this site: every caller reaches it from a barriered row (§4.4).
- SyncRenderbufferObjectToBackend refuses by name under a transport - its only caller
is the copy-image endpoint, whose renderbuffer form both the emitter and the sink
already refuse - and the scope at that site is deleted.
- Clear's debug-only draw-FBO diagnostic reads the record under a transport.
- SyncAttachmentSurface loses its frontend attachment parameter (CONTRACT-P5E §5.4).
The storage syncs have by-handle forms now, so the object was only there to be
cross-checked - and a second answer only a monolith build can produce is not a
check. Review N-6's one-directional hole ("record empty, frontend still holding")
becomes unrepresentable: the caller's detach and the attach read one test.
- The renderbuffer cross-check at the old Managers.cpp:9711 is DELETED rather than
transport-gated (§4.4). It was the one HandleOf in this family that was not gated
while its texture sibling was; it ran a client-allocator probe on the apply thread
under split, legal only because the scope named it and the client was parked.
- BackendFramebufferObject::SyncToBackendByHandle and
BackendRenderbufferObject::SyncToBackendByHandle land in place of c0e's aborting
seams, beside the object forms they overload. The record's eleven surfaces are the
point set; the per-point frontend version memo is gone and the record hash is the
only gate, with g_attachmentBackendIdGeneration and the re-entry kept because no
client-side value can answer what they answer.
- SyncReadBufferToBackend's decision moves into ApplyReadBufferFromRecord, which takes
no frontend object; both overloads funnel through it.
- The reverse index texture -> framebuffers, maintained at the by-handle sync, for the
detach walk (tx2 needs it) instead of a walk over every twin's frontend attachments.
- Images: ResolveShaderImageRecord and its identity probe are gone - under a transport
there is no second answer to arbitrate. SyncImageTextureBinding gains a record
overload; Access is DECODED through MGPipeValueTypes.h (ruling 16 / ID-94) and the
stale "the encoding does not exist" comment is deleted. The bind's format rules are
factored into one helper both arms call rather than written twice.
- The image sweep gate is re-keyed onto (ShaderImagesSerial, TextureShutterSerial,
ContextSerial, g_backendContextGeneration) under a transport (ruling 7 / ID-86),
never a backend re-mint counter; the window/high-water union is kept and the walk
reads BoundShaderImages[].
- MarkWritableImageBufferTexturesGpuWritten returns early under a transport - the
client owns the GPU-write set; paired with sb's deletion of the backend marks.
- BoundImageUnitFormat answers from MGPImageView::InternalFormat under a transport.
- c0e and id were implemented in parallel and each declared the two shared seams,
so the merge produced five duplicate definitions the text could not collide:
MGPipeBarriered, MGPipeApplierCurrentRecordIsBarriered and the three by-handle
twin resolvers. Resolved per ID-99 and ID-103.
- MGPipeBarriered keeps c0e's real body (the static wait-class column, the XFB
escalation and the client-array escalation); id's constant-true placeholder goes.
- The current-record flag keeps c0e's thread_local storage and setter and loses
id's MGPipeApplierState member: it describes which record THIS THREAD is inside,
not per-context state the applier owns, and the allocator guard that reads it
would otherwise have to find the right context's applier first. id's test hook
posts through the setter.
- The three resolvers keep id's real bodies beside each registry; c0e's asserting
stubs for them go. The seam stubs no package has bodied yet stay.
- ApplyOne stamps `true` while kMGPipeP5eClientWaitRuleLanded is false. The
predicate says what the client WILL do; until ra changes EmitAndWaitTails the
client still blocks after every record, so a `false` stamp would withdraw the
§4.4 exemption from a probe the client's own wait still makes safe. The
predicate is computed on every record regardless, so it is exercised for the
whole phase instead of first running on the day it starts deciding.
- Gates on the merged tree: unit 2206/2206, integration-split 111/111, both
generators --check/--self-test green, include closure 0 problems, dirty surface
rc 0, doc citations one pre-existing CONTRACT-P5.md line (c0e retired another).
- MGPipeBarriered(op, payload, applierState), IMPLEMENTED, not stubbed: the static wait-class
column plus the two payload-derived escalations the contract names (an open XFB span, a draw
carrying kDrawClientArrays). One function and two callers is the point - the emit table and
the sink must reach the same answer or the client runs on while the server reads a value that
has already moved. set_context_values now mirrors IsTransformFeedbackActive into the applier
state as well, because a predicate that read it out of gPipeInputs would be reading the very
block run-ahead stops filling.
- MGPipeApplierCurrentRecordIsBarriered() with the writer beside it. It answers true by default
and that IS the landed lockstep answer, not a stub: on a server without kCapRunAheadApply
every record is barriered and CONTRACT-P5C's two exemption scopes keep working unchanged.
- The sb applier fields (BoundShaderBuffers[3][84], the three windows, the writable masks and
ShaderBuffersSerial) and the pg record fields (the three binding tails, Signature,
BindingsSerial) with the two entry points MGPipeApplySetShaderBuffers /
MGPipeApplySetProgramBindings declared and ABORTING BY NAME.
- The by-handle backend seams in Managers.h with aborting bodies in one block: ResolveVaoTwin,
ResolveTextureTwin, ResolveProgramTwin, SyncTextureToBackendByHandle,
SyncMipmapsToBackendByHandle, and the FBO / RBO / program SyncToBackendByHandle overloads.
Five packages sit on opposite sides of these calls - tx2 writes what fb calls, fb writes what
tx2 needs, id rekeys the registries all three read - so the signature is fixed once here and
each family replaces a body. Abort and not null: a resolver that answered null renders a
blank draw and a sync that did nothing renders stale pixels, and both are green lanes.
- Coverage.def's GetBufferBindingPoint -> SetShaderBuffers row with BOTH of its PipeFill.cpp
switch arms and the pairing asserts, shape-only and inert. It lands with the contract package
because PipeFill.cpp belongs to the contract package for the whole phase - an emitter
enumerator whose dispatch arm lived in the family's worktree is the merge trap that file's
own comment was written about.
- FieldOwnership.def re-annotates the six object-class rows plus GetProgramForDispatch to
"P5e (Espryt unbarriered), P7 (Magma)", and the strict-abort case follows the string, which
IS the debt entry rather than decoration. kMGPipeMaxBufferBindingPoints is pinned against
BufferState.h's 84 where both are visible. gen_pipe_field_ownership's refusal count is 8.
- OnFrontendStateObjectDestroyed's split arm loses the mailbox hop
(CONTRACT-P5E §4.4). It existed for the ONE shape the object_death record
cannot carry - a transport with no client session at all, i.e. a ServerLoop
fixture driving a server role with no client, never a real split - and it
posted a stack struct to the apply thread, which re-entered this function and
ran DestroyByLifetimeId: a probe AND a Free in the CLIENT'S allocator from the
apply thread. That is not a read a barrier could ever make safe, because under
run-ahead there is no wait behind which the allocator stands still. Such a
fixture's twins die with the backend at Stop, which is what already happened
for every death raised past Running() == false.
- The frontend-keyed scope this function opened goes with it: the one arm that
needed it was the hop. Falling through under a transport now means the death
was raised ON the apply thread - the server backend destroying a frontend
object it created itself - and those sites carry their own named scope.
- ID-96, ANSWERED BY MEASUREMENT rather than by reading: a probe that aborted in
exactly the deleted branch (NoSession with a RUNNING server loop) was run over
the whole unit lane (2204 entries) and the whole integration-split lane (111)
and NEVER FIRED. The only fixture family that could reach it is
ServerLoopTest.cpp's ServerFixture / EglServerFixture - the tree's only server
role with no client session - and its one case that lets a re-keyed frontend
object die off the apply thread
(ServerLoopEglTest.FrontendFramebufferDeathDeletesOnTheContextOwner) is already
refused at its FIRST step by P5c's allocator guard. So the set of fixtures that
lose death delivery is empty: none rewritten, none deleted.
- ReleaseTwinsForWireObjectDeath gains §4.2's death moment and §4.3's ABA answer
in its own voice: the ordering is the argument (every record naming {s,g} is
applied before object_death{s,g}, which is applied before create{s,g+1}) and
the Gen compare is the proof (forward re-mints, a stale handle resolves to
null, a backward generation is refused) - so a skipped death is answered by the
forward-Gen reset alone and no wait is added at any delete.
- The three by-handle resolvers the per-family packages call, in the shape
SamplerImpl::ResolveSamplerCsoTwin already runs (CONTRACT-P5E §4.1/§4.2):
handle in, THE RECORD FIRST, then AdoptTwinByHandle, then a twin if the slot is
empty. A handle with no applier record is a seam defect and adopting a slot for
it would leave a twin nothing can sync, so it is a loud null rather than an
empty twin; a generation behind the live entry's is refused by
PipeTwinHandleIsAdoptable rather than dropping the incumbent's driver object.
- WHAT THEY DO NOT DO is the deliverable: no frontend object is touched and the
client's slot allocator is never probed. They also do not SYNC - which serials
gate a VAO / texture / program sync, and what the sync reads, belongs to vi,
tx2 and pg, and a resolver that synced would have to know.
- ResolveProgramTwin resolves through the band-aware PipeShaderCsoRecordForHandle
and adopts into SlotTables.h's m_band, which is why the band landed in the same
package: after the rekey a program-pipeline composite's bind is the ordinary
path through this function.
- Declared beside each family's registry global rather than in the registry class
region BRIEF-P5E names, so that the three read exactly like the sampler
resolver they copy. Reported to the integrator as a range widening.
- CONTRACT-P5E §2.1's predicate gets its two names in MG_Pipe/PipeApply.h:
MGPipeBarriered(op, payload, st) and MGPipeApplierCurrentRecordIsBarriered(),
with MGPipeApplierState::CurrentRecordBarriered as the per-record flag that
PipeApplier::ApplyOne stamps before every decode. The body answers TRUE for
every record until ra lands the wait rule, so lockstep is unchanged and this
package alters no behaviour; ra rebases onto that line rather than adding it.
Package c0e owns the final declaration of both names - the overlap is one hunk.
- §4.4: MGPipeRefuseAllocatorFromApplyThread now exempts a probe only inside a
named scope AND while the record being applied is barriered. The client's wait
is the whole of what makes an apply-thread frontend probe safe; behind an
unbarriered record the allocator's free list and lifetimeId -> slot map are
moving under the read, so the value is wrong by construction - there is no
"count it" arm and MOBILEGL_IPC_STRICT_ERRORS is not consulted.
- MGPipeApplierIsUnbarrieredApply() and
MGPipeRefuseFrontendKeyedRegistryFromUnbarrieredApply() are the non-allocator
half of the same rule, for the surfaces that answer from a frontend SharedPtr
without calling the allocator (SlotTables.h's state note and its reader).
- Ruling 12: MGPipeReverseAnnouncementScope -> MagmaP7AllocatorDebtScope, keyed
on MG_Config::ActiveBackendType == DirectVulkan. Magma's four apply-thread
allocator debts move onto it (VulkanRenderer's two hidden-resource shutdowns
and the named-blit endpoint resolve, ResourceTracker's GPU-write
announcement); an Espryt probe can no longer borrow Magma's exemption by
constructing its scope.
- BufferImpl::HandleOfBuffer is Espryt's debt, not Magma's, so it moves to
MGPipeFrontendKeyedRegistryScope - which the Magma-keyed scope would not have
exempted at all. It retires when vi/sb carry the handle into
EnsureBufferResourceForHandle / IsBufferDrawCleanByHandle.
Layer 1 arms the texture object surfaces at MipmapStorage (the one point every concrete
texture class and view forwards through): AllocateStorage, TruncateMipmapLevels,
UpdateMipmapSubData, MapMipmapData, MarkStorageDirty(Region), IsStorageDirty,
GetStorageDirtyRegion(s) take Fatal{RoleViolation, texture-legacy-arm} from the apply
thread with an active transport. What the lane then exposed is fixed, not exempted: the
completeness gate answers from the staged store's defined-ness, the two logging reads go
through the descriptor macro, and the remint replay loop keeps its named
Fatal{UnmigratedEmulation} ahead of the guard. Magma's texture sync read path is the one
named exemption (MGPipeTextureLegacyArmScope, P7). Layer 2 wires InBarrierWait as the
client half of the gPipeInputs single-writer rule (RefusePipeInputsTouchWhileApplierOwnsIt:
applier owns it AND this thread not in a barrier wait => Fatal{RoleViolation,
gPipeInputs}; the barrier-off negative control is deferred to its own Fatal), and stale
reads of RECORD-SUPPLIED / APPLIER-DERIVED fields under a server stamp now report
RoleViolation with the field named. The RemoteGuards suite pins all nine texture surfaces
plus HasDefinedContent by name, and a negative control for the fill window. CI gains the
integration-split-strict job: unit lane as the hard gate under MOBILEGL_IPC_STRICT_ERRORS=1,
the scenario lane expected-red with the marker asserted (rewritable to plain green once P7
retires the object-class rows). rsp is per-frame in the stats line and measured on the
locally available traces (bsl in-world 948.5/frame avg over 123 frames, complementary
1591.9, iterationrp 286 until its named remint-pull Fatal); value-class pulls are zero by
rv's pinned list, what remains is the object-class fifteen.
Evidence: unit 2187/2187 (also under STRICT_ERRORS=1); integration-split 111/111; red-once
per layer (the texture guard and the gPipeInputs guard each turned their named death test
red and restored green). CONTRACT-P5C sections 5.4, 6.
Conflict: VulkanRenderer.cpp's disaggregated include block took both halves.
Merge coordination (integrator, with the evidence inline):
- hd's Fatal{RoleViolation, MGPipeSlots} guard as landed was kind-blind: it fired on the
G6 frontend-keyed twin registry (texture/VAO/program/sampler resolver probes - P3b/P4b's
to rekey) and on the families whose handle-carrying records the client does not emit
until P4b (the buffer binding-point ensures, the GPU-written announcement). Integration-
split went 104/107 red at the first Clear. Two NAMED, scoped exemptions keep the guard's
teeth for every other caller (SlotAllocator.{h,cpp}): MGPipeReverseAnnouncementScope
(buffer ensure + announcement family, P4b/P7) and MGPipeFrontendKeyedRegistryScope
(the G6 registry family, P3b/P4b; also the mailbox death switch until ct's object_death
record retires it). ~30 resolver sites wrapped, each naming its debt in the comment.
- ScopedRestartIndexSubstitution needed NO exemption: the EBO resolves from the record's
IndexBuffer.Res and the bytes read from the server staged shadow - CONTRACT-P5B d1's
own sentence, implemented.
- Magma's named blit takes a G6 consume arm (frontend FBOs resolved by lifetime id inside
the scope, VerbBlitNamedConsumed set, loud refusal kept for a failed resolution), so the
two DirectVulkan.Split.NamedBlit correctness cases stay green; Magma's hidden blit and
depth-mipmap resources' teardown probes are wrapped and named (P7: give them non-frontend
storage).
- MG_State gains GLContext::FindFramebufferObjectByLifetimeId (behaviour-neutral accessor
for the arm above).
Gates on the merged tree: unit 2166/2166; integration-split 107/107 (2 design skips);
hd's RemoteGuards death tests still abort the unwrapped paths by name; PrimitiveRestart
7/7; the two Magma named-blit cases render. CONTRACT-P5C sections 3, 5.4.
Two appended wire records replace the two cross-role control bypasses:
applier_reset (opcode 77, MGPApplierReset{Uint64 ContextSerial}, kScreen) is emitted by
the GL thread at the FreshlyPrimed make-current edge, before the client-side resets, so
the record's barrier orders the server's MGPipeApplierReset against every verb that
follows; a direct MGPipeApplierReset() from a non-apply thread with a live session is
Fatal{RoleViolation, "g_applier"} (MGPipeApply.cpp carries the layer-2 guard; the
apply-thread and no-session bring-up shapes keep the direct call verbatim).
object_death (opcode 78, reuses MGPipeHandleOnly, kCtxObject) is the framebuffer
family's first wire delete opcode: the client thread resolves the dying object's handle
in its OWN allocator, emits nothing for a handle the server never saw, and EmitAndWaits
otherwise - the wait preserving the death's ordering against in-flight records that
name the handle, the only property the blocking mailbox hop provided. The sink releases
per-kind twins through a handle-keyed ReleaseTwinByHandle (seven kinds, SamplerViewCso's
idempotent second path kept); a null arriving handle is
Fatal{ProtocolCorruption, "ObjectDeath.Handle"}. A documented NoSession fallback arm
delivers a death to a server-only fixture's loop; monolith keeps the mailbox hop and
the FreshlyPrimed direct call character for character.
Evidence: gen_pipe --check/--self-test and gen_pipe_field_ownership --check/--self-test
green; unit 2155/2155; integration-split 111/111 (107 plus four new CtWireScenario
entries, all genuinely run, each with its own log path and an EXPECT_EXIT death case);
red-once - shorting the emission to the direct call turns the four Ct cases red with
Fatal{RoleViolation, "g_applier"} by name, then restores green verbatim.
CONTRACT-P5C sections 1, 5.1-5.2, 7.
A split-only verb-handle workspace in MGPipeApplierState (written by the sinks before
dispatch, read by the backend within the same verb) carries the record's handles to the
backend entries whose shared function-table signatures cannot grow them without moving
G1. OnBlit resolves ReadFbo/DrawFbo through the FBO twin table by handle (the named arm
of BlitFramebuffer, a loud refusal when the backend does not consume it) and the split
path's ScopedBlitBindings rebinding is deleted; CopyTex resolves MGPCopyFromFramebuffer::Dst;
the mip descriptor check resolves MGPMipPlan::Res; the indirect families resolve their
buffer handles from the records and read the bytes from the server staged shadow with
RequireStagedCoverage. EnsureBufferResourceForHandle answers HasDefinedContent from
Desc.HasDefinedContent OR the staged coverage set (the streaming idiom defines content
through subdata with no new descriptor). The minting GetOrCreate(StatePtr), HandleOf and
the death switch's lifetime-id arms take Fatal{RoleViolation, MGPipeSlots} from the apply
thread, as do the three MGPipeSlots() entry points; the frontend BufferObject legacy
accessors take Fatal{RoleViolation, buffer-legacy-arm}; Magma's four caps reads go to the
server's own backend and the client-mirror fallback arm is a named refusal.
Evidence: unit 2156/2156 (incl. 9 new RemoteF1 + 10 new RemoteGuards death tests pinning
the four Fatal names); monolith smoke green; six production mutations each turned their
named test red and were restored; c1f_redcheck M5 RED then RESTORED GREEN. The mip
descriptor check overlaps tx and is coordinated at the merge (tx's sync rekey wins the
sync body, this handle resolution wins the identity check). CONTRACT-P5C section 3.
StagedTextureStore (MG_Remote/Server/StagedTextureStore.h), the texture twin of R-11's
StagedShadowStore: keyed by the wire handle (a twin-address key would force glGenTextures
at adopt time), coverage = the whole staged run per (uploadTarget, level), defined-ness
tracked from the respecify hook so sparse chains keep their holes. ApplyTextureUpload
adopts the staged bytes at the last instant they are alive (rule C) through three new
disaggregated-only MGPipeResourceOps members; SyncMipmapsToBackend's four arms read the
store and the descriptor instead of the client's MipmapStorage (texels, extent, target,
defined-ness, dirty); Magma's GenerateMipmap marks the server shadow Defined+GpuDirty
instead of writing the client's level storage (T5). Monolith arms reproduce the pre-tx
expressions character for character behind the same macro discipline as P4a.
Evidence: unit 2147/2147 incl. two named-Fatal death tests and the copies/no-copies
difference suite; DirectGLES.Split 104/104 on WSL GPU; red-once - reverting the adoption
to pointer-dropping turns exactly StagedTextureProductionTest red; the two monolith lane
failures reproduce on the base commit (DirectVulkan.IterationRPProgram203Scenario,
DirectVulkan.F1WireScenario.GenerateMipmapPackedFloatPixels). CONTRACT-P5C section 2.