mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
Docs (P15): per-context pipe dirty mask plan.
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
# Per-context pipe dirty mask: plan (2026-10-09, not started)
|
||||
|
||||
Replaces `MGPipeTracker::Update`'s per-verb polling of ~25 frontend counters with a per-context
|
||||
mark word. Mutations OR in the subsystem bits they affect; the validate point visits only the
|
||||
marked bits and keeps today's leaf compare. A sharper form of PLAN-P15 stage 1; it is client-side
|
||||
only and the record stream stays byte-identical on every transport.
|
||||
|
||||
## Expected savings (revised by the planning pass)
|
||||
|
||||
The pool this change can touch on FCL is ~0.04-0.06 ms/frame:
|
||||
- `Tracker::Update` 0.018-0.029;
|
||||
- `CopyField` 0.018-0.021;
|
||||
- part of `MGPipeValidateForVerb` self.
|
||||
|
||||
| workload | gain (ms/frame) |
|
||||
|---|---|
|
||||
| FCL | ~0.012-0.03 |
|
||||
| rd12 | ~0.10-0.15 |
|
||||
|
||||
The 0.3-0.5 of PLAN-P15 stage 1 also skipped `ReadDrawBindings`, `MarkGpuWritesForDraw` and the
|
||||
backend walks, which are out of scope here.
|
||||
|
||||
## Design
|
||||
|
||||
- `GLContext` gets:
|
||||
- `Uint32 m_pipeDirty = ~0u`;
|
||||
- an owner serial (`TakePipeDirty(trackerSerial)` returns all bits to a different tracker, so
|
||||
the verify shadow walk or a test tracker cannot steal marks);
|
||||
- `m_seenShareClock`.
|
||||
- Root check = mask 0. No separate counter.
|
||||
- Shutter groups are the visit unit, computed with today's formulas word for word:
|
||||
|
||||
| group | bits | covers |
|
||||
|---|---|---|
|
||||
| GR | 0, 1, 3 | render state, pipeline state, patch |
|
||||
| GK | 2 | pixel pack |
|
||||
| GA | 4 | attrib defaults |
|
||||
| GV | 5, 9, 10 | VAO, vertex buffers + base instance, index |
|
||||
| GP | 6, 7, 8 | program walk |
|
||||
| GT | 12, 13, 14 | texture aggregates, bind generation, max unit |
|
||||
| GF | 11 | framebuffer |
|
||||
| GB | 15, 16, 17 | bind points |
|
||||
|
||||
Closure: GT and GB also compute GP, because they mix `shader`, `opaqueUnits` and `programImages`.
|
||||
- **Share-group clock:** in `ShareGroupState`, an atomic gen plus a context count.
|
||||
- Bumped by:
|
||||
- the unclassified-entry prologue;
|
||||
- classified bodies that write shared objects (`Uniform*`);
|
||||
- texture aggregates;
|
||||
- `BumpSamplingResolutionGeneration`;
|
||||
- link publish.
|
||||
- The bump is skipped while the group has one context; a later context starts with all bits.
|
||||
- Ordering: release `fetch_add` after the object writes, acquire load in the tracker.
|
||||
- Fan-out `{6, 7, 8, 12, 14, 15}`.
|
||||
- **Pseudo-bit CV** (bit 18) for `EmitContextValues`. Its own accumulator is consumed only when a
|
||||
verb class that reads context values runs.
|
||||
|
||||
## Mark sites (chokepoints first; no per-call-site edits where a funnel exists)
|
||||
|
||||
| site | marks |
|
||||
|---|---|
|
||||
| M1 RenderState: sink from the `GLContext` constructor | `BumpVersions` → GR; the bare `++m_version` sites go through one `BumpRenderVersion()`; `SetPixelStoreParam` → bit 2 |
|
||||
| M2 `BindingSlot` change sink (`MG_Util/Types.h`), marks only on a real change | FBO draw/read slots → 11; each VAO's index slot → 10 |
|
||||
| M3 TextureState | `NoteUnitTouched`, bind generation, content/params notes → GT, plus CV when the high-water mark grows; sampling-resolution bump → GT + clock; `SetActiveTextureUnit` → CV |
|
||||
| M4 BufferState | bind-point change → GB by target; `TouchBindPoint` → CV |
|
||||
| M5 VertexArrayState | bind, delete, attribute change → GV |
|
||||
| M6 FramebufferState | attachment change → GF |
|
||||
| M7 GLContext | `UseProgram` / pipeline bind / pipeline delete → GP\|GT\|GB; XFB state → 17 + CV; attrib defaults → 4 |
|
||||
| M8 `MGPipeNoteAggregate` | via M3-M6; clock bump for TextureContent/TextureParams/FramebufferAttachment |
|
||||
| M9 `BumpLinkObservableVersions` → `MGPipeNoteProgramObjectChanged()` | covers link, both joins and the binary failure path; the hot `ProgramObject` setters get no marks (a TLS read per uniform write) |
|
||||
| M10 tracker carry | pending base instance → GV |
|
||||
|
||||
**Export prologue.** `DECLARE_GL_FUNCTION_END*` (`Definitions.cpp`) runs `MGP_ENTRY_PROLOGUE(name)`
|
||||
with a compile-time lookup in a new `MG_Pipe/PipeDirtyEntries.def`.
|
||||
- Default: mark all bits and bump the clock.
|
||||
- A `kBodyMarks` row means the body and funnels mark.
|
||||
- Hand-written exports that do not forward (`glFinish`, `glFlush`, debug stubs, ...) call the
|
||||
prologue themselves.
|
||||
- EGL/GLX/WGL/CGL entries mark all bits.
|
||||
|
||||
**Events outside GL entries:**
|
||||
|
||||
| event | coverage |
|
||||
|---|---|
|
||||
| MakeCurrent | tracker `Reset` + mark all on the incoming context; after the TLS rebase, move this into `CurrentGLContextSlot::operator=` |
|
||||
| surface resize | not a tracker input (frontend default FB is a fixed placeholder); EGL mark-all covers it anyway |
|
||||
| deletes that unbind | mark-all, plus the M2/M5 sinks on the inner unbinds |
|
||||
| buffer realloc | no shutter reads buffer size; no change |
|
||||
| GenerateMipmap mip growth | entry mark-all; the inner path also goes through the M8/M3 funnels |
|
||||
| context reset / new session | `MGPipeForgetEndedSession` → `Reset` → all bits |
|
||||
| backend in-verb writes | the M3/M8 funnels |
|
||||
| async link completion | M9 |
|
||||
| other contexts in the group | the share clock |
|
||||
|
||||
## Validate path
|
||||
|
||||
- **`Update` visit set:** the context marks, the tracker carry, the share fan-out when the clock
|
||||
moved, and all bits until primed; mapped to groups and closed.
|
||||
- If the set is empty, return 0.
|
||||
- Otherwise compute, compare and latch the visited groups only.
|
||||
- **Oracle** (verify builds, `PipeVerify` armed): a shadow full walk F against the gated walk G.
|
||||
- It requires F == G, else `Fatal{PipeDirtyUnderMark, "<bits>@<verb>"}` with a ring of the last
|
||||
8 entry names.
|
||||
- It honours `VERIFY_FATAL=0`.
|
||||
- **Emission:** wrapped in `if (liveDirty)`. Fired bits, and therefore records, are unchanged.
|
||||
- **CV accumulator:** the existing generation and suppressor fast path stays as a second line of
|
||||
defence. `VerbMaskReadsContextValues` is precomputed per class.
|
||||
- **Optional residual gating (C5)** is limited to rows that depend only on the address or the
|
||||
context identity.
|
||||
- It is keyed on context id, the bound VAO lifetime id, a fill-plan rebuild epoch and an
|
||||
applier-reset / pin-release epoch.
|
||||
- `GetProgramForDraw` / `GetProgramForDispatch` are never skipped: `CopyField` joins pending
|
||||
links and refreshes the uniform mirror there.
|
||||
- The accumulator is cleared only inside `fillOwed`; under run-ahead, unbarriered verbs keep it.
|
||||
- Poison stamps still stamp every walk field.
|
||||
- **Left alone:** the run-ahead/barriered predicate, `QuiesceApplierBeforeFill` and the dual-block
|
||||
(`ROLE_SPLIT_STATE`) semantics.
|
||||
|
||||
## Found during planning: a pre-existing cross-context gap
|
||||
|
||||
Shutters 11-14 compare per-context aggregate counters that a texture or renderbuffer change bumps
|
||||
only on the live context.
|
||||
- Effect: a change made through context B, with no verb on A in between, does not fire A's
|
||||
framebuffer or texture bits.
|
||||
- The mask keeps today's bytes exactly (visit only).
|
||||
- Closing the gap means mixing the share clock into shutters 11-14 (C8). That changes the wire for
|
||||
multi-context traces, so it is a separate decision.
|
||||
- FCL is single-context.
|
||||
|
||||
## Tests
|
||||
|
||||
- **Existing, unedited:** `TrackerTest` (`TrackerWalk`, `TrackerShippedEmitter`,
|
||||
`TrackerAggregates`) stays green; `RecorderGoldenTest` goldens are unchanged.
|
||||
- **New `TrackerDirtyMarks`** table test, one row per tracker input. Each row:
|
||||
- mutates through the real mutator, not the export, so mark-all cannot hide a gap;
|
||||
- asserts the fired bits match the full walk;
|
||||
- asserts the expected bits were marked.
|
||||
- **New share-group cases:**
|
||||
- deterministic: A walks, B writes a uniform on a shared program, A walks again and bit 8 must
|
||||
fire;
|
||||
- threaded: a hand-off between threads, checked by readback in `ConcurrentContextsTest`.
|
||||
- **Red-once:** drop each funnel's mark in turn and see its row fail.
|
||||
- **CI:**
|
||||
- `integration-verify` runs the oracle fatally.
|
||||
- Verify-only negative control `MOBILEGL_PIPE_DIRTY_DROP=<site>`, one site per class.
|
||||
- With `FATAL=0`, `entry-prologue` lists every entry still relying on mark-all.
|
||||
- `gen_pipe_dirty_surface.py --check` gains mark coverage.
|
||||
- A scanner CTest checks that every export has a `.def` row or the default.
|
||||
- **Wire identity:** spawn/inproc/TCP arms, `spawn_lane_parity.py`, the retrace legs, and PipeStats
|
||||
fire counts identical to the baseline.
|
||||
|
||||
## Measurement
|
||||
|
||||
- **Host:** PipeStats per-group visits, the TrackerRoot hit rate, fires equal to the baseline.
|
||||
- **Device:**
|
||||
- cpuhunt, FCL monolith on both backends + rd12, 3 interleaved reps.
|
||||
- Read from profiles: `Tracker::Update`, `ValidateForVerb` inclusive/self, `CopyField`, the
|
||||
prologue, and the TLS resolver for extra reads.
|
||||
- fps only as a no-regression check.
|
||||
|
||||
## Commits
|
||||
|
||||
| # | commit | size | risk | gain |
|
||||
|---|---|---|---|---|
|
||||
| C1 | regroup `Tracker.h` into shutter groups; still visits all | ~+250/-150 | low | 0 |
|
||||
| C2 | mark plumbing M1-M10, prologues mark-all, `.def`, DROP knob, oracle in audit mode | ~+500, 14 files | medium | 0 (prologue +~0.004) |
|
||||
| C3 | gate on, fatal oracle, share-group tests | ~+150 | medium | most of the `Update` cut |
|
||||
| C4 | CV accumulator | ~+40 | low | ~0.002 FCL |
|
||||
| C5 | residual gating (optional, measure first) | ~+120 | medium | 0.005-0.01 FCL / 0.03-0.05 rd12 |
|
||||
| C6 | classify the FCL between-draw entries, table test, scanner, CI controls | ~+400 | medium-high | the actual win |
|
||||
| C7 | docs, SCOREBOARD | small | - | - |
|
||||
| C8 | optional, separate approval: close the cross-context aggregate gap | ~+60 | wire change in multi-context traces | correctness |
|
||||
|
||||
**C6 entry list:**
|
||||
- draws (free);
|
||||
- `BindVertexArray`, `BindBuffer`, `BindVertexBuffer`;
|
||||
- float `Uniform*` / `UniformMatrix*` → {7, 8} + clock; `Uniform1i` → {7, 8, 12, 14};
|
||||
- `UseProgram`, `BindFramebuffer`;
|
||||
- `Viewport`, `Scissor`, `PolygonMode`, `BlendFunc*`;
|
||||
- `Enable`/`Disable`, once every capability path is confirmed to use `SetCapability`;
|
||||
- the depth, colour and stencil setters;
|
||||
- `BindTexture`, `ActiveTexture`, `BindSampler`.
|
||||
|
||||
**Overlap with the TLS work (`tlshoist`):**
|
||||
- `Core.h` / `Core.cpp`: separate hunks.
|
||||
- `PipeFill.cpp`: medium conflict around `gPipeInputs` and `MGPipeNoteAggregate`.
|
||||
- `TrackerTest` fixtures: use `.Take()`.
|
||||
- `Definitions.cpp` macros and the GLImpl entry bodies: conflict if TLS hoists there.
|
||||
|
||||
Order:
|
||||
1. C1 can run now.
|
||||
2. C2-C5 after the TLS merge.
|
||||
3. C6 after the TLS per-entry hoist.
|
||||
Reference in New Issue
Block a user