perf(chat): slice 3 — raster-on-change cache in ChatOverlayLayer (take-5 fix)
Take 5: render 58.9 -> 25.5ms (138/300, still ~2.2x). The blits were fixed; the resolver was not: ResolveOutputFrame -> RenderChatBox ran a FULL WPF raster (FormattedText + RenderTargetBitmap + CopyPixels + channel swap) EVERY tick whenever the chat buffer was non-empty — and the buffer survives sessions, so even a signed-out record-only take paid it. Established answer (OBS text sources): re-render on change, blit the cache every tick. ChatOverlayLayer: content version bumped from Messages.CollectionChanged (covers adds, the 500-cap removal, the fade Clear from any caller) + a config key (size + all Chat* appearance props); RenderFrame returns the cached VideoFrame by identity until either changes (compositor only reads cached frames). Conservative ordering (version latched BEFORE render) makes a mid-render message re-render next tick, never serve stale. ONE integration test: ChatOverlayLayerCacheTests (RealApp, real renderer): Same() for unchanged inputs, NotSame() on message/config change, null on empty. Full regression green (62 across touched classes), clean build 0 warnings. Accepted cost pending take 6: one ~15-25ms tick per arriving message; if live-chat bursts sag n/300, next slice = debounced off-tick re-render. Docs same commit: ai.md pipeline section, TASKS.md TASK 18, MyMistakes recipe (raster-on-change + session-surviving-buffer trap), HANDOFF (take 6 -> then Unit B, spec unchanged).
This commit is contained in:
@@ -74,6 +74,14 @@ Both halves were solved by OBS/libyuv long ago; do not re-derive:
|
||||
sync continuation and hang the test run (hit this 2026-09-03; the existing fakes all
|
||||
yield for exactly this reason). Assert the REQUESTED wait (< interval with a
|
||||
≥cost-ms fake render) — never wall-clock rate, which flakes on loaded machines.
|
||||
4. **Expensive content: raster on change, never on read (take 5, 2026-09-04).** A source
|
||||
that updates once a minute (chat text!) must not full-rasterize (`FormattedText` +
|
||||
`RenderTargetBitmap` + `CopyPixels` ≈ 15-25ms) every compositor tick. OBS text sources
|
||||
re-render on property/message change; the per-tick pass blits the cache. Implement as:
|
||||
content version (collection-changed counter) + config key (size/appearance) → cached
|
||||
immutable `VideoFrame` returned by identity. Gotcha: buffers that SURVIVE sessions
|
||||
(the chat log) silently arm the per-tick cost even in flows that never touch the
|
||||
feature (signed-out record-only takes paid chat rendering!).
|
||||
|
||||
**Take-4 follow-ups (2026-09-04) — the symptom needed a second pass, so cite again:**
|
||||
render was still 58.9ms after slice 1. Slice 2 (buffer pool + opaque-row memcpy +
|
||||
|
||||
Reference in New Issue
Block a user