forked from unom/punktfunk
The rest of the client-gaps handoff, plus what a parity sweep surfaced: * Every whole-file settings save rebases on the file first — the GTK dialog close and its two speed-test apply arms, the Windows per-control commit, the console screen. The file has five writers and no merge (profiles.rs documents the debt); saving a shell-lifetime snapshot silently reverted whatever another writer stored meanwhile, most visibly the spawner-persisted match-window size (item 4). * Windows honors the Speaker/Microphone picks: `audio_wasapi` grows endpoint enumeration (on its own MTA thread — UI threads are STA) and resolves PUNKTFUNK_AUDIO_SINK/SOURCE as endpoint ids, falling back to the default when the picked device is gone; the settings page gets the two rows (defaults scope, "(not detected)" like the GPU row). `speaker_device`/`mic_device` stop being Linux-only fields (item 5). * A punktfunk:// link with `launch=` opened a plain desktop session on Windows — the id was parsed, validated, planned, and dropped at the last hop. It now rides `initiate_launch` (plus a waking variant for the dial-first path). Linux always forwarded it. * The console UI offered "VAAPI" on Windows — a dead option there that also hid d3d11va, the actual Windows hardware path. The decoder list is per-OS now. * The Windows gamepad picker learns "Steam Deck" (the GTK picker had it; the host-side pad has always existed). * Doc fixes: gpu.rs named a nonexistent env var (PUNKTFUNK_ADAPTER → PUNKTFUNK_VK_ADAPTER), and session/Cargo.toml claimed video decode is Linux-only while explaining what the ARM64 leg drops. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
95 lines
4.3 KiB
Rust
95 lines
4.3 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_VK_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
|
|
}
|