Commit Graph
29 Commits
Author SHA1 Message Date
swung0x48 7218bacb49 [Feature] (SharedImage): sample NV12/P010 dma-bufs through GL_OES_EGL_image_external on both backends, copying foreign buffers into server YUV images 2026-10-03 22:46:00 -04:00
swung0x48 15f9b27a42 [Fix] (P14): publish a cached sampler CSO into each share group that names it 2026-10-02 10:35:44 -04:00
swung0x48 4ed1c9cef2 [Feature] (P14): state ownership - multi-session, multi-context, share groups
Breaks the one process = one server = one session = one context equation
that the whole stack's correctness used to rest on. Design and per-slice
shape: docs/Disaggregated/design/11-state-ownership.md.

S1 wire: bind_context ring record (opcode 84) announces the current
context at the make-current edge; CreateContext/DestroyContext join the
SurfaceOp control frames; control revision 5 -> 6; the server keeps a
per-session token table and refuses unknown tokens by name.

S2 server: SessionRuntime per session with thread-scoped resolution
(singletons become fallbacks), per-thread gPipeInputs, per-session latch
domains, per-thread segment resolvers; the in-process unix supervisor
serves up to MOBILEGL_IPC_INPROC_MAX_SESSIONS (16) concurrent sessions;
surface mode is declared per session and the display lease refuses a
second on-screen session by name.

S3 frontend: ShareGroupState owns the shared object stores and name
generators (buffer/texture/renderbuffer/sampler/program), GLContext keeps
per-context bindings/errors/container objects; pGLContext becomes
thread-local with a process default context preserving the single-context
world verbatim; eglCreateContext builds a real GLContext and joins the
sharing context's group.

S4 backend: native EGL tuples keyed by (session, context token);
InitDisplayAndContext's unconditional teardown is gone - creating a
second context or surface no longer destroys the first; eglCreateContext
sharing is wired to the native driver; bind_context is the native switch
point.

S5 applier: MGPipeApplierState splits into MGPipeObjectRecords (per
share group) and MGPipeWorkingState (per context), resolved through a
{session, token} registry with a thread-local cached fast path.

Tests: MultiSessionTest (incl. two sessions x two contexts interleaved
with real clear/readback witnesses), ApplierOwnershipTest (with a
null-resolver negative control), ContextStateOwnershipTest,
ServerLoopEglTest native-context liveness; 99/99 of the acceptance set
green on the host build.

Known debts (documented in the design doc): twin registries still keyed
by handle alone, no per-record ContextSlot, process-global backend
generations, MakeCurrent dedup per current binding.
2026-10-01 19:16:41 -04:00
swung0x48 ec5f6ead3f [Test] (P11 M2): TextureEmit.MoreThanKMaxDirtyRectsCollapsesToTheBoxWithRegionCountZero keeps its name and pins the one collapse left - a footprint larger than a record carries falls back to the storage's rect list 2026-09-29 12:26:10 -04:00
swung0x48 9145433890 [Fix] (P11 M2, Espryt, Magma): the texture emitter carries a level's exact dirty footprint as its regions (dev 9524c688) - a single partial box as one region, only a whole-level write as none - so both servers upload exactly the texels written over GPU content; the applier keeps it exact up to 4096 regions, and Espryt's handle arm applies the storage's box-or-rects cut-offs itself while its texture holds no GPU writes 2026-09-29 12:16:55 -04:00
swung0x48 6c2f8a560c [Fix] (Espryt, P9 W2): a texture re-mint never needs the client - read back only levels the driver holds, replay the staged level when the readback is refused, count trp on the server path, and retire the texture-remint-pull markers and OnTexturePullRequest (callbacks 9 -> 8) 2026-09-29 03:23:54 -04:00
swung0x48 0535c4f07d [MG_Pipe, MG_Remote, MG_Backend] (P7): bind staged bytes to declared mip extents 2026-09-23 13:43:44 -04:00
swung0x48 5fb54f03fe [MG_Util, MG_Test, MG_IntegrationTest, CI] (P6): one log per role, and every reader moved with it
The spawn work had the two roles append to one MOBILEGL_LOG_FILE_PATH under
O_APPEND. It was correct - two processes at one offset would corrupt the file,
O_APPEND made each write atomic - but it put two sessions' lines in one place
and made every per-side assertion a search. This splits the sink: the client
role writes <base>.client.log and the server role writes <base>.server.log.

