From e00284777731abc154dd1f9376a9ea08c8d7bd0c Mon Sep 17 00:00:00 2001 From: gramps Date: Sun, 6 Sep 2026 12:56:34 -0700 Subject: [PATCH] =?UTF-8?q?fix(web):=20feed=20full=20canvas=20to=20composi?= =?UTF-8?q?tor=20=E2=80=94=20transparent=20margins=20reveal=20layers=20ben?= =?UTF-8?q?eath=20(take-21)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Services/WebView2Manager.cs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) 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.