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>
94 lines
4.2 KiB
Rust
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
|
|
}
|