perf(compositor): slice 5 — paste cache for non-opaque layers (take-7/8 data)

The stamped build settled what slices 3-4 could not: chat cache works (resolve
~0.0ms) but render stayed 26-27ms -> 124-135/300. The cost was the compositor
re-rasterizing EVERY layer every tick: this Live scene re-samples chat (159k) +
web widget (271k) + image (95k) + cam (156k) ~ 680k px @ ~38ns — for layers
whose pixels do not change between chat/web/cam updates.

OBS shape: cache the surface, paste per tick. BlitCachedLayer rasterizes a
non-opaque layer ONCE into an element-space, transparent-based frame keyed by
(source-array identity, src W/H, ceil'd dst rect, round, mirror), then pastes:
integer position, row alpha-blend, opacity applied at paste. Producers hand out
fresh immutable arrays -> array-identity keys cannot serve stale content; dict
bounded (48, clears whole). Drag/opacity live in paste params, not keys, so
editing stops triggering resamples too. Opaque backdrop keeps the memcpy path;
the webcam keeps the direct path via its IsOpaque flag (revisit if take 9 is
borderline).

ONE integration test: PasteCache_RepeatRender_IsByteIdentical_And_ContentChange-
Propagates (byte-exact raster-vs-paste incl. round-clip margins, new-array
propagation); existing pixel suite guards sampler semantics. 85/85 across
compositor/pump/chat/capture/session classes, clean build 0 warnings. Also:
BuildStampTests.cs was written last commit but never staged — its own scope-check
slip, added here (the run had used the on-disk file; tracked now).

Docs same commit: ai.md slice 5 + stale 'general path only 130k' claim corrected,
TASKS.md take-9 gate, MyMistakes recipe (prove the stage; a fix that doesn't move
the stat wasn't the bottleneck), HANDOFF. take 9 expectation: 300/300, render
<= ~8ms -> saga closes, Unit B starts.
This commit is contained in:
2026-09-04 11:44:19 -07:00
parent 27bf74389d
commit 6af2026906
7 changed files with 258 additions and 16 deletions
+12 -11
View File
@@ -44,17 +44,18 @@ RealApp boot-smoke. Scope-check passed.
raster on message/config change, blit the cached frame every tick (OBS text-source pattern).
Tests: `ChatOverlayLayerCacheTests` (the ONE, RealApp) + full regression green (62 across
touched classes), clean build 0 warnings.
2. **BUILD STAMP shipped (slice 4, same day):** take 6 came back 35-41ms — WORSE than take 5 —
and attribution was impossible (exe timestamp ≠ binary contents; incremental builds served
unverified exes). Every build now stamps a fresh GUID (`ytLive.csproj GenerateBuildStamp` →
`Helpers/BuildStamp`), shown as the wordmark superscript (build id) + `Build xxxxxxxx (compiled
...)` in startup.log; stats report `avg render Xms (resolve Y)` so the hot half of the tick is
named. Tests `BuildStampTests` + `BuildStampDisplayTests` (real window, namescoped FindName).
3. **Take 7 (user, ~30s record-only):** FIRST read the superscript + startup.log Build line — the
take is meaningless without it. Expect slice 3 in any post-stamp build: `avg render ≤ ~10ms
(resolve ≪ render), ≈300/300`. If resolve dominates → resolver-side surprise (chat config thrash
/ web frame); if blit dominates → measure the general-path element sizes. If chat-burst sags
`n/300`: debounced off-tick re-render slice. If clean → recording saga CLOSED, Unit B.
2. **Takes 7/8 + slice 5 (2026-09-04):** the stamp (f190587b) settled attribution — chat cache REAL
(`resolve ≈ 0`) but render stayed 26-27ms: the compositor re-rasterized every non-opaque layer
per tick (his Live scene: chat+web+image+cam ≈ 680k samples @ ~38ns). Slice 5: `BlitCachedLayer`
— one raster per (source-array, rect, round/mirror), paste at integer pos with opacity; static
layers now cost row-blends, only content changes resample; drag/opacity changes are paste params,
not cache keys. Tests: `PasteCache_...` + 85/85 across compositor/pump/chat/capture/session
classes; clean build 0 warnings. (Cam bypasses the cache via IsOpaque; revisit if take 9 is
borderline. Follow-ups unchanged: vertical-tier alloc, debounced chat re-render on bursts.)
3. **Take 9 (user, ~30s record-only):** confirm the wordmark superscript matches the startup.log
Build line, then stats must show `≈300/300, avg render ≤ ~8ms` and playback 1x. If yes →
**recording saga CLOSED**, Unit B starts. If render ~10-15 → cam-resample cache (drop the
IsOpaque bypass); if n/300 sags only during chat bursts → debounced off-tick raster slice.
4. **Unit B — the top bar + session logic (user spec 2026-09-04 re-sent twice + decisions settled in Q&A):**
- Two-line top bar. Line 1: center = REC + **LIVE** pills (text renamed from ON-AIR; pills become
mutually-exclusive RADIOS — record-OR-stream ruling), right = avatar + **Login/Logout** button