THE BASE NAME NAMES NO FILE ANY MORE, AND THAT IS THE POINT. Leaving the
client at the old unsuffixed path would have been the compatible choice and the
wrong one: a reader that was not updated would go on finding a file, go on
passing, and go on seeing only half the session - and a refusal census that
reports a confident zero for the half it cannot see is worse than one that
fails. With both names moved, an un-updated reader gets "file not found" and
says so. There were 34 such readers; every one is moved here.

THE ROLE IS PER-THREAD, not per-process, because under inproc both roles are
threads of one process. Log.cpp keys the sink on a thread-local role: the apply
thread declares itself server (ServerLoop.cpp), a spawned image is server for
its whole life (MOBILEGL_IPC_ROLE=server, read once), and everything else is
the client. The naming rule (RoleLogPath) and the two whole-log operations
(ReadRoleLogs / TruncateRoleLogs) are EXPORTED from the library, so no reader
reimplements the derivation - a second copy is the copy that falls behind.

Readers, by how they read:
 - unit mains set MOBILEGL_LOG_FILE_PATH and read a role file: they keep the
   base in the env and derive the client path from the library helper.
 - the death-test child runners (ReadLog) now concatenate BOTH roles: a death
   test asserts the CHILD said something, and the applier's refusals - the very
   diagnostics these check - are written under the server role.
 - the offset-based Emit readers derive the client path once after setenv.
 - PipeStatsWindow reads both, because draws/vbs increment in the backend's
   PrepareForDraw - the apply thread, the server role - so the summary line is
   in the server file even under inproc.
 - run_trace_case.cmake's split census reads both files; the client keeps the
   transport markers.
 - split_log_paths.py (and its self-test, and the control stub) derive both
   role files from the declared base.
 - the Android script pulls both role files off the device and concatenates
   them, so every downstream check is unchanged - and it judges a role file by
   CONTENT, because `run-as test -f` through `exec-out` returned 0 for a missing
   file on the device and `exec-out` folds cat's own not-found line into stdout.
   The monolith arm legitimately has no server file, and that is a silent skip
   rather than a one-line server log.

Guarded so a pull build is untouched: RoleLogPath and the role sink are wholly
inside MOBILEGL_BUILD_DISAGGREGATED; a non-disaggregated build writes the base
path exactly as before. G1 re-measured - 0 symbol diff, .text unchanged at
0xa52203.

Verified on device 2f7cbe2e: monolith writes only the client log (no phantom
server log), inproc and spawn each write both and pass their transport proofs
(spawn: server role in pid 16125). Unit 2353 (one pre-existing environmental
failure), integration-split 186/186, integration-spawn 102/102, retrace 6/6,
control smoke 12/12, split-log-paths self-test 9/9.
2026-09-21 21:26:05 -04:00
swung0x48 7059869848 [Test] fix the two new stage-chunk cases' own expectations
Both cases were written against the wrong property and went red on the first WGB
build, which is the one thing a case may not do.

TextureEmit.ALevelTooLargeForTheStageChunkIsCutIntoSlabs asserted D == 8 slices for
every slab of the 512x128x33 level, whose last slab is the 33rd slice alone. The
per-slab check now names the scheme (Z = 8 * index, D = 8 for every slab but the
remainder) instead of restating one number for all of them.

StagedTextureStoreTest.ALevelWithAPieceMissingIsRefusedAsAWholeLevelReadByName
wanted "not covered" out of a single LEADING run, and the store cannot answer that:
it knows no format, so it cannot know how many bytes the level should have - the
check is "the range the reader asks is covered". The case now uses the shape the
check really is about, two runs with a hole between them (ServerLoopTest's
R-11 coverage case, one store over), and the looseness is written down: in the
header's coverage ruling, and as an assertion on the leading-run case beside it.
2026-09-20 19:22:38 -04:00
swung0x48 9469d48edb [MGPipe, MG_Remote] cut a texture level's staged run at the stage chunk
ONE RECORD'S BLOB IS STAGED WHOLE IN SEG_STAGE, a linear arena, so a texture
level larger than the segment's chunk budget was Fatal{RingOverrun, "SEG_STAGE"}
at the encoder rather than a split - and the CI traces hit it: the texture half of
resource_subdata stages the whole level shadow regardless of how little of it
changed, and a 512x128x33 GL_RGBA32F level is 34,603,008 bytes while a 192-cube
GL_RGBA16 level is 56,623,104, both against the default 32 MiB segment. Three
retrace cases aborted there: iterationt-in-world and iterationt-nodsa-in-world in
both backends, and iterationrp-in-world in DirectVulkan.

