1d587a259e
ci / web (push) Successful in 47s
ci / docs-site (push) Successful in 1m8s
apple / swift (push) Successful in 1m18s
decky / build-publish (push) Successful in 23s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 13s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 6m49s
ci / bench (push) Successful in 7m2s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 12s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 5m4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 11s
apple / screenshots (push) Successful in 6m12s
deb / build-publish (push) Successful in 9m49s
android / android (push) Successful in 13m29s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 6m53s
docker / deploy-docs (push) Successful in 27s
flatpak / build-publish (push) Successful in 6m32s
deb / build-publish-host (push) Successful in 13m26s
arch / build-publish (push) Successful in 17m13s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 8m0s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 14m10s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 21m2s
ci / rust (push) Successful in 28m51s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 9m0s
The decode surface is DXVA-aligned (height rounded up to the profile's
alignment, 128 for HEVC/AV1), so it is taller than the frame: a 2400-line
stream decodes into a 2432-line texture. VideoProcessorBlt was called with
no stream source rect, so it blitted the WHOLE surface — the uninitialized
padding rows (NV12 Y=0,U=V=0, which converts to vivid green) landed at the
bottom of the output and the picture was squashed to fit. Set the source
rect to the real frame.
Pre-existing on this backend, but 40030e90 made D3D11VA auto's first choice
on Intel, so it went from a corner case to what every Intel user sees — a
~32 px green bar at 3840x2400, reported on glass within the hour.
Also dedupe the Settings GPU picker by description: that string IS the
identity downstream (persisted as Settings::adapter, matched by name as
PUNKTFUNK_VK_ADAPTER), so repeated enumerations of one adapter offered the
user the same choice twice — live on an Intel Arc laptop, whose Vulkan ICD
also enumerates the single physical iGPU twice. Both paths now log their
raw enumeration (decode texture dims; DXGI name/LUID/ids) for the next
report.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
82 lines
3.4 KiB
Rust
82 lines
3.4 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::Graphics::Dxgi::{CreateDXGIFactory1, IDXGIAdapter, IDXGIFactory1};
|
|
|
|
/// The adapter's human-readable description.
|
|
fn adapter_name(adapter: &IDXGIAdapter) -> String {
|
|
unsafe {
|
|
adapter
|
|
.GetDesc()
|
|
.map(|d| {
|
|
String::from_utf16_lossy(&d.Description)
|
|
.trim_end_matches('\0')
|
|
.to_string()
|
|
})
|
|
.unwrap_or_else(|_| "<unknown adapter>".into())
|
|
}
|
|
}
|
|
|
|
/// Every DXGI adapter, in enumeration order.
|
|
fn all_adapters() -> Vec<IDXGIAdapter> {
|
|
let factory: IDXGIFactory1 = match unsafe { CreateDXGIFactory1() } {
|
|
Ok(f) => f,
|
|
Err(_) => return Vec::new(),
|
|
};
|
|
let mut v = Vec::new();
|
|
let mut i = 0u32;
|
|
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::Graphics::Dxgi::IDXGIAdapter1>()
|
|
.and_then(|a1| unsafe { a1.GetDesc1() })
|
|
.ok();
|
|
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
|
|
}
|