Commit Graph
3123 Commits
Author SHA1 Message Date
swung0x48 1ebe6abeb2 [MG_Pipe, MG_Impl, MG_State, MG_Remote, MG_Backend, MG_Test] (Disaggregated): P5e sb - set_shader_buffers is emitted, routed and applied
- MGPShaderBuffers::WritableMask is Uint32[3] and the POD grows 32 -> 40 (ID-104). One
  Uint32 could describe only the first 32 of an 84-point window, so a storage buffer bound
  at point 32 or above read as unwritable with nothing able to see it; the ruling widens the
  field rather than narrowing the window, which would change what an application may bind.
  MGPipeShaderBufferMaskHas / ...Set are the one spelling of the mask, so the emitter, the
  applier and the four backend sites cannot disagree about which word a point lives in.
- MGPipeApplySetShaderBuffers has its body: per-class window, Start/Count validated against
  the capacity, the mask copied by word, ONE ShaderBuffersSerial advanced on every applied
  record and ADVANCED - never zeroed - by both resets, which also clear the three windows.
- Route rows for opcode 38 on both arms (Mono_ and Wire_) and the server sink in the wire
  codec's decode switch, which is where a kCtxState row's sink lives - PipeApplier.cpp's
  ServerVerbSink has no method for it and the brief's "OnSetShaderBuffers" names no code.
  The codec also bounds Class and Start+Count where Count becomes a byte length.
- New MG_Impl/Pipe/ShaderBufferEmit.h: one record per class, window = the touched
  high-water mark clamped to 84, zero early-out before any hash, a BASE binding travelling
  as kMGPipeWholeBuffer so the server re-resolves the extent at use, three suppressor slots
  (ruling 11), and the writable mask that replaces the backend's GPU-write walk.
- BufferState gains a per-target bind-point generation (push-only, G1) and dirty bits
  15/16/17 shutter on it instead of the buffer CONTENT aggregate. That aggregate answered
  the wrong question in both directions: any glBufferSubData fired all three, and
  glBindBufferBase fired none of them, because it mutates a binding point through a returned
  reference. Same defect class as P4a's glBindSampler hole.
- Init.cpp publishes consumer bit 13 for DirectGLES and not for Magma (ID-106), in the same
  commit as the wired constant, and PipeFill's liveness gate asks R-8 for bit 13 in
  particular plus bit 13-requires-bit-7.
- `Touch` joins the dirty-surface scanner's mutator prefixes; TouchBufferBindingPoint gets
  its row and SetNamedTransformFeedbackBinding's is rewritten now that bit 17 reads a
  generation a binding moves. Negative control 6 is re-pointed: the pair it used to refute
  is a live answer, and a control that has quietly become positive is what the self-test is
  for.
2026-09-18 08:08:04 -04:00
swung0x48 f6cfcbd36b [MG_Pipe, MG_Backend, MG_Remote, MG_Test] (Disaggregated): P5e wave 1 integration - one body per seam, and the stamp waits for ra
- 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).
2026-09-18 07:05:48 -04:00
swung0x48 367e473416 Merge branch 'p5e/id' into feat/disaggregated (P5e id: handle-keyed twins, the barriered exemption, the scope rename) 2026-09-18 07:00:07 -04:00
swung0x48 11cbf4b688 Merge branch 'p5e/c0e' into feat/disaggregated (P5e c0e: the contract, the wait-class column, opcode 80, the knobs and the cross-package seams) 2026-09-18 07:00:07 -04:00
swung0x48 a513c53ee3 [Docs] (Disaggregated): P5e c0e - fix the one citation the draft got wrong
- SlotAllocator.cpp:410-419 named lines that do not exist; the Gen-on-reuse rule is at :176-189
  for the ordinary table and :240-258 for the composite band. The doc-citation lint now reports
  the same four pre-existing problems it reports at the base and no new one.
2026-09-18 06:50:23 -04:00
swung0x48 b4cf9b0527 [Docs, MG_Remote] (Disaggregated): P5e c0e - the contract lands, and the roadmap row
- MG_Remote/CONTRACT-P5E.md: the draft edited into its landed form. The nineteen rulings are
  as the integrator decided them (ID-80..98) and are restated here rather than referenced,
  because a package reads this file and not the brief. The "draft" wording is gone and every
  file:line was re-resolved at this head.
- FOUR PLACES WHERE THE PLAN WAS WRONG AND THE CODE IS RIGHT, each argued where it sits: the
  descriptor has no spare byte so MGPProgramDesc grew 192 -> 200; the applier's per-class
  windows are ShaderBufferStart/Count/WritableMask, not bare Start/Count/WritableMask; the
  three suppressor slots are named per Class; and the wire codec's layout carries three tails
  now, because set_program_bindings has three and the third one's offset is derived from the
  first two.
- One finding the contract now carries rather than leaves for sb to discover: MGPShaderBuffers'
  WritableMask is a Uint32 and the window is 84, so the mask can only describe the first 32
  points of a class. sb asks for the ruling; it is a wire field and not a package's to pick.
- A new section 10 lists, by name, every symbol c0e put in the tree and what each package
  compiles against - plus the three red-once evidence lines.
- ROADMAP.md gains the P5e row after P5d (status, the eight packages in dependency order, the
  integration gate and the device exit) and the two lines that said P6 was next now say P5e is.
  CURRENT_STAGE_PROGRESS.md section 1 gains its row and section 6 its next-step entry.