THE CLIENT now cuts such a level into WHOLE-WIDTH SLABS, one resource_subdata
each (TextureEmit.h's MGPipeForEachTextureSlab, driven at MGPipeStageChunkBytes -
the same cap PipeFill.cpp's buffer walks are cut at). A piece is a set of
consecutive rows of the level shadow, so its box names both its texels and its
byte run; whole slices are the coarsest cut, a slice bigger than the cap falls
back to whole rows and a row bigger than the cap to whole texels inside one row.
Every piece is emitted, dirty or not: the server assembles one level image out of
the runs, so a piece left out would leave a hole no texel read can answer. Each
piece carries at least one region (its clipped dirty rects, or its own box when
none reaches it) because RegionCount == 0 remains the whole-level spelling, and
each region's SrcOffset is rebased onto its own piece's run - MGPipeTypes.h has
always said SrcOffset is "into the blob" - while the strides stay the level's. A
level that FITS keeps the single whole-level record, field for field what it has
always been, which is what every existing gate is written against.

THE SERVER now assembles levels from those runs. StagedTextureStore::AdoptRun
places one run at an image offset, its coverage set is exactly the runs it has
adopted (StagedShadowStore's CoverageAdd, the buffer half's helpers), and only a
FULLY covered level satisfies IsCovered / RequireLevelBytes - a level with one
piece missing is as unreadable as one that never arrived, and it is the same
Fatal{StageSnapshotTooNarrow}. The whole-level spelling survives as Adopt, its
offset-0 replacement, and the two are told apart by the run's own LENGTH: the
whole-level run is the level's complete shadow whatever its box says, a piece's
run is exactly its box's byte extent. Both adoption call sites -
Ops_H_TextureSubData and PipeApply.cpp's hookless fallback, which is what Magma
takes - read the offset that way.

THE PROTOCOL IS UNCHANGED: no field moved, no caps bit was added. The one
compatibility statement is that a split level now crosses as several records of
one apply batch, so an OLD server (which replaces the level on every record) would
keep only the last piece - inproc runs both roles out of one library, so the
constraint is noted here rather than armed with a gate.

Tests. TextureEmit.ALevelTooLargeForTheStageChunkIsCutIntoSlabs drives the split
at MGPipeStageChunkBytesFor's clamp with both trace shapes (5 and 7 slabs), the
row-granularity and texel-granularity fallbacks, the three shapes that refuse to
cut, the region clipping/rebasing, and the piece no rect reaches.
StagedTextureStoreTest gains the multi-run assembly, the gap that must not read as
coverage, the partial-level Fatal by name, the run-offset discriminator, and two
production cases - the applier's hookless arm and DirectGLES's real TextureSubData
hook - that would put both halves at the front of the image if the offset were
dropped.
2026-09-20 19:19:47 -04:00
swung0x48 677922a930 [Tests] pin full texture staging extents beside scattered upload regions 2026-09-20 12:49:50 -04:00
swung0x48 bf30909aab [Fix, Test] (MG_Test): include the header that defines MOBILEGL_PIPE_POISON - seven emitter suites tested the macro without it, compiled the non-aborting arm, and turned eighteen expected refusals into aborts the moment a disaggregated build armed it 2026-09-11 13:05:05 -04:00
swung0x48 420c0dbdfd [Fix, Test] (clientfb): heal a texture's record from its first set_texture_params - a texture born while the family was not live (the context's default textures are constructed before the backend registers its consumer) has no record, so the application's first glTexParameter* on texture 0 was refused, silently latched away before m-1 and the retrace census's one residual after it went loud; the params path now publishes the create, the storage if the texture has any, and then the parameters, the respecify path's own self-heal shape 2026-09-09 00:43:38 -04:00
swung0x48 35f3468edb [Fix, Docs] (MG_Pipe, clientfb): latch set_texture_params on acceptance, and two comments - the emitter advanced its version latch before the applier answered, so a refused record (no record for the handle, no consumer) was not re-sent until the next glTexParameter* moved a version (final review m-1, audit F-7): the applier now returns whether the record took the parameters and the emitter latches on true, counting and naming a refusal; Config.h's mask table gains the fourth D-K2 row (0x400 requires 0x800), and MGPipeTypes.h's metadata paragraph says what the applier does (replaces the descriptor whole with the client's sticky OR) instead of "ORed, never replaced" 2026-09-09 00:16:35 -04:00
swung0x48 a7d56a2fe5 [Fix, Test] (MG_Pipe, clientfb, clientsp): retire every emitter's entry at the object's death - the six death helpers freed the slot and told no emitter, so a dead-but-unrecycled texture handle still resolved to the freed ITextureObject* (the allocator's generation moves only at the next hand-out) and the drain list kept the level: glTexImage2D; glDeleteTextures; <any verb> called a virtual on freed memory from the next validate point (final review C-2); the helpers now forward to the texture, renderbuffer, framebuffer, sampler-view and shader-CSO emitters between the wire delete and the free, ResolveTexture refuses a dead slot loudly on IsLive, the sticky-mask producers stamp the generation they write under, and the delete-then-use sequence is pinned for every kind, for a recycled slot, and under MALLOC_PERTURB_ on both backends 2026-09-08 23:59:48 -04:00
swung0x48 4bb5e082d3 [Fix, Test] (clientfb, MG_Pipe): pass the level a texture respecify redefines - every per-level glTexImage*D and glGenerateMipmap grow took the applier's whole-resource arm and dropped the texture's every pending upload, including a level the applier had accepted at an earlier verb with the client's dirty flag already clear, so L0; draw(other); L1; draw(T) read a black level 0 on the handle arm (final review C-1); the storage entry points now state the scope (one level, a chain cut, the whole resource), the emitter builds wire's MGPRespecifiedLevel with the drain's packed target, a per-level call is never deduped on the descriptor, and the applier drops a named level whether or not the descriptor moved 2026-09-08 23:55:44 -04:00
swung0x48 33a7c554c5 [Test] (Pipe): erase only the cube face a respecify names, not every pending upload that shares its level 2026-09-08 19:08:12 -04:00
swung0x48 150bc4292a [Fix, Test] (MG_Impl, Pipe): gate the four P4a families on D-K2's dependency bits as well - Espryt refuses bit 10 without bit 11 or bit 7, but the client had already emitted and the acceptance-cleared level flags left its legacy arm nothing to upload 2026-09-08 18:35:01 -04:00
swung0x48 55990c982e [Fix, Test] (Pipe): spell the skip twin's line continuation as a real newline - the previous commit left a literal backslash-n and the pull build of the suite did not compile 2026-09-08 17:47:01 -04:00
swung0x48 351baba677 [Test] (Pipe): give the no-consumer texture gate case its pull-build skip twin so the pull and push test-name sets stay equal 2026-09-08 17:46:05 -04:00
swung0x48 f96f22ebbd [Fix] (MG_Pipe, MG_Impl): gate the four P4a families on a backend having registered MGPipeResourceOps - Magma emitted, the applier accepted, and the acceptance-cleared dirty flags left its legacy path nothing to upload 2026-09-08 17:37:37 -04:00
swung0x48 782075ce78 [Fix, Test] (Pipe): reconcile the framebuffer and texture emit suites after the client replay - the union of wire's and the client's cases in one file, spelled against the contract's Named enumerator 2026-09-08 16:54:50 -04:00
swung0x48 e540ffce17 [Test] (Pipe): pin the region tail against what the applier stored, the dirty flag surviving a refusal, the metadata respecify an immutable texture needs, the per-object Named record and the six minors 2026-09-08 16:52:11 -04:00
swung0x48 fcfdc41380 [Feat, Test] (Pipe): wire the framebuffer subsystem bit and pin the resolved read surface, the sticky bind mask, the level-shadow strides and the union-box/region-list invariant 2026-09-08 16:52:11 -04:00
swung0x48 8f1eaafa79 [Test] (Pipe): pin what a Named framebuffer record does and does not move, the stale-generation refusal on the per-object table, the two handles it will not store, and that a respecify which redefines no storage carries the mask without eating the texels 2026-09-08 15:07:23 -04:00
swung0x48 08922b6f78 [Fix] (Pipe): read a sub-data record's target halves through the contract's own accessors, answer the emitter whether a create or a respecify was accepted, and apply a respecify that restates the stored storage as a metadata update rather than a redefinition 2026-09-08 15:07:23 -04:00
swung0x48 5ab90dec24 [Test] (Pipe): pin a respecify's per-level scope, the sub-data acceptance signal, the upload targets that name no texture and what a release of the object records clears 2026-09-08 15:07:23 -04:00
swung0x48 ae1a1c503f [Test] (Pipe): pin every P4a record's lifecycle, its bounds gate and what a make-current does and does not clear 2026-09-08 15:07:23 -04:00
swung0x48 08192d7266 [Feat] (Pipe): land the P4a contract - the resource target enum, the framebuffer target byte, the texture params' builtin sampler, the sampler-parameter field table, four subsystem bits, seven dirty arms and the program archive codec 2026-09-08 12:18:29 -04:00