fix(host): filter IddCx ghost adapters out of the Windows GPU inventory

pf-vdisplay's IddCx adapter enumerates as a perfect DXGI twin of the GPU it
renders on — same Description, VendorId/DeviceId/SubSysId, even
DedicatedVideoMemory; only the LUID differs (verified on the RTX box: the
twins were byte-identical in DXGI). Every host with the driver installed
therefore listed each render GPU twice: the console picker offered a dead
twin (display-only — D3D11 device creation on it fails), and auto-select's
max-VRAM tie between the twins was settled by enumeration order — the real
GPU won by luck, not construction.

The kernel adapter type is the only field that separates the twins, so
enumerate() now queries KMTQAITYPE_ADAPTERTYPE per adapter (raw gdi32 FFI —
the windows crate's Wdk bindings aren't enabled, and one 3-call query
doesn't justify them) and drops indirect-display, display-only, and software
adapters. Fail-open: a failed query keeps the adapter — better a listed twin
than a hidden real GPU. Incidentally also filters other vendors' virtual
displays (Parsec-VDD class) from user machines' pickers.

Existing manual preferences heal via find_preferred's same-model fallback
when occurrence indices shift — a ghost accidentally pinned as -0 now
resolves to the real GPU. The regression test pins the adapter-type words
captured from real hardware; a Windows-gated smoke test runs enumerate()
against the live machine (on the RTX box it actively exercises ghost
exclusion: the inventory went from twin 4090s to exactly one).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 20:42:42 +02:00
parent 5d9f1ccb6d
commit c19c87c435
2 changed files with 157 additions and 6 deletions
@@ -8,8 +8,9 @@
//!
//! The selection logic itself now lives in [`crate::gpu`] (shared with the mgmt API's GPU
//! endpoints): **operator preference (web console) > `PUNKTFUNK_RENDER_ADAPTER` substring > max
//! `DedicatedVideoMemory`**, WARP/Basic-Render always excluded. This wrapper is the LUID-shaped
//! view of it, plus the per-decision logging (call sites are per-session, never per-frame).
//! `DedicatedVideoMemory`**, WARP/Basic-Render and indirect-display ghost twins always excluded.
//! This wrapper is the LUID-shaped view of it, plus the per-decision logging (call sites are
//! per-session, never per-frame).
use windows::Win32::Foundation::LUID;