perf(capture): slice 8 — buffer ring + paste-cache Epoch; gen2 visibility (take-11 spikes)
Take 11 (c10ce06c) validated the off-UI architecture: typical frames land work ~10ms + wait ~6.8ms = 16.7 exactly on the deadline; 212/300 best yet. The ENTIRE remaining gap is periodic 35-65ms render spikes that WORSENED across the take (189 -> 147) — the signature of gen2 GC pauses. Biggest churner is structural: the screen capture minted a fresh ~8.3MB byte[] per DWM frame (~500MB/s of LOH), a producer OBS never does (it owns fixed surface pools). - ScreenCaptureFrameSource: 4-deep buffer ring with size-matched slots (a <=17ms consumer cannot be lapped at 60Hz) + reused downscale row scratch. - VideoFrame.Epoch: monotonic per producer frame. The paste cache keys on array IDENTITY, so recycled arrays MUST be distinguished — epoch joins the PasteKey. Producers handing fresh arrays leave it 0 (key unchanged effect). - Stats print 'gen2 +N' per 5s window: next take acquits or convicts GC without another guess (rule: prove the stage). - Test (the ONE): PasteCache_RecycledArrayWithNewEpoch_ReRasterizes_NotStaleHits — same array, new content, bumped epoch; fails on the old key by construction. 37/37 compositor/pump, clean build. - Next suspect if gen2 stays hot: the 10Hz WebView2 capture (full-canvas PNG decode + fresh arrays on the UI thread) — recorded, untouched. Creator audio ask queued in the same working session (+40% post-mix master gain before the -1dBFS limiter) lands as its own commit next.
This commit is contained in:
@@ -19,10 +19,11 @@ public sealed class SceneCompositor
|
||||
// chat/web/image layers are identical frame after frame. Same shape as OBS (the
|
||||
// source surface is cached, the compositor blits): a scaled/round-clipped/mirrored
|
||||
// layer rasterizes once and is pasted (row-copy/blend) on every later tick.
|
||||
// Keyed by the SOURCE FRAME IDENTITY — every producer (capture, webcam, chat
|
||||
// renderer, web capture, static cache) hands out immutable byte[], so "same pixels
|
||||
// array + same target rect" ⇒ same raster. Bounds-checked eviction clears wholesale.
|
||||
private sealed record PasteKey(byte[] Pixels, int SrcW, int SrcH, int DstW, int DstH, bool Round, bool Mirror);
|
||||
// Keyed by the SOURCE FRAME IDENTITY (array + Epoch) — producers hand out fresh
|
||||
// immutable byte[], or recycled-ring arrays whose monotonic Epoch distinguishes
|
||||
// each generation (screen capture, take-11 fix), so "same array+epoch + same
|
||||
// target rect" ⇒ same raster. Bounds-checked eviction clears wholesale.
|
||||
private sealed record PasteKey(byte[] Pixels, long Epoch, int SrcW, int SrcH, int DstW, int DstH, bool Round, bool Mirror);
|
||||
private static readonly Dictionary<PasteKey, VideoFrame> _pasteCache = new();
|
||||
private static readonly object _pasteGate = new();
|
||||
private const int MaxPasteEntries = 48;
|
||||
@@ -284,7 +285,7 @@ public sealed class SceneCompositor
|
||||
VideoFrame? raster = null;
|
||||
lock (_pasteGate)
|
||||
{
|
||||
key = new PasteKey(frame.BgraPixels, frame.Width, frame.Height, dw, dh, isRound, mirror);
|
||||
key = new PasteKey(frame.BgraPixels, frame.Epoch, frame.Width, frame.Height, dw, dh, isRound, mirror);
|
||||
_pasteCache.TryGetValue(key, out raster);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user