feat: web sources frame-captured via composition (CoreWebView2CompositionController → Windows.Graphics.Capture); PNG poll + CaptureScheduler deleted

The ~30Hz CapturePreviewAsync PNG poll capped real cadence at ~20Hz
(35–165ms full-HD encode+decode), so a 60fps widget still juddered at ~1/6
speed. Replaced polling with frame-driven capture of the composition
controller's root visual — the mechanism WebView2CompositionControl and
Flutter's webview_windows use (graphics_context.cc captures the root
surface_ visual via CreateGraphicsCaptureItemFromVisual; reference:
github.com/microsoft/Windows.UI.Composition.WinUI / flutter-internal
webview_windows). Frames now arrive at the renderer's own pace; capture
memory is epoch'd ring reuse + one crop-sized shared WriteableBitmap.

New Services/WebCaptureFrameSource.cs owns GraphicsCaptureItem + free-
threaded Direct3D11CaptureFramePool + session (Straight alpha readback,
per-frame FindContentBounds → CropBounds). WebView2Manager reworked around
per-session composition controllers + one UI-thread Compositor created via
the CoreMessaging CreateDispatcherQueueController P/Invoke (the 19041
projection lacks CreateOnCurrentThread); internal seam ctor
(Dispatcher, Func<string,IScreenCaptureSource>?) for hermetic tests.
CaptureScheduler.cs deleted; the three SetCaptureInterval cadence hooks
removed; InitWebView2() moved from MainWindow ctor to Loaded (a parent
HWND must exist for the composition controller); the hidden WebViewHostPanel
overlay deleted. TransparentBackgroundScript unchanged.

Tests: WebView2ManagerTests reworked — 4 control-size + scheduler tests
dropped, FindContentBounds tests moved to WebCaptureFrameSource, ONE
integration test (Frames_PublishCroppedPreview_And_CoalesceToLatest_CarryingCropBounds)
drives the seam with a FakeWebSource + background-STA DispatcherPump.
Suite 293/293, 0 warnings.

NOTE: composition path NOT yet verified on a device — the take is the
next step. Web work committed locally only (no push per standing rule).

Docs same-commit: ai.md Slice 14 + supersede marker on Slice 11, HANDOFF,
MyMistakes (CoreMessaging DQ + namespace-landmine recipe), TASK 17,
Controls/ViewModels/Services indexes.
This commit is contained in:
2026-09-14 16:14:00 -07:00
parent 40c453805d
commit c01206fb8a
16 changed files with 890 additions and 581 deletions
+5 -4
View File
@@ -84,10 +84,11 @@ public sealed class RoundClipInteractionTests
// of PreviewGrid); otherwise Window_PreviewMouseLeftButtonDown
// deselects the source and the handle can never be grabbed.
// UIElement.InputHitTest IS the input pipeline — what Mouse uses.
// Do NOT use VisualTreeHelper.HitTest here: it returned the window's
// non-hit-testable WebViewHostPanel overlay for EVERY point (it does
// not model IsHitTestVisible input semantics) — the bug this test
// "found" for months was this wrong API (root-caused 2026-09-01).
// Do NOT use VisualTreeHelper.HitTest here: it ignores IsHitTestVisible
// input semantics (the old invisible WebView2 host overlay for web
// sources used to win every visual hit — the wrong-API false bug this
// test "found" for months, root-caused 2026-09-01; the overlay is gone
// with the composition-based web render but the API lesson stays).
var hit = window.InputHitTest(cornerInWindow) as DependencyObject;
var grabbable = hit != null && IsDescendantOf(hit, previewGrid);