2026-09-18 06:49:46 -04:00
swung0x48 c06878ef73 [MG_Pipe, MG_Impl, MG_Backend, MG_Test] (Disaggregated): P5e c0e - the cross-package seams
- 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.
2026-09-18 06:40:43 -04:00
swung0x48 0117d15725 [MG_Test] (Disaggregated): P5e id - the band case, and the exemption scope stops exempting an unbarriered apply
- New DirectGLESSlotTable.ACompositeHandleDoesNotGrowTheOrdinaryTable (with its
  G2/G14 skip twin) pins the band: a composite handle grows m_band by four
  entries and the ORDINARY table by none, the lookup finds it, an ordinary slot
  beside it disturbs neither space, ForEachLive reports the composite at the slot
  THE CLIENT MINTED rather than at its band index, and §4.3's recycle answer
  holds inside the band. It asserts on the two spaces' CAPACITIES, not on
  liveness: the table holds one live entry either way, so LiveCount could not see
  the growth this case exists to catch.
- RemoteGuards.AnAllocatorProbeInsideAnExemptionScopeOnTheApplyThreadIsAllowed is
  DELETED. It asserted that a named scope exempts unconditionally, which is the
  rule CONTRACT-P5E §4.4 retires. Its replacement,
  ...FromTheApplyThreadIsFatalEvenAtTheOldDebtSites, opens the same scope with
  the applier's CurrentRecordBarriered forced false and expects HandleOf to
  abort. The hook is the production field ApplyOne stamps, not a test-only branch
  beside it; the texture and the table are built on the CLIENT thread, because a
  TextureObject2D's constructor mints and building it on the apply thread would
  abort at the mint with the same diagnostic - a case passing for the wrong
  reason.
- The green half of the deleted case needs no replacement: every Espryt
  integration-split entry runs the frontend-keyed scope's surviving sites on the
  apply thread behind barriered records, so a guard that stopped exempting would
  take the whole lane down.
- Unit 2203 -> 2204 (the new name only adds); integration-split unchanged at 111.
2026-09-18 06:36:07 -04:00
swung0x48 287bade924 [MG_Backend] (Disaggregated): P5e id - the NoSession mailbox hop deleted, the twin death moment and the ABA answer written down
- 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.
2026-09-18 06:35:49 -04:00
swung0x48 0d54de62e2 [MG_Backend] (Disaggregated): P5e id - ResolveVaoTwin / ResolveTextureTwin / ResolveProgramTwin by handle
- 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.
2026-09-18 06:35:24 -04:00
swung0x48 69a278549b [MG_Backend] (Disaggregated): P5e id - the composite band, ForEachLive by handle, the frontend-keyed half as named monolith glue
- BackendSlotTable gains m_band, the ShaderCso composite space indexed by
  (slot - kMGPipeShaderCsoCompositeSlotBase), beside the vector the ordinary
  slots live in - the shape MGPipeSlotAllocator::KindState and the applier's
  CompositeShaderCsos already use. EntryAt used to index m_slots by slot and
  resize to it, so ONE program pipeline grew g_backendProgramObjects to ~983k
  entries (~40 MB). That was reachable only through GetOrCreate(StatePtr) for a
  composite program; after id's rekey the by-handle resolution IS the ordinary
  path, which is why the band is a P5e prerequisite and not a follow-up.
  EntryOrNull / EntryAt / IndexOfSlot are the one place either space is chosen.
- ForEachLive is re-typed fn(MGPipeHandle, const BackendPtr&) (§4.1): a walk that
  handed a frontend SharedPtr out of server memory on every step was the last
  place the server could hold one across records. Its single caller,
  ScopedDetachedTextureFramebufferAttachments, asks StateForHandle for the object
  instead - one named read inside the scope that owns the debt. The set of
  framebuffers walked is unchanged: an entry the old walk skipped because its
  stateRef would not lock is one StateForHandle answers null for.
  StateBackendObjectRegistry::ForEachLive is now handle-arm-only; the legacy map
  has no handle to hand over and its one caller has always had its own
  begin()/end() walk beside the call.
- The frontend-keyed half is KEPT, not deleted (§5.8, ruling 1): the push build
  under Transport=monolith keeps its frontend arms token for token, which the
  PIPE_VERIFY comparator needs and which is what makes RUN_AHEAD=0 a pure
  wait-rule A/B on identical server code. Every one of the six surfaces is a
  named Fatal from an unbarriered apply - the allocator guard for the three that
  call the allocator, MGPipeRefuseFrontendKeyedRegistryFromUnbarrieredApply for
  NoteStateForHandle / StateForHandle. No silent path.
- NoteStateForHandle / StateForHandle move from MOBILEGL_BUILD_DISAGGREGATED to
  MOBILEGL_PIPE_PUSH, because the re-typed walk's caller needs them in the
  push-monolith build too.
