Revert "fix(web): composite consumes the FULL canvas, alpha crop is measure-only — transparent web margins reveal the webcam again"
This reverts commit 0f441d825a.
This commit is contained in:
+20
-44
@@ -33,36 +33,16 @@ public sealed class WebView2Manager : IDisposable
|
||||
// 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). The full-canvas slots rotate an 8-deep ring with a
|
||||
// monotonic Epoch (identity-keyed consumers — the compositor's paste cache —
|
||||
// must see each generation); the crop slots feed the preview WriteableBitmap,
|
||||
// recreated only when the cropped size changes.
|
||||
public readonly byte[]?[] CanvasRing = new byte[8][];
|
||||
public int CanvasRingNext;
|
||||
public long Epoch;
|
||||
// "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.
|
||||
public byte[]? CanvasScratch;
|
||||
public readonly byte[]?[] OutRing = new byte[8][];
|
||||
public int OutRingNext;
|
||||
public long Epoch;
|
||||
public WriteableBitmap? PreviewBitmap;
|
||||
|
||||
public byte[] RentCanvasBuffer(int size)
|
||||
{
|
||||
for (var tries = 0; tries < CanvasRing.Length; tries++)
|
||||
{
|
||||
var idx = (CanvasRingNext + tries) % CanvasRing.Length;
|
||||
var buf = CanvasRing[idx];
|
||||
if (buf != null && buf.Length == size)
|
||||
{
|
||||
CanvasRingNext = (idx + 1) % CanvasRing.Length;
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
var slot = CanvasRingNext;
|
||||
CanvasRingNext = (slot + 1) % CanvasRing.Length;
|
||||
var fresh = new byte[size];
|
||||
CanvasRing[slot] = fresh;
|
||||
return fresh;
|
||||
}
|
||||
|
||||
public byte[] RentOutBuffer(int size)
|
||||
{
|
||||
for (var tries = 0; tries < OutRing.Length; tries++)
|
||||
@@ -96,10 +76,11 @@ public sealed class WebView2Manager : IDisposable
|
||||
// OBS model: the page renders at the MASTER CANVAS size (1920×1080),
|
||||
// stable, never tracked. Full-bleed widgets (designed for the canvas)
|
||||
// render fully instead of being clipped to a small box-sized viewport;
|
||||
// dragging the box never reflows the page. The alpha bounding box
|
||||
// (FindContentBounds) measures the widget's extent for the UI/selection;
|
||||
// the COMPOSITE consumes the FULL canvas — the element rect is a viewport
|
||||
// onto the page, whose transparent margins reveal the layers beneath.
|
||||
// dragging the box never reflows the page. The capture is tightly
|
||||
// cropped to the widget's ALPHA bounding box (FindContentBounds) and
|
||||
// the display (Stretch=Fill) maps it flush under the box, anchored at
|
||||
// (0,0) — no DOM query, so no timing fragility; worst case is the
|
||||
// full frame, which is the confirmed-good rendering.
|
||||
var webView = new WebView2
|
||||
{
|
||||
Width = 1920,
|
||||
@@ -209,15 +190,11 @@ public sealed class WebView2Manager : IDisposable
|
||||
|
||||
// The widget's extent is measured from the captured frame itself: the
|
||||
// bounding box of non-transparent pixels (the background is injected
|
||||
// transparent, so the alpha channel IS the widget). Immune to DOM timing
|
||||
// (fonts/images/animations), needs no JS round-trip, and 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 take-19-2/take-20 "black square
|
||||
// over the webcam" was the CROP fed to the compositor: UniformToFill then
|
||||
// zoomed the opaque content box to cover the whole element rect).
|
||||
// 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.
|
||||
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;
|
||||
@@ -268,7 +245,9 @@ public sealed class WebView2Manager : IDisposable
|
||||
if (pixW < 1 || pixH < 1) return;
|
||||
|
||||
var need = pixW * pixH * 4;
|
||||
var pixels = session.RentCanvasBuffer(need);
|
||||
var pixels = session.CanvasScratch is { } scratch && scratch.Length >= need
|
||||
? scratch
|
||||
: (session.CanvasScratch = new byte[need]);
|
||||
formatted.CopyPixels(pixels, pixW * 4, 0);
|
||||
|
||||
var stride = pixW * 4;
|
||||
@@ -287,10 +266,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);
|
||||
|
||||
// The compositor consumes the FULL canvas (alpha intact): the element rect
|
||||
// scales the page region onto itself and the transparent margins reveal what
|
||||
// is beneath. The alpha crop above exists only for the preview/selection.
|
||||
session.LatestFrame = new VideoFrame(pixW, pixH, pixels) { Epoch = ++session.Epoch };
|
||||
session.LatestFrame = new VideoFrame(cropW, cropH, outPixels) { Epoch = ++session.Epoch };
|
||||
|
||||
// The preview bitmap is now MUTABLE and reused (CameraManager precedent):
|
||||
// WPF re-renders it after WritePixels; handlers must not Freeze it.
|
||||
|
||||
Reference in New Issue
Block a user