diff --git a/Services/Compositor/SceneCompositor.cs b/Services/Compositor/SceneCompositor.cs index 711ec53..06f6dab 100644 --- a/Services/Compositor/SceneCompositor.cs +++ b/Services/Compositor/SceneCompositor.cs @@ -19,11 +19,13 @@ 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 (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); + // Keyed by the SOURCE FRAME IDENTITY (array + Epoch + CropBounds) — producers hand + // out fresh immutable byte[], or recycled-ring arrays whose monotonic Epoch distinguishes + // each generation (screen capture, take-11 fix). CropBounds must be in the key because + // the same frame (same Pixels/Epoch/Width/Height) can have different CropBounds as + // the element is resized — a stale cached raster with wrong crop pixels would corrupt + // the output. 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, (int X, int Y, int W, int H)? CropBounds); private static readonly Dictionary _pasteCache = new(); private static readonly Dictionary _elementRasters = new(ReferenceEqualityComparer.Instance); private static readonly object _pasteGate = new(); @@ -286,7 +288,7 @@ public sealed class SceneCompositor VideoFrame? raster = null; lock (_pasteGate) { - key = new PasteKey(frame.BgraPixels, frame.Epoch, frame.Width, frame.Height, dw, dh, isRound, mirror); + key = new PasteKey(frame.BgraPixels, frame.Epoch, frame.Width, frame.Height, dw, dh, isRound, mirror, frame.CropBounds); _pasteCache.TryGetValue(key, out raster); }