Files
punktfunk/clients/windows/src/gpu.rs
T
enricobuehlerandClaude Fable 5 35c61fee64 chore(client): the windows-rs pin moves a month forward, onto the SDK-metadata bindings
The July 2026 windows-rs brings a reconciler keyed-child-order fix (#4728), widget
validation (#4727), a DPI collision fix (#4751), icon elements (#4736), multi-window
support (#4730) and scroll virtualization (#4710) — the re-render fixes the Windows
client has been working around at the architecture level. All three pinned deps
(windows-reactor, windows, windows-reactor-setup) move together so windows-core
stays unified across the swap-chain hand-off, and pf-client-core moves with them.

The bulk of the diff is #4689: windows/windows-sys now generate straight from the
Windows SDK, so the `Win32_*` namespace features became one feature per SDK header
(winuser, dxgi, d3d11, …), the PascalCase namespace modules became header-named
modules, struct-returning COM methods take explicit out-params and return HRESULT,
Win32 functions return their raw BOOL/HANDLE instead of Result, and flag constants
are plain integers. Both crates' Win32 code is rewritten to that shape; behaviour
is unchanged on every path.

Riding along, all already stale before the bump: the README and the three Windows
workflows stop claiming windows-reactor's build.rs needs CARGO_WORKSPACE_DIR (that
build.rs no longer exists — staging moved to windows-reactor-setup via OUT_DIR);
the README layout section stops describing modules that moved into the session
binary long ago and gains the manual smoke checklist; the notices generator learns
the SPDX for crates that ship license files without a `license` field, which turns
windows-reactor-setup's UNKNOWN into MIT OR Apache-2.0; and the crate records its
real rust-version (1.96) instead of inheriting the workspace's 1.82.

Verified: cargo check/clippy/fmt clean on punktfunk-client-windows, pf-client-core
and punktfunk-client-session; both bins build; --discover finds the LAN hosts; the
GUI shell comes up (WinAppSDK bootstrap intact under the new reactor-setup).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 18:31:47 +02:00

94 lines
4.2 KiB
Rust

//! DXGI adapter enumeration for the Settings "GPU" picker.
//!
//! Streaming (decode + present) runs in the spawned `punktfunk-session` binary; the shell only
//! needs the list of real (hardware) adapters to offer on a multi-GPU box (a hybrid laptop or an
//! eGPU). The picked adapter description is persisted (`crate::trust::Settings::adapter`) and read
//! by the session child at connect (`PUNKTFUNK_ADAPTER` remains the session binary's env override).
use windows::core::Interface;
use windows::Win32::dxgi::{CreateDXGIFactory1, IDXGIAdapter, IDXGIFactory1};
/// The adapter's human-readable description.
fn adapter_name(adapter: &IDXGIAdapter) -> String {
// SAFETY: a read-only COM call on the live `adapter` borrow, filling a zeroed local
// descriptor through the out-param, checked before the descriptor is read; `&IDXGIAdapter`
// is a reference-counted wrapper, so the borrow IS the liveness.
unsafe {
let mut d: windows::Win32::dxgi::DXGI_ADAPTER_DESC = std::mem::zeroed();
if adapter.GetDesc(&mut d).is_ok() {
String::from_utf16_lossy(&d.Description)
.trim_end_matches('\0')
.to_string()
} else {
"<unknown adapter>".into()
}
}
}
/// Every DXGI adapter, in enumeration order.
fn all_adapters() -> Vec<IDXGIAdapter> {
// SAFETY: DXGI factory creation takes no pointer and returns an owned factory or an error,
// matched on below before anything uses it.
let factory: IDXGIFactory1 = match unsafe { CreateDXGIFactory1() } {
Ok(f) => f,
Err(_) => return Vec::new(),
};
let mut v = Vec::new();
let mut i = 0u32;
// SAFETY: a COM call on the live factory above; it takes an index and yields an owned adapter,
// and the `Ok` pattern is what proves one came back.
while let Ok(a) = unsafe { factory.EnumAdapters1(i) } {
i += 1;
if let Ok(a) = a.cast::<IDXGIAdapter>() {
v.push(a);
}
}
v
}
/// Descriptions of the real (hardware, non-WARP) GPUs — the Settings GPU picker's option list.
/// The picker only shows when this has more than one entry.
///
/// **Deduplicated by description**, because the description IS the identity everywhere
/// downstream: the pick is persisted as that string (`Settings::adapter`) and matched by
/// name in the session binary (`PUNKTFUNK_VK_ADAPTER`). So two entries with the same name
/// are one selectable choice however many times DXGI enumerates them — listing it twice
/// only offers the user a meaningless coin flip. Seen live on an Intel Arc laptop
/// (2026-07-19), whose Vulkan ICD likewise enumerates the one physical iGPU twice.
pub fn adapter_names() -> Vec<String> {
const DXGI_ADAPTER_FLAG_SOFTWARE: u32 = 2; // dxgi.h; not in this windows-rs feature set
let mut names: Vec<String> = Vec::new();
for a in all_adapters() {
let desc1 = a
.cast::<windows::Win32::dxgi::IDXGIAdapter1>()
.ok()
// SAFETY: a read-only COM call on the adapter just cast, filling a zeroed local
// descriptor through the out-param, discarded unless the call reports success.
.and_then(|a1| unsafe {
let mut d: windows::Win32::dxgi::DXGI_ADAPTER_DESC1 = std::mem::zeroed();
a1.GetDesc1(&mut d).is_ok().then_some(d)
});
let name = adapter_name(&a);
// Forensics for the next duplicate/oddity report — which adapters DXGI actually
// returned, and whether the repeats share a LUID (one adapter enumerated twice)
// or are distinct devices that merely present the same description.
if let Some(d) = &desc1 {
tracing::debug!(
name = %name,
luid = format!("{:08x}-{:08x}", d.AdapterLuid.HighPart, d.AdapterLuid.LowPart),
vendor = format_args!("{:#06x}", d.VendorId),
device = format_args!("{:#06x}", d.DeviceId),
flags = d.Flags,
"DXGI adapter"
);
}
if desc1.is_some_and(|d| d.Flags & DXGI_ADAPTER_FLAG_SOFTWARE != 0) {
continue; // WARP / software renderer — never a streaming target
}
if !names.contains(&name) {
names.push(name);
}
}
names
}