2026-09-18 06:35:08 -04:00
swung0x48 f40a57cc96 [MG_Pipe, MG_Impl, MG_Remote, MG_Backend] (Disaggregated): P5e id - the barriered seam, the allocator guard and the two scopes
- 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.
2026-09-18 06:34:48 -04:00
swung0x48 5ef4701294 [Config, MG_Backend, MG_Test] (Disaggregated): P5e c0e - the two knobs and the caps arm
- IpcTable::RunAhead (MOBILEGL_IPC_RUN_AHEAD, default 1, forced 0 under PipeVerify beside
  BatchWaits) and IpcTable::PresentCredit (MOBILEGL_IPC_PRESENT_CREDIT, default 1, range
  1..8). Both parsed unconditionally like every other IPC knob and both in the IPC log
  line, because a knob that silently means nothing on one arm of an A/B is how an A/B
  stops being one.
- RUN_AHEAD=0 is an A/B CONTROL and not a negative one: identical server code, identical
  records, identical fill decision, and only the client's wait differs. VERB_BARRIER=0
  keeps its own meaning as the lockstep arm's negative control.
- MG_Backend/Init.cpp: kMGPipeP5eRunAheadReady = false, and the caps arm goes through
  MGPipeRunAheadCapBitsFor so the "DirectGLES only, and only once the integration commit
  flips the constant" decision has one spelling. A constant and not a knob: an operator
  must not be able to turn a half-migrated server into a run-ahead one, because the
  failure mode is a torn read that renders wrong rather than aborting.
- MagmaPipeIdentityTest pins both halves - no bit 10 for DirectVulkan whatever the
  integration constant says, bit 10 for DirectGLES only when it is true, and that one bit
  and nothing else.
- The push default becomes kMGPipeSubsystemsMigratedAtP5e (0x3fff). Inert: bit 13 is not
  in kMGPipeWiredSubsystems, so no emission gate opens on it; 0x1fff survives as P5e's
  "everything P4a had and nothing of mine" control.
2026-09-18 06:26:32 -04:00
swung0x48 e99796d6ec [MG_Pipe, MG_Remote, MG_Test] (Disaggregated): P5e c0e - the wait-class column and opcode 80
- PipeCalls.def gains a FIFTH COLUMN, WaitClass, on every row, and gen_pipe.py generates
  MGPipeWaitClassFor(op) beside kMGPipeCallFlags. It is a VALUE and not a new op: opcode
  order and the flag column are untouched, and the point of generating it is that the emit
  table and the sink compute the barriered predicate's static half from ONE table instead
  of two copies that agree until they do not.
- Two new generator gates, each with its own --self-test negative control: a token that is
  not an MGPipeWaitClass enumerator, and - both ways - a kReplySlot row that does not wait
  for its reply or a kWaitReply row that owns no slot. The first would be a waiter that
  hangs, the second a block on an answer nobody posts. Twelve controls trip now, up from
  nine.
- set_program_bindings appended as opcode 80 (kCtxState, kVarTail|kHostSpan): the three
  POST-LINK mutable reflection fields, which live inside the LinkArtifacts the archive
  snapshots and all move after the link, so no existing row can carry them. It lands with
  a NULL ROUTE and a sink arm that validates and declines, exactly as set_shader_buffers
  has sat catalogued-and-dead since P4a.
- The codec's record layout grows to THREE tails for it, with each declared count refused
  against its bound rather than truncated, and the third tail's name spans walked through
  CheckHostSpanIsHonest in the decode arm - they are members of a 40-byte element, so the
  encoder's blanket second-tail pass would read one span and a quarter of garbage.
- PipeCatalogueTest: the new sizes (MGPProgramDesc 200), the opcode-80 and null-route pins,
  the draw flag, and a wait-class case that re-states CONTRACT-P5E section 2.2's table BY
  NAME rather than reading the def - every other statement about the column is derived
  from the def and moves with it, so this is the second place a demoted row has to be
  argued. The ABI fingerprint moves with MGPWireOp::kOpCount, which the handshake case
  already pins.
2026-09-18 06:26:14 -04:00
swung0x48 7568161eb4 [MG_Pipe, MG_Impl, MG_Test] (Disaggregated): P5e c0e - the types, the subsystem bit and the image-access table
- kCapRunAheadApply (bit 10) and MGPipeRunAheadCapBitsFor(backend, ready): the arm is a
  pure function so "Magma never publishes it" is a unit case rather than a line only a
  live split session reaches. Espryt's half is gated on the integration constant.
- MGPProgramBindings (opcode 80's head) plus its two tail element types, the three
  declared bounds, and MGPProgramDesc::LinkStatus. The descriptor grows 192 -> 200: its
  four trailing Uint8s end exactly at offset 24 and MGPBlobRef is 8-aligned, so the
  draft's "in the descriptor's existing pad" was wrong and there is no spare byte. The
  payload is instantiated only under MOBILEGL_PIPE_PUSH, so G1 does not see it.
- kDrawClientArrays (bit 6) and kMGPipeMaxBufferBindingPoints (84). The draw flag is what
  makes a client-memory vertex array visible on the wire; it is refused under run-ahead
  by vi, staged by P8, and the lockstep arm is unchanged.
- MGPipeImageAccess + MGPipeDecodeImageAccess in MGPipeValueTypes.h, and the client
  encode in ImageEmit.h moved onto those enumerators instead of literal 0/1/2. One table,
  both roles: the server's open-coded reverse mapping and its "the encoding does not
  exist" comment were the drift this closes. The encode is now a free function so the
  unit case can reach both halves.
- MGPipeWaitClass, the enum PipeCalls.def's fifth column spells; kMGPipeSubsystemBufferBindings
  (bit 13) with kMGPipeSubsystemsMigratedAtP5e = 0x3fff; the three suppressor slots for
  set_shader_buffers, one per Class, so an emission of one class cannot cancel another's.
- Tracker.h maps dirty bits 15/16/17 onto bit 13 - inert, because the emission gate also
  tests kMGPipeWiredSubsystems and the family's wired constant is still 0 - and
  TrackerTest follows the phase constant, which is now the whole bit set.
2026-09-18 06:25:54 -04:00
swung0x48 2fde70341d [Docs] (Disaggregated): P5d round 3 close-out - the report, the measurements section and the roadmap row
- P5D-INPROC-PERFORMANCE.md: the round-2 diagnosis is corrected against the
  symbolized call graph (the apply thread's mutex time was the idle poll, not
  the backend pendingMutex), the four round-3 packages and the two race fixes are
  described, the numbers table carries the two device caveats (monolith is
  120 Hz vsync-capped in most runs; the kernel ignores thread affinity, so the
  client thread runs on a mid core), and the lockstep-feasibility research says
  why the deferred draw barrier waits for P3b/P4b and P11.
- MEASUREMENTS.md section 9: the per-arm table with per-thread CPU ms/frame as
  the primary metric, the interleaved session and the wait/park counters.
- ROADMAP.md: the P5d row is the three-round shape; P11 gains the gPipeInputs
  versioning item the research points at.
- CURRENT_STAGE_PROGRESS.md, README.md, ARCHITECTURE.md (env table: the
  generate_mipmap exception to batched waits, the calibrated spin, the affinity
  readback): head 1f8de61b, the gate numbers and ruling ID-79.
2026-09-18 04:30:38 -04:00
swung0x48 1f8de61be1 [MG_Remote, MG_Pipe, MG_Impl, MG_State, MG_Backend, MG_Test, MG_IntegrationTest] (Disaggregated): P5d round 3 - the profile-directed cuts on both roles
- The round-2 report guessed the apply thread's ~48% mutex time was the backend
  pendingMutex; the symbolized call graph at 56a77348 says it is the idle poll in
  ServerLoop::ApplyThreadMain taking m_controlMutex twice per iteration
  (ControlIsPending in the ready lambda, PumpControlRequest), with DrainRing at
  29% of the thread. Every package below is cut from that graph, not from self%.
- T (transport): the control mailbox gets an atomic shadow bit m_controlPosted so
  the idle poll takes no lock (handshake, C2 critical section and the
  NOT_INITIALIZED answer unchanged); Doorbell::Wait spins on a once-calibrated
  iteration budget and reads no clock in steady state (the park phase alone
  computes deadlines); client/server wait and park counts ride the stats line
  as wait[srv= srvpark= cli= clipark=] and the wire ledger as cliwait=/clipark=.
- D (role guards): OnApplyThread() is inline - one relaxed atomic key against the
  caller's thread pointer (aarch64 __builtin_thread_pointer, else
  std::this_thread::get_id) - and m_applyThreadId, the function-static guard and
  the pthread_equal call are gone; the three scope depth counters stop being
  thread_local (their only reader is the apply-thread guard, so they count only
  when OnApplyThread() is true; a GL-thread scope no longer exempts the apply
  thread, pinned by a new case); the guard chains are ordered cheapest-first.
- B (persistent maps): the shadow allocator rounds the SIZE to a page multiple, so
  TrackWriteMap aligns OUTWARD when the shadow is page-granular - a sub-page map
  is one protected page, edges and their per-draw XXH3 disappear; the inward arm
  stays as the fallback. Review found a hole the edge hashes had hidden since
  round 1: a map that faulted but was not consumed by the walk that advanced the
  epoch was never pushed. The walk now drains every marked member before the
  binding walk, registration bumps the epoch so a fresh map ships once on the
  draw path, and a non-4 KB kernel page refuses the mprotect arm by name.
- C (client per verb): an image-unit high-water mark (push build only, fed by
  glBindImageTexture) replaces the 192-unit sweep per draw; the residual fill's
  7-term supplied conjunction is a memo keyed on its four inputs; the server's
  63-field stamp per verb is a constexpr per-class mask table.
- generate_mipmap no longer skips its wait under MOBILEGL_IPC_BATCH_WAITS: its sink
  is the backend GenerateMipmap, which reads MGB_CTX->GetActiveTextureUnit(), and
  a batched publish let the next validate move the serial under it -
  Fatal{UnmigratedPipeInput, "GetActiveTextureUnit@ReadPixels"} on the gate tree.
  The rule is written at the site: a record may skip its wait only if its apply
  reads nothing the residual fill writes. The texture-death scenario gets the
  framebuffer case's fence before it reads the object_death tally.
- Redmi 2f7cbe2e, FCL + Minecraft 26.3-rc-3, VD12, CPU pinned: inproc 64.3 fps
  (client 15.0 ms/frame, apply 12.4) -> 103-114 fps (client 8.7-9.2 ms/frame on a
  mid core, apply 7.0-7.4); monolith 115 (120 Hz vsync-capped runs) / 206 (one
  uncapped run, 5.0 ms/frame on the big core). Gates: unit 2203/2203,
  integration-split 111/111 (the two formerly flaky cases 6/6 on repeat),
  generators, include closure, dirty surface and doc citations green.
2026-09-18 04:28:25 -04:00
swung0x48 85362a856d [Docs] (Disaggregated): P5d close-out - the inproc performance report and its roadmap row
P5D-INPROC-PERFORMANCE.md states what the phase set out to do (inproc frame
rate comparable to monolith at VD12 without touching wire semantics or the
correctness gates), what the two landed commits did (cb06538c, 56a77348:
7-13 -> 63-65 fps against monolith 110-118, pmap 25 MB/frame -> 0.23
MB/frame), and what is explicitly not done - the backend sync mutex complex
(~48% of the apply thread's own time), the R-1 serialization (P11's
versioned inputs), the emutls/set-hash small items, and the verification
debt (79-trace set, Redmi retest window, arm64-only design note, the
uncommitted FCL-side test plumbing).
2026-09-18 02:29:36 -04:00
swung0x48 56a77348f6 [MG_Remote, MG_State] (Disaggregated): inproc performance, round 2 - every persistent map is tracked, and the consumer walk goes away in steady state
Follow-up to the performance package (cb06538c): on device the epoch skip
never engaged, because the workload always has sub-page persistent maps and
any untracked (hash-arm) member vetoed it - the full binding walk ran at
every draw (~8% of the client thread at VD12), and the direct-scan
alternative measured strictly worse (41-43 vs 58-62 fps: per-draw whole-range
scans of every untracked member plus 64-slot tracker lookups per member).

The two-part answer, after the membership map started caching the tracker
slot pointer in the parent commit:
- a sub-page range no longer falls back to the hash arm: it registers with an
  EMPTY interior (no protected pages, no bitmap) and hasEdges set, so its
  whole range is served by the two bounded edge hashes instead of vetoing the
  skip for every buffer in the process;
- the shadow's allocation alignment is a full page
  (SHADOW_ALLOCATION_ALIGNMENT = 4096, GL_MIN_MAP_BUFFER_ALIGNMENT still
  answers 64): every page a shadow occupies is wholly its own, so the
  tracker can protect the interior without ever covering a neighbour
  allocation's byte, and edge spans - and the zero-page shape itself -
  virtually disappear from real workloads. Monolith shares the allocator and
  is unaffected (110-118 fps A/B, no behaviour-visible change).

VD12 Minecraft, same scene: 63-65 fps (parent commit 60-66, with the skip now
engaging every steady-state draw rather than never). GetBindingPoint and the
binding walk no longer appear in the profile at all; XXH3 drops 6.1 -> 2.6%
(the rest is the set-record dedup hash, a pre-existing P5 site). Lanes:
unit 2188/2188, integration-split 110/111 with the one red again the
monolith-inert GenerateMipmapDepthPixels environmental flake.
2026-09-18 02:21:08 -04:00
swung0x48 cb06538c06 [MG_Remote, MG_Pipe, MG_Impl, MG_Test, MG_IntegrationTest, Docs] (Disaggregated): inproc performance package - dirty-driven persistent-map push, batched barrier waits, spin-clock batching
VD12 Minecraft on the Redmi (FCL, DirectGLES): the split's in-world frame rate
13 -> 60-66 fps against monolith's 110-114, and persistent-map wire bytes
25 MB/frame -> ~0.23 MB/frame. The residual split-specific costs are named at
the end, not hidden: the R-1 serialization (WaitForApplied ~12%, P11's
versioned-inputs design) and a split-only mutex complex still under
investigation.

Persistent-map push, three arms picked per buffer at map time:
- mprotect dirty tracker: the mapped range's page-aligned interior is
  protected PROT_READ; the app's next write faults, the SIGSEGV handler marks
  the page in the slot's bitmap and unprotects, and the push ships one 64 KB
  block per block containing a faulted page. Ownership is three tests -
  SEGV_ACCERR only (a userspace raise and an unmapped page chain verbatim),
  inside a live tracked range, and not a refault on a still-set bit (a write
  we unprotected succeeds, so that is a non-write access and it crashes
  honestly). The alignment is INWARD: an outward-aligned edge page holds
  foreign bytes, and a signal-blocking worker thread faulting on one is a
  process kill - that was the ctest-only SmallRing segfault. Edge spans
  (at most two per map) are hashed per push and ship their containing block
  only on change (unconditional edges measured 112 MB/frame). Re-arm is one
  mprotect per contiguous run. The first push ships everything once.
- hash-suppress arm (MOBILEGL_IPC_PERSISTENT_HASH_SUPPRESS, default 1):
  per-block XXH3 against the last push's hashes; untracked buffers only.
- PushDrawConsumers: only the buffers the upcoming draw/dispatch can read
  (VAO attribs/element, indexed binding points, indirect/parameter slots,
  TexBuffer backing - the last also retires the per-frame-cadence cloud
  twitch), plus the fault-epoch skip: an unmoved epoch proves no interior
  byte changed, tracked edges are hashed in place, and any hash-arm member
  vetoes. The membership map caches the tracker slot pointer (a 64-slot
  rescan per buffer per draw was measured and removed).

MOBILEGL_IPC_BATCH_WAITS (default 1; MOBILEGL_PIPE_VERIFY forces 0): a
value-class record (kCtxState/kCtxCso/kCtxObject with no reply slot) is
published without waiting for its own apply; the barrier is taken at the next
pull-reading verb, the only place BARRIER-PULLED fields are read, so the R-1
fence posts are untouched and only the round trips between them change. Two
consequences are pinned rather than argued: kCtxObject verbs the tests read
synchronously get explicit fences (GenerateMipmapFieldsCross,
FramebufferDeathCrosses), and a frontend object whose last SharedPtr drops on
the apply thread - impossible before batching - now enqueues its
(kind, lifetimeId) on the deferred destroy queue and the GL thread replays
the DestroyAndFree helper at the verb hooks (CopyImageLayeredScenario aborted
Fatal{RoleViolation, MGPipeSlots} without it).

Doorbell::Wait's spin reads the clock once per 64 yields: on the workload
device a steady_clock read is a real syscall (no vDSO) and the per-iteration
reads were ~20% of the whole process at ~1000 waits/frame.

Gates: gen_pipe --check/--self-test green; unit 2188/2188;
integration-split 110/111, the one red being GenerateMipmapDepthPixels whose
DirectGLES and DirectVulkan MONOLITH siblings flake identically with every
code path of this package provably inert (PushIsArmed false, no ClientSession,
deferral's monolith early-out).
2026-09-18 01:58:59 -04:00
swung0x48 7cdc8d775e [Docs] (Disaggregated): refresh the P5c close-out status lines - README and ARCHITECTURE section 17.6 were still describing P5c as planned, and the code head is the triage head b88e8487 everywhere 2026-09-17 18:57:14 -04:00
swung0x48 36176b9dbc [Docs, MG_Remote] (Disaggregated): close out P5c - the inproc split's direct cross-role access is zeroed
ROADMAP marks P5c closed at the triage head and P6 next; CURRENT_STAGE_PROGRESS carries
the current-head gate table (unit 2187/2187, integration-split 111/111, the census
classification with every newly-red entry evidenced by its Fatal name, the three named G1
resizes, the 0xDD audit, the per-frame rsp measurements, and the two items deliberately
not done locally: the Redmi retest and the 79-trace census); MEASUREMENTS gains section 8
with the per-gate evidence. CONTRACT-P5C takes the amendments ratified at gt's landing
and at the regression triage: per-level extent is retired at the sites rather than
guarded on the accessor, the strict CI lane is two-sided (unit green gate plus
expected-red scenario lane with the marker asserted), the drain points are two classes
(the EGL RPC returns beside EmitAndWait), and oversized writebacks are client-sliced at a
quarter of the event ring.
2026-09-17 13:15:57 -04:00
swung0x48 b88e848761 [MG_Remote, MG_State, MG_Impl, MG_Backend, MG_IntegrationTest] (Disaggregated): P5c census triage - the three real regressions the merge owed
The broad inproc census against a same-machine 11ac3de6 baseline found 183 newly-failing;
172 are the design-red the contract rules (the legacy-arm refusal under transport: the
subsystem-off and legacy control lanes, and Magma's P7 surface - every one carries its
Fatal name in ~/p5c-fatal-map.tsv), and the other eleven were three REAL regressions,
fixed here:

1. The surface-changed event drained at the first verb's EmitAndWait, but a pre-verb
   glGetFramebufferAttachmentParameteriv on the default framebuffer was answered from the
   placeholder attachments, and buffers allocated from that answer are blit-incompatible
   with the real surface (DepthStencilReadbackAttachmentShapeScenario). The event ring
   gains its SECOND drain-point class: the blocking EGL lifecycle RPC returns
   (BackendObject_Remote's surface create/resize and make-current) - the same known-idle
   premise as EmitAndWait's post-barrier point. CONTRACT-P5C section 4.1 amended.
2. A whole-buffer writeback larger than the event ring could never fit one record
   (capacity/2), aborting the server with Fatal{EventRingOverflow} on the large-arena
   readbacks. The client now slices the request at a quarter of the ring (header and
   co-posted small events ride along), each slice round-tripping its own barrier + drain;
   the whole-buffer flag is cleared by hand after the last slice lands, which is exactly
   what WritebackFromBackend's clear says. CONTRACT-P5C section 4.4 amended.
3. ScatterCapturedRecords / ReadbackCapturedRanges read the capture buffers through the
   frontend MappedData() (a layer-1 surface since gt) and wrote back by direct call. The
   XFB capture path now reads the server's staged shadow (RequireStagedCoverage asserted,
   syncedChangeSerial bumped so no later draw overwrites the capture with stale shadow)
   and writes back through OnBufferWriteback, the Ops_H_Readback order.

CtWireScenario's two death cases skip on DirectVulkan by name: object_death's producer is
the Espryt-side death-notice ops and Magma installs none until P7.

Evidence: unit 2187/2187; integration-split 111/111; the three families' named cases green
again; census newly-failing reduced to the evidenced design-red set.
2026-09-17 13:13:41 -04:00
swung0x48 39d1a5d38c Merge branch 'p5c-gt' into feat/disaggregated (P5c gt: role guards, InBarrierWait wired, rsp measured) 2026-09-17 11:21:39 -04:00
swung0x48 ab9250281b [Docs, MG_Backend] (Disaggregated): P5c housekeeping - the death-switch scope comment describes the landed ct shape (the NoSession arm only), and the README counts 79 calls after ct/rv 2026-09-17 11:21:39 -04:00
swung0x48 a0865b00f4 [MG_State, MG_Backend, MG_Remote, MG_Impl, CI] (Disaggregated): P5c gt - the role guards close the phase
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.
2026-09-17 11:20:44 -04:00
swung0x48 c74de6b506 Merge branch 'p5c-rv' into feat/disaggregated (P5c rv: residual-value record, value-class pulls retired) 2026-09-17 09:27:47 -04:00
swung0x48 82b1f743a7 [MG_Pipe, MG_Impl, MG_Remote, MG_Backend] (Disaggregated): P5c rv - set_context_values retires the value-class BARRIER_PULLED rows
set_context_values (opcode 79, MGPContextValues 96B, kCtxState) carries the active texture
unit, the max touched unit, the fifteen touched-binding-point counts and the five value-class
XFB rows, emitted at validate under the subsystem bit and hash-suppressed as a whole; the
emit gate and the residual-skip gate read the same answer so they can never disagree.
MGPipeApplySetContextValues writes gPipeInputs (the PackState shape). MGPAttribValue grows
24->56B to carry the frontend's converted three views, and MGPipeApplySetVertexAttribDefaults
finally writes them (the 'ignores MGPAttribValue::ValueClass' warning dies), so
GetCurrentVertexAttribute leaves the refusal set. The three texture shutters answer the
applier's own serials under a server-stamped verb (APPLIER_DERIVED), storage answer kept
verbatim elsewhere. FieldOwnership.def: nine value-class rows become derived RECORD_SUPPLIED,
the three shutters APPLIER_DERIVED, the nine object-class rows keep their phases;
FieldOwnershipTest pins that the remaining pulls are exactly the object-class list.
gen_pipe.py gains SCAN_EXEMPT_ACCESSORS with per-name reasons and retiring phases (it repairs
the --check gate the hd merge's two G6 probes had left red).

Evidence: unit 2175/2175; integration-split 111/111; both generators' --check and
--self-test green; rsp on the Xfb capture scenario 36 -> 22; red-once - gating the emission
off turns seven named scenarios red, restoring returns 111/111. CONTRACT-P5C section 5.3.
2026-09-17 09:27:36 -04:00
swung0x48 04650ca35e Merge branch 'p5c-ct' into feat/disaggregated (P5c ct: applier_reset / object_death control records)
# Conflicts:
#	MobileGL/MG_Backend/DirectGLES/Managers.h
2026-09-17 06:58:45 -04:00
swung0x48 63f8bb1f8f [Docs, MG_Pipe] (Disaggregated): CONTRACT-P5C amendments ratified at the tx/ev/hd landing
Table 0 gains the MGPSurfaceInfo zero-extent semantics row (ev). section 2.1 is amended
to the handle-keyed store with whole-run coverage and defined-ness tracking (tx's three
in-field rulings: a twin-address key would force glGenTextures at adopt time; region-
precise coverage false-Fatals the legal texStorage-then-small-box flow; Levels=N says
nothing about a sparse chain). section 2.3 records Espryt's driver-side mip generation
as a final shape beside Magma's store marking. section 3.1 gains the two named scoped
exemptions (the un-emitted binding records family, P4b/P7; the G6 frontend-keyed
registry family, P3b/P4b) and section 3.3/3.6 take hd's in-field shapes (the verb-handle
workspace with the state note; HasDefinedContent OR staged coverage, because the
streaming idiom defines content through subdata with no new descriptor). section 5.2
takes ct's documented NoSession server-only-fixture arm. FieldOwnership.def moves
InvalidateCompileEnv (field and forward rows) to FATAL with the deletion as mechanism
(P5c ev), regenerated .inc checked in; --check and --self-test green.
2026-09-17 06:57:38 -04:00
swung0x48 b45bf68101 Merge branch 'p5c-hd' into feat/disaggregated (P5c hd: handle-keyed resolution)
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.
2026-09-17 06:57:16 -04:00
swung0x48 a7e55ee62a [MG_Pipe, MG_Remote, MG_Backend, MG_Impl, MG_IntegrationTest] (Disaggregated): P5c ct - the applier_reset and object_death control records
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.
2026-09-17 06:08:54 -04:00
swung0x48 7f5bdd3aa5 [MG_Backend, MG_Remote, MG_Impl, MG_State] (Disaggregated): P5c hd - sinks and twins resolve by the handle the record carried
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.
2026-09-17 04:55:50 -04:00
swung0x48 1eeb4740ca Merge branch 'p5c-ev' into feat/disaggregated (P5c ev: SEG_EVENT reverse channel) 2026-09-17 03:55:46 -04:00
swung0x48 930ad0193f [MG_Remote, MG_Impl, MG_Backend] (Disaggregated): P5c ev - SEG_EVENT is the only reverse channel
The three reverse MGPipeCallbacks entries become the SERVER session's producer callbacks
(Reserve + fill the head + copy the payload inline + PublishEvents; the host pointer never
reaches the wire), installed at Accept and uninstalled at Close, a second installation
over a claimed entry being Fatal{RoleViolation, callback-double-install}. Producers wired:
buffer writeback (the mapped bytes are copied into the record's inline tail instead of a
raw pointer in MGPBlobRef::Offset), GPU-written (Magma's three direct MarkGpuWritten
bypasses are routed through the callback), surface-changed (the backend posts
MGPSurfaceInfo; the client consumer replays the allocate/format writes against
pDefaultFramebufferInfo on the GL thread, where R3's ownership always was), and
kEventGlError = 4 for PipeInputs::RecordError (ordering stays P9's).
PipeInputs::InvalidateCompileEnv's forward is deleted with an active transport - R-12's
caps re-publication already does its job. The consumer's monolith guard is replaced by
the three segment arms (kSegEvent resolves through the session's SegmentTable;
kMGHostSpanSegNone is monolith-only and Fatal{ProtocolCorruption} on the wire; anything
else the same), and DrainEventRing calls the client consumers BY NAME, never through the
global table. Overflow is Fatal{EventRingOverflow} - P5c's events are lossless, P9 owns
the drop policy. Monolith arms keep the pre-P5c expressions character for character.

Evidence: unit 2139/2139 (incl. the new kEventGlError round-trip), integration-split
107/107, monolith GPU subset 24/24; negative control executed - a writeback blobref
reverted to the raw-pointer shape turns DirectGLES.Split.AtomicCounterScenario.
SubDataAfterDispatchSurvivesAnImmediateReadback red with Fatal{ProtocolCorruption,
OnBufferWriteback.Seg}. CONTRACT-P5C sections 1, 4.
2026-09-17 03:55:30 -04:00
swung0x48 4b54a6f902 [MG_Remote, MG_Backend] (Disaggregated): P5c tx - the server's texture staged shadow
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.
2026-09-17 03:55:13 -04:00
swung0x48 851bc70c1d [MG_Remote] (Disaggregated): CONTRACT-P5C - the wire contract for zeroing the inproc split's 59 direct cross-role memory accesses (P5c package c0c)
Texture staged shadow ownership and exact per-level coverage (tx); SEG_EVENT blobref convention with Seg = kSegEvent plus the kEventGlError kind, producer ownership and the consumer segment arm that replaces the monolith guard (ev); handle-keyed sink and twin resolution with MGPipeSlots() ruled a client-only surface (hd); the applier_reset (77) and object_death (78) control records (ct); set_context_values (79) and the residual-value field table retiring every value-class BARRIER_PULLED row (rv); the two Fatal{RoleViolation} guard layers with InBarrierWait wired rather than deleted (gt); FieldOwnership.def and Table 3 amendments; the six E-P5c exit gates.
2026-09-17 01:34:17 -04:00
swung0x48 11ac3de6f9 [Docs] (Disaggregated): plan P5c - audit the inproc split's remaining direct cross-role memory access (59 sites at a79a0af6) and schedule its zeroing before P6 as texture staged shadow, SEG_EVENT producers, handle-keyed resolution, control records, residual-value records and role guards 2026-09-17 00:51:40 -04:00
swung0x48 a79a0af69d [Docs] (Disaggregated): consolidate the five documents at the P5b close - drop superseded narrative, the -O0 device tables and process history, renumber MEASUREMENTS by phase, correct the catalogue and emit-table counts to the head, and record the Redmi pin profile 2026-09-16 23:08:39 -04:00
swung0x48 ea34bccb63 [Docs] (Disaggregated): close out P5b - record the completed host gate and merged census at 348d22a4, the review addendum's pinned results, and the Redmi exit at 82683d4a (correctness 8/8, first barrier-tax measurement, iris-bsl fixture-limit supplement); next is P6 spawn transport 2026-09-16 22:24:56 -04:00
swung0x48 c77831e0fa [Docs] (Disaggregated): rewrite the staged-progress log as a concise current-state document and link it from the roadmap's status line 2026-09-16 20:45:35 -04:00
swung0x48 ef35ea0ce5 [Docs] (Disaggregated): move the integrator's running progress log into the tree as CURRENT_STAGED_PROGRESS.md 2026-09-16 20:43:56 -04:00
swung0x48 7cb29d460b [Fix, Test] (MG_Test/Wire): empty the log before each forked child and read it whole - the library truncates its log per process, so a case that forks three times to drive one refusal through the encoder, the decoder and the sink read the second and third child's diagnostic from an offset past the end and saw nothing 2026-09-16 20:33:38 -04:00
swung0x48 171f186741 [Merge] (Docs, P5b): land the P5b documentation 2026-09-16 20:24:09 -04:00
swung0x48 696acce5d7 [Docs] (Disaggregated): record the P5b inproc verb migration - the class-C census, the five package families and what each removed, and the first Minecraft traces to render on the apply thread 2026-09-16 20:24:08 -04:00
swung0x48 82683d4a83 [Fix] (Remote): honor fence wait budgets and reject invalid readback status 2026-09-16 14:12:14 -04:00
swung0x48 a021e3cc5d [Fix] (MG_Remote): bound user-index draws by the declared span 2026-09-16 14:08:49 -04:00
swung0x48 348d22a4b9 [Fix] (DirectGLES): verify generated mip storage from pushed descriptors 2026-09-16 13:56:55 -04:00
swung0x48 f018c0ef05 [Feat] (MG_Remote, P5b sync): migrate fence lifecycle and wait replies to the apply thread 2026-09-16 13:46:49 -04:00
swung0x48 56c6e49d1d [Feat] (MG_Remote): lower named framebuffer blits through scoped wire bindings 2026-09-16 13:44:35 -04:00