perf(pump): slice 6 — break the 15.6ms sleep quantum (the REAL ceiling behind takes 7-9)
Take 9's numbers were decisive: paste cache moved work to ~25ms/frame but the period stayed ~37ms. The missing ~12ms per tick is Task.Delay rounding every sub-tick request up to the Windows system-clock tick (~15.6ms default — documented: learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.delay). A frame finishing 3ms early requested 3ms and slept 15.6. Producer capped at ~27fps no matter how fast the compositor got — which is why two real render fixes read as 'zero change' in playback. Game-loop/OBS canon for this (stackoverflow.com/questions/5441464; learn.microsoft.com/en-us/windows/win32/ api/timeapi/nf-timeapi-timebeginperiod): raise the timer resolution for the session, sleep only the bulk of the remainder, and SPIN the last ~2ms across the deadline. - FramePump: timeBeginPeriod(1) on entering the pump loop, timeEndPeriod(1) in the finally; pacing = bulk _pacingDelay(ahead - 2ms) + bounded Thread.SpinWait tail; blown deadlines rebase unchanged (never burst). - Stats now report avg wait: render+submit+wait must equal the period — the accounting is closed, no stage can hide in an unmeasured gap again. - Webcam dropped its IsOpaque paste-cache bypass: it re-sampled ~156k px every tick even between identical device frames; cached paste beats the sampler on hits, costs the same on misses. 52/52 per-class green (pacing + pixel suites unchanged — output byte-stable), clean build 0 warnings. Docs same commit (ai.md slice 6, TASKS.md take-10 gate, MyMistakes #3 + renumber, HANDOFF). User's top-bar spec remains next in queue (Unit B) — re-sent many times, captured, no open questions.
This commit is contained in:
+13
-3
@@ -68,13 +68,23 @@ Both halves were solved by OBS/libyuv long ago; do not re-derive:
|
||||
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
|
||||
3. **On Windows, `Task.Delay` is a 15.6ms QUANTUM, not a timer.** Any request under one
|
||||
system-clock tick sleeps a full tick (documented — learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.delay:
|
||||
"approximately 15 milliseconds on Windows systems"). A deadline pacer built on Task.Delay caps the
|
||||
producer at ~40fps-ish EVEN IF render is instant — take 9 proved the signature: work fell 26.5→22.4ms
|
||||
but the period sat at ~37ms (≈ one padded wait/frame), so two real optimizations read as "zero change".
|
||||
Frame-accurate loops (OBS/Chromium/game-loop canon — stackoverflow.com/questions/5441464) do:
|
||||
`timeBeginPeriod(1)` for the session (paired with `timeEndPeriod`), sleep only the BULK of the
|
||||
remainder, SPIN the last ~2ms across the deadline. Diagnostic before touching the compositor again:
|
||||
period ≈ work + 15.6 → the SLEEP is the bug, not the work.
|
||||
|
||||
4. **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.
|
||||
4. **Expensive content: raster on change, never on read (take 5, 2026-09-04).** A source
|
||||
5. **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:
|
||||
@@ -83,7 +93,7 @@ Both halves were solved by OBS/libyuv long ago; do not re-derive:
|
||||
(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!).
|
||||
|
||||
5. **Prove the stage, then the fix — and re-prove after every slice (2026-09-04, takes 6-8).**
|
||||
6. **Prove the stage, then the fix — and re-prove after every slice (2026-09-04, takes 6-8).**
|
||||
The chat raster fix was REAL but the composer blamed it for the residual slowness it did not
|
||||
own; two takes burned before the render/resolve split showed `resolve ≈ 0` and pointed at the
|
||||
compositor pasting static layers per tick (`BlitCachedLayer` finished the job OBS-style). Before
|
||||
|
||||
Reference in New Issue
Block a user