From c8fb4822a28c4656d76a9eae85f5eb256ca79ba7 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Tue, 16 Jun 2026 15:29:17 +0000 Subject: [PATCH] fix(host/windows): per-thread Per-Monitor-V2 DPI awareness so DuplicateOutput1 succeeds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The remaining born-lost ACCESS_LOST storm traces to ONE thing: our IDXGIOutput5::DuplicateOutput1 returns E_ACCESSDENIED (0x80070005) ~4370x, so we fall back to legacy DuplicateOutput, which yields a BORN-LOST duplication on this hybrid box. Apollo's DuplicateOutput1 SUCCEEDS on the identical desktop/output/4090-device → a working dup, clean capture. Root cause: DuplicateOutput1 REQUIRES Per-Monitor-Aware-V2. At startup our SetProcessDpiAwarenessContext(PER_MONITOR_AWARE_V2) FAILS with E_ACCESSDENIED ('already set' — a manifest/runtime locked the process to a lower awareness), and GetAwarenessFromDpiAwarenessContext reports 2 for BOTH Per-Monitor V1 and V2, so the earlier 'awareness=2' was misleading — the process is likely V1, which DuplicateOutput1 rejects with E_ACCESSDENIED. (Legacy DuplicateOutput has no V2 requirement, so it 'worked' but born-lost.) Fix: SetThreadDpiAwarenessContext(PER_MONITOR_AWARE_V2) on the capture thread in open() — a per-thread override that takes regardless of the process default, so DuplicateOutput1 can succeed (the working dup Apollo gets). Logs set_ok + thread_is_v2 (via AreDpiAwarenessContextsEqual) to confirm V2 actually applied. Topology fixes (sole display, no MODE_CHANGE) and the recovery backstops stay. Co-Authored-By: Claude Opus 4.8 --- crates/punktfunk-host/src/capture/dxgi.rs | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/crates/punktfunk-host/src/capture/dxgi.rs b/crates/punktfunk-host/src/capture/dxgi.rs index 175cec5..09435f4 100644 --- a/crates/punktfunk-host/src/capture/dxgi.rs +++ b/crates/punktfunk-host/src/capture/dxgi.rs @@ -1026,6 +1026,31 @@ impl DuplCapturer { // Stop DXGI hybrid-GPU output reparenting BEFORE we create the factory / enumerate outputs // (the cause of the 0x887A0026 ACCESS_LOST churn on this hybrid box: RTX 4090 + AMD iGPU). install_gpu_pref_hook(); + // Force PER-MONITOR-AWARE-V2 on THIS (capture) thread. IDXGIOutput5::DuplicateOutput1 + // REQUIRES V2 — without it the call returns E_ACCESSDENIED forever (the 4370x failures + // measured live), forcing the legacy DuplicateOutput fallback which yields a BORN-LOST + // duplication on this box → the ACCESS_LOST storm. SetProcessDpiAwarenessContext failed at + // startup ("already set" — a manifest/runtime locked the process to a LOWER awareness, and + // GetAwarenessFromDpiAwarenessContext can't tell V1 from V2: it reports 2 for both). The + // per-THREAD override works regardless of the process default, so DuplicateOutput1 can + // succeed (the working dup Apollo gets). Must run on the capture thread before any DXGI use. + { + use windows::Win32::UI::HiDpi::{ + AreDpiAwarenessContextsEqual, GetThreadDpiAwarenessContext, + SetThreadDpiAwarenessContext, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, + }; + let prev = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + let is_v2 = AreDpiAwarenessContextsEqual( + GetThreadDpiAwarenessContext(), + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, + ) + .as_bool(); + tracing::info!( + set_ok = !prev.0.is_null(), + thread_is_v2 = is_v2, + "capture thread DPI awareness -> PER_MONITOR_AWARE_V2 (required for DuplicateOutput1)" + ); + } // Keep the IDD (SudoVDA) virtual display awake for the capture lifetime: an idle indirect // display can be power-gated, which invalidates the duplication (a contributor to the // "freezes randomly while streaming" loss). Restored to ES_CONTINUOUS on Drop. (Apollo does