From d638a93e04261f7d9fdf107132d5e3300b0a4642 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Thu, 25 Jun 2026 13:33:23 +0000 Subject: [PATCH] =?UTF-8?q?refactor(windows-host):=20move=20resolve=5Frend?= =?UTF-8?q?er=5Fadapter=5Fluid=20to=20a=20neutral=20module=20(audit=20?= =?UTF-8?q?=C2=A79=20/=20F1=20pt=201)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The discrete-render-GPU LUID picker was display-utility living in the SudoVDA backend; moved it verbatim to a backend-neutral crate::win_adapter module (the plan's windows/adapter.rs). The IDD-push capturer + pf-vdisplay backend now depend on it as a PEER instead of reaching into vdisplay::sudovda — the first step in breaking the circular reach-in so SudoVDA can eventually be dropped (Goal 2). sudovda re-exports it for its own callers. Remaining F1 increments: the CCD/HDR helpers (resolve_gdi_name, set_advanced_color, advanced_color_enabled, set_active_mode, isolate/restore_displays_ccd) → a neutral win_display module. Verified: host clippy (nvenc) clean on the RTX box. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/punktfunk-host/src/capture/idd_push.rs | 2 +- crates/punktfunk-host/src/main.rs | 2 + .../src/vdisplay/pf_vdisplay.rs | 5 +- crates/punktfunk-host/src/vdisplay/sudovda.rs | 57 ++-------------- crates/punktfunk-host/src/win_adapter.rs | 65 +++++++++++++++++++ 5 files changed, 75 insertions(+), 56 deletions(-) create mode 100644 crates/punktfunk-host/src/win_adapter.rs diff --git a/crates/punktfunk-host/src/capture/idd_push.rs b/crates/punktfunk-host/src/capture/idd_push.rs index 1ffcfcd..b7f1487 100644 --- a/crates/punktfunk-host/src/capture/idd_push.rs +++ b/crates/punktfunk-host/src/capture/idd_push.rs @@ -884,7 +884,7 @@ pub fn spawn_observer(target: WinCaptureTarget, preferred: Option<(u32, u32, u32 /// The discrete render GPU LUID (where NVENC runs), falling back to the monitor's `OsAdapterLuid`. fn resolve_render_adapter_luid_or(fallback_packed: i64) -> LUID { - if let Some(l) = unsafe { crate::vdisplay::sudovda::resolve_render_adapter_luid() } { + if let Some(l) = unsafe { crate::win_adapter::resolve_render_adapter_luid() } { return l; } LUID { diff --git a/crates/punktfunk-host/src/main.rs b/crates/punktfunk-host/src/main.rs index 4886019..eb1ff96 100644 --- a/crates/punktfunk-host/src/main.rs +++ b/crates/punktfunk-host/src/main.rs @@ -39,6 +39,8 @@ mod spike; mod vdisplay; #[cfg(target_os = "windows")] mod wgc_helper; +#[cfg(target_os = "windows")] +mod win_adapter; #[cfg(target_os = "linux")] mod zerocopy; diff --git a/crates/punktfunk-host/src/vdisplay/pf_vdisplay.rs b/crates/punktfunk-host/src/vdisplay/pf_vdisplay.rs index fda8ce4..02ec3d1 100644 --- a/crates/punktfunk-host/src/vdisplay/pf_vdisplay.rs +++ b/crates/punktfunk-host/src/vdisplay/pf_vdisplay.rs @@ -41,9 +41,10 @@ use super::{Mode, VirtualDisplay, VirtualOutput}; // is a real OS target id, so these operate identically). The shared MON_GEN/CURRENT_MON_GEN generation // counter is reused too, so the IDD-push stale-ring bail works regardless of which backend is active. use super::sudovda::{ - isolate_displays_ccd, resolve_gdi_name, resolve_render_adapter_luid, restore_displays_ccd, - set_active_mode, SavedConfig, CURRENT_MON_GEN, MON_GEN, + isolate_displays_ccd, resolve_gdi_name, restore_displays_ccd, set_active_mode, SavedConfig, + CURRENT_MON_GEN, MON_GEN, }; +use crate::win_adapter::resolve_render_adapter_luid; // pf-vdisplay device-interface GUID (pf_vdisplay_proto::PF_VDISPLAY_INTERFACE_GUID_U128). Deliberately // NOT SudoVDA's `{e5bcc234-…}` — we own this driver, so a private interface GUID signals it and avoids diff --git a/crates/punktfunk-host/src/vdisplay/sudovda.rs b/crates/punktfunk-host/src/vdisplay/sudovda.rs index a024b21..431c07f 100644 --- a/crates/punktfunk-host/src/vdisplay/sudovda.rs +++ b/crates/punktfunk-host/src/vdisplay/sudovda.rs @@ -135,59 +135,10 @@ unsafe fn set_render_adapter(h: HANDLE, luid: LUID) -> Result<()> { .context("SudoVDA SET_RENDER_ADAPTER") } -/// Resolve the LUID of the GPU that should RENDER the virtual display = the GPU that drives NVENC + -/// Desktop Duplication (e.g. the RTX 4090). Default: the discrete adapter with the most -/// `DedicatedVideoMemory`, skipping WARP / Basic-Render and the SudoVDA software adapter (≈0 VRAM). -/// `PUNKTFUNK_RENDER_ADAPTER=` forces a match by Description (Apollo's `adapter_name`). -/// `pub(crate)` so the IDD direct-push capturer can create its shared textures on the same discrete -/// GPU it pins here (and where NVENC runs). -pub(crate) unsafe fn resolve_render_adapter_luid() -> Option { - use windows::Win32::Graphics::Dxgi::{CreateDXGIFactory1, IDXGIFactory1}; - let want = std::env::var("PUNKTFUNK_RENDER_ADAPTER") - .ok() - .filter(|s| !s.is_empty()); - let factory: IDXGIFactory1 = CreateDXGIFactory1().ok()?; - let mut best: Option<(LUID, u64, String)> = None; - let mut i = 0u32; - while let Ok(a) = factory.EnumAdapters1(i) { - i += 1; - let Ok(d) = a.GetDesc1() else { continue }; - let name = String::from_utf16_lossy(&d.Description); - let name = name.trim_end_matches('\u{0}').to_string(); - let lname = name.to_ascii_lowercase(); - if lname.contains("basic render") || lname.contains("warp") { - continue; // never pin to the software rasterizer - } - if let Some(w) = &want { - if lname.contains(&w.to_ascii_lowercase()) { - tracing::info!( - adapter = name, - "render adapter chosen by PUNKTFUNK_RENDER_ADAPTER" - ); - return Some(d.AdapterLuid); - } - continue; - } - let vram = d.DedicatedVideoMemory as u64; // SudoVDA software adapter ≈ 0 → loses to the dGPU - if best.as_ref().is_none_or(|(_, v, _)| vram > *v) { - best = Some((d.AdapterLuid, vram, name)); - } - } - match best { - Some((luid, vram, name)) => { - tracing::info!( - adapter = name, - vram_mb = vram / (1024 * 1024), - "render adapter chosen (max VRAM)" - ); - Some(luid) - } - None => { - tracing::warn!("no suitable render adapter found for SET_RENDER_ADAPTER"); - None - } - } -} +// `resolve_render_adapter_luid` moved to the backend-neutral `crate::win_adapter` (audit §9 / Goal 2: +// it is display-utility, not SudoVDA-specific). Re-exported so this backend's own callers keep the short +// name; external callers (idd_push, pf_vdisplay) use `crate::win_adapter` directly. +pub(crate) use crate::win_adapter::resolve_render_adapter_luid; #[repr(C)] struct RemoveParams { diff --git a/crates/punktfunk-host/src/win_adapter.rs b/crates/punktfunk-host/src/win_adapter.rs new file mode 100644 index 0000000..a70f308 --- /dev/null +++ b/crates/punktfunk-host/src/win_adapter.rs @@ -0,0 +1,65 @@ +//! Backend-neutral DXGI adapter selection. +//! +//! The discrete render-GPU LUID picker used to live in the SudoVDA backend (`vdisplay::sudovda`) — a +//! historical accident, since it is display-utility, not SudoVDA-specific. It lives here so the capturers +//! (IDD-push) and the pf-vdisplay backend depend on it as a *peer* instead of reaching into the SudoVDA +//! module — breaking that circular reach-in so SudoVDA can eventually be dropped without losing this +//! helper (audit §9 / Goal 2). This is the plan's `windows/adapter.rs`. + +use windows::Win32::Foundation::LUID; + +/// Pick the discrete render GPU LUID: the adapter with the most `DedicatedVideoMemory`, skipping +/// WARP / Basic-Render and the SudoVDA software adapter (≈0 VRAM). `PUNKTFUNK_RENDER_ADAPTER=` +/// forces a match by Description (Apollo's `adapter_name`). Used by the IDD direct-push capturer (to +/// create its shared textures on the same discrete GPU it pins, where NVENC runs) and SET_RENDER_ADAPTER. +/// +/// # Safety +/// Creates + enumerates a DXGI factory; the COM calls run in the caller's apartment (the existing callers +/// already satisfy this). +pub(crate) unsafe fn resolve_render_adapter_luid() -> Option { + use windows::Win32::Graphics::Dxgi::{CreateDXGIFactory1, IDXGIFactory1}; + let want = std::env::var("PUNKTFUNK_RENDER_ADAPTER") + .ok() + .filter(|s| !s.is_empty()); + let factory: IDXGIFactory1 = CreateDXGIFactory1().ok()?; + let mut best: Option<(LUID, u64, String)> = None; + let mut i = 0u32; + while let Ok(a) = factory.EnumAdapters1(i) { + i += 1; + let Ok(d) = a.GetDesc1() else { continue }; + let name = String::from_utf16_lossy(&d.Description); + let name = name.trim_end_matches('\u{0}').to_string(); + let lname = name.to_ascii_lowercase(); + if lname.contains("basic render") || lname.contains("warp") { + continue; // never pin to the software rasterizer + } + if let Some(w) = &want { + if lname.contains(&w.to_ascii_lowercase()) { + tracing::info!( + adapter = name, + "render adapter chosen by PUNKTFUNK_RENDER_ADAPTER" + ); + return Some(d.AdapterLuid); + } + continue; + } + let vram = d.DedicatedVideoMemory as u64; // SudoVDA software adapter ≈ 0 → loses to the dGPU + if best.as_ref().is_none_or(|(_, v, _)| vram > *v) { + best = Some((d.AdapterLuid, vram, name)); + } + } + match best { + Some((luid, vram, name)) => { + tracing::info!( + adapter = name, + vram_mb = vram / (1024 * 1024), + "render adapter chosen (max VRAM)" + ); + Some(luid) + } + None => { + tracing::warn!("no suitable render adapter found for SET_RENDER_ADAPTER"); + None + } + } +}