diff --git a/Services/WebView2Manager.cs b/Services/WebView2Manager.cs index 409670f..df1569c 100644 --- a/Services/WebView2Manager.cs +++ b/Services/WebView2Manager.cs @@ -30,13 +30,13 @@ public sealed class WebView2Manager : IDisposable public bool Disposed; public bool Initialized; - // Buffer reuse (2026-09-04, same lesson as the capture/camera rings): every - // capture tick used to mint a fresh 8.3MB canvas array + a fresh crop array - // (~83MB/s of LOH at 10Hz → gen2 pauses surfaced as the pump's 35-40ms - // "worst render" spikes). Canvas scratch is reused while the size holds; - // the frame handed to the compositor rotates an 8-deep ring with a monotonic - // Epoch (identity-keyed consumers must see each generation). The preview - // WriteableBitmap is recreated only when the cropped size changes. + // Buffer reuse (2026-09-04): every capture tick used to mint a fresh + // 8.3MB canvas array + a fresh crop array (~83MB/s of LOH at 10Hz → + // gen2 pauses surfaced as the pump's 35-40ms "worst render" spikes). + // CanvasScratch is reused while the size holds. The frame handed to the + // compositor is always the FULL canvas (transparent margins reveal layers + // beneath); the crop is only for the preview WriteableBitmap. Epoch is + // monotonic per canvas generation (identity-keyed consumers see each frame). public byte[]? CanvasScratch; public readonly byte[]?[] OutRing = new byte[8][]; public int OutRingNext; @@ -192,9 +192,13 @@ public sealed class WebView2Manager : IDisposable // bounding box of non-transparent pixels (the background is injected // transparent, so the alpha channel IS the widget). This is immune to // DOM timing (fonts/images/animations), needs no JS round-trip, and - // CANNOT break rendering: if the widget fills the canvas the box equals - // the full frame (identical to the confirmed-good uncropped display); if - // it floats in transparent margin, the crop maps it flush under the box. + // CANNOT break the render: if the widget fills the canvas the box equals + // the full frame; if it floats in transparent margin the box is the + // measured content extent — used ONLY for the preview bitmap / selection. + // The frame handed to the compositor is always the FULL canvas, so the + // page's transparent margins keep revealing the layers beneath (the + // "black square over webcam" was the crop fed to the compositor: + // UniformToFill zoomed the opaque content box to cover the whole element). internal static (int X, int Y, int W, int H) FindContentBounds(byte[] pixels, int pixW, int pixH, int stride) { int minX = pixW, minY = pixH, maxX = -1, maxY = -1; @@ -266,7 +270,7 @@ public sealed class WebView2Manager : IDisposable session.PreviewBitmap = new WriteableBitmap(cropW, cropH, 96, 96, PixelFormats.Bgra32, null); session.PreviewBitmap.WritePixels(new Int32Rect(0, 0, cropW, cropH), outPixels, cropW * 4, 0); - session.LatestFrame = new VideoFrame(cropW, cropH, outPixels) { Epoch = ++session.Epoch }; + session.LatestFrame = new VideoFrame(pixW, pixH, pixels) { Epoch = ++session.Epoch }; // The preview bitmap is now MUTABLE and reused (CameraManager precedent): // WPF re-renders it after WritePixels; handlers must not Freeze it.