fix(pump+compositor): take-3 starvation — deadline pacing + row-blit fast paths

Two defects made the producer 17x slow (37s record -> 2.1s/127-frame file,
rawvideo stamps by arrival): FramePump slept the FULL interval after each
render (period = render+submit+interval) and SceneCompositor did per-pixel
float sampling + Math.Round blends over all 2.07M master pixels, scanning the
whole destination per overlay (258ms avg render vs 1.5ms submit).

Both solutions are established, not invented — researched before coding per
the derivative-work rule:
- deadline pacing: OBS libobs/media-io/video-io.c video_thread (nextTick +=
  intervalTicks, sleep only the remainder, rebase on overrun, never burst)
- row blits: libyuv pattern (BSD-3, chromium.googlesource.com/libyuv/libyuv)
  — 1:1 aligned identity fast path, per-pixel alpha branch, integer
  fixed-point blend, overlay clipped to the intersection rect, skip the dead
  black pre-fill when the backdrop covers

ONE integration test: Pump_Paces_To_The_Deadline_Compensating_Render_Cost
(lands after a fake-seam lesson: pacing fakes must await, not complete
synchronously, or the pump loop runs inline on StartAsync and hangs vstest).
Clean build 0 warnings; FramePumpTests 10/10, SceneCompositor/SceneGraph/
SocialBar/StretchMath 20/20. Docs same commit: ai.md pipeline section,
TASKS.md TASK 18 (webcam-in-output + rename modal verified from take 3),
MyMistakes recipe, HANDOFF rewritten. Take 4 pending on the user's machine.
This commit is contained in:
2026-09-03 09:02:45 -07:00
parent 7fcb2ad3da
commit 716a77f61a
7 changed files with 306 additions and 75 deletions
+30
View File
@@ -47,6 +47,36 @@ packages, high-quality downscale via `TransformedBitmap`. Screenshots compress
---
### Feeding a rawvideo pipe at 60fps: deadline pacing + row-blit budget
Derived 2026-09-03 (take-3 diagnosis — the stats seam from `97ffc42` named the stage
in one line: `17/300 frames per 5s, avg render 258.1ms, avg submit 1.5ms`).
Both halves were solved by OBS/libyuv long ago; do not re-derive:
1. **Pacing is a DEADLINE, never a post-render sleep.** `sleep(interval)` after each
frame makes the period `render + submit + interval` — the producer can hit ≤ half
the declared rate even with a free render. OBS's `video_thread`
(`libobs/media-io/video-io.c`) advances an absolute `nextTick += intervalTicks` and
sleeps only the remainder; if the deadline blew, skip the wait AND the missed ticks
(rebase, no catch-up burst — a burst queues stale frames). Critical with rawvideo:
pts is stamped by ARRIVAL, so a starved producer silently time-lapses the file.
2. **A 1080p frame is ~2.07M pixels — the hot path must be row-simple.** Per-pixel
`Math.Round` + float source-over in managed code costs ~100ns/px = the whole 258ms.
libyuv's pattern (https://chromium.googlesource.com/libyuv/libyuv/): branch per
pixel on source alpha (opaque → 4-byte copy, transparent → skip), integer
fixed-point blend `(s*a + d*(255-a) + 127)/255` otherwise; and ALWAYS clip the loop
to the intersection rect (our social-bar overlay scanned all 2M dst px for a 64px
strip). A full-cover 1:1 blit also obsoletes the opaque-black pre-fill — skip dead
writes.
3. **Hermetic pacing test:** inject the delay seam to RECORD the requested TimeSpan and
genuinely await it (`Task.Delay(d, ct)`) — a fake that returns
`Task.CompletedTask` synchronously makes the whole pump loop run on `StartAsync`'s
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.
---
## Splitting a large file into partials — NEVER `awk … > SRC` while awking SRC
(2026-08-31, Commit D) Tried to split `SocialsDialogViewModel.cs` in one line: