perf(compositor): reuse per-element rasters in-place — take-16 GC-churn fix
Slice 10 from today-slices (d1126dd): the paste cache minted a fresh raster array per new source frame (webcam ~30 keys/s ≈ 18MB/s LOH churn → gen2 pauses that ate camera frames). Fix: _elementRasters — re-rasterize INTO the element's existing array when geometry holds, so steady-state allocation ≈ 0. The paste cache keys on (array+epoch) still protect correctness; _elementRasters[element] tracks the element's current raster so stale keys can never paste a mid-update array. MaxPasteEntries 48→256 (was a leak guard, not an allocation stream). Regression test: PasteCache_RingRecycledSources_ReRasterInPlace_WithoutAllocationChurn. Build 0 warnings, 289 tests.
This commit is contained in:
@@ -25,8 +25,9 @@ public sealed class SceneCompositor
|
||||
// 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 Dictionary<SceneElement, VideoFrame> _elementRasters = new(ReferenceEqualityComparer.Instance);
|
||||
private static readonly object _pasteGate = new();
|
||||
private const int MaxPasteEntries = 48;
|
||||
private const int MaxPasteEntries = 256;
|
||||
|
||||
/// <summary>
|
||||
/// Composite <paramref name="scene"/> into the tier's output frame. Layer order (back →
|
||||
@@ -294,14 +295,26 @@ public sealed class SceneCompositor
|
||||
// Rasterize in ELEMENT space on a TRANSPARENT base: the sampler writes 255
|
||||
// alpha only where it actually draws, so untouched margins (outside the
|
||||
// round clip, past the UniformToFill edges) stay alpha 0 and the paste
|
||||
// reveals the master beneath instead of black.
|
||||
var pixels = new byte[dw * dh * 4];
|
||||
BlitContentRaw(pixels, dw, dh, 0, 0, ew, eh, frame, 1f, isRound, mirror);
|
||||
raster = new VideoFrame(dw, dh, pixels);
|
||||
// reveals the master beneath instead of black. The ARRAY is reused per
|
||||
// element (see _elementRasters note) — new source generations re-raster
|
||||
// in place, so the steady state allocates nothing.
|
||||
lock (_pasteGate)
|
||||
{
|
||||
if (_elementRasters.TryGetValue(element, out var existing)
|
||||
&& existing.Width == dw && existing.Height == dh)
|
||||
raster = existing;
|
||||
}
|
||||
if (raster == null) raster = new VideoFrame(dw, dh, new byte[dw * dh * 4]);
|
||||
Array.Clear(raster.BgraPixels);
|
||||
BlitContentRaw(raster.BgraPixels, dw, dh, 0, 0, ew, eh, frame, 1f, isRound, mirror);
|
||||
lock (_pasteGate)
|
||||
{
|
||||
// Only the SOURCE frame's identity is a real key owner; drop any older
|
||||
// entry that shared this element's reused raster so a stale key can never
|
||||
// paste a mid-update array.
|
||||
if (_pasteCache.Count >= MaxPasteEntries) _pasteCache.Clear();
|
||||
_pasteCache[key!] = raster;
|
||||
_elementRasters[element] = raster;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user