feat(clients/windows): port the Vulkan session client to Windows — session-always
The punktfunk-session Vulkan client (clients/linux-session, now clients/session) builds and runs on Windows; the WinUI shell spawns it for every stream. Verified live: 10-bit HEVC via Vulkan Video on both AMD (iGPU) and NVIDIA, 5120x1440 at 130 fps / 8 ms end-to-end on the RTX 4090. - pf-ffvk: Windows bindgen branch (FFMPEG_DIR + PF_FFVK_VULKAN_INCLUDE, no pkg-config); provisioning fetches Vulkan-Headers (pinned v1.4.309). - pf-client-core: builds on Windows — WASAPI audio (audio_wasapi.rs, cfg-swapped via #[path], same surface as the PipeWire twin), VAAPI/dmabuf gated inline (chain = vulkan -> software), trust reads the WinUI shell's %APPDATA% stores (parity tests pin both serialized shapes), Settings gains adapter/hdr_enabled (serde-defaulted; Linux stores unaffected). - pf-presenter: builds on Windows — dmabuf module Linux-gated; SDL keyboard grab while captured (Alt+Tab/Win reach the host); pick_device ranks discrete over integrated (device 0 was the iGPU on hybrid boxes — the silent footgun) and honors PUNKTFUNK_VK_ADAPTER (the Settings GPU pick, exported by the session). - run loop: block in one SDL wait woken by input AND decoded frames (a per- session forwarder pushes a FrameWake user event) instead of a 1 ms poll — measured 111%% -> 5%% of a core (NVIDIA), 86%% -> 3.5%% (AMD), stats unchanged. The pump's decode-fence wait became once-per-window sampling (no per-frame pipeline stall; the stat now shows true backlog). - pf-console-ui: builds on Windows (skia-safe msvc prebuilts); font lookup falls through fontconfig aliases to concrete DirectWrite families (Consolas/Segoe UI) — browse/coverflow works, verified against a live host. - WinUI shell: session-always via new src/spawn.rs (GTK spawn.rs port — CREATE_NO_WINDOW, stdout contract, kill handle); the Stream screen is a status card (chips + stage lines from the child's stats). The legacy in-process D3D11VA path stays behind Settings "Streaming engine" / PUNKTFUNK_BUILTIN_ STREAM=1 as the A/B baseline until Phase 8 deletes it. SessionParams.video_caps makes the HDR toggle real. - clients/linux-session renamed to clients/session (builds for both OSes). - CI/MSIX: both workflows build/test both bins with widened path filters; the MSIX ships punktfunk-session.exe. ARM64 session builds --no-default-features (rust-skia has no aarch64-pc-windows-msvc prebuilts; flip when it does). A/B on this box (5120x1440 HEVC vs home-worker-5): NVIDIA Vulkan 130 fps / 8 ms e2e / 1.6 ms decode — clearly better than the built-in path. The AMD iGPU VCN saturates at ~52 fps where its own D3D11VA does ~70 — Adrenalin Vulkan decode is slower on APU silicon; discrete RDNA validation gates Phase 8. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ repository.workspace = true
|
||||
# FF_API_VULKAN_* deprecation gates that change AVVulkanDeviceContext's layout between
|
||||
# FFmpeg builds. This is deliberately not hand-transcribed.
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
[target.'cfg(any(target_os = "linux", windows))'.dependencies]
|
||||
ash = { version = "0.38", features = ["loaded"] }
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
+81
-28
@@ -7,41 +7,33 @@
|
||||
//! libavutil/version.h, so bindgen over the installed header is the only ABI-safe
|
||||
//! source of truth — hand transcription would silently skew on the next FFmpeg bump.
|
||||
//!
|
||||
//! Non-Linux targets get an empty file: the workspace builds on macOS (clients/apple is
|
||||
//! the client there), and this shim is Linux-client plumbing only.
|
||||
//! Header discovery is per-OS: Linux asks pkg-config; Windows reuses the FFMPEG_DIR
|
||||
//! tree ffmpeg-sys-next links against (BtbN trees ship no .pc files) plus an explicit
|
||||
//! Vulkan-Headers include dir, since Windows has no system <vulkan/vulkan.h>. Other
|
||||
//! targets get an empty file: the workspace builds on macOS (clients/apple is the
|
||||
//! client there).
|
||||
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=wrapper.h");
|
||||
println!("cargo:rerun-if-env-changed=PF_FFVK_VULKAN_INCLUDE");
|
||||
let out = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs");
|
||||
|
||||
if env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("linux") {
|
||||
std::fs::write(&out, "// pf-ffvk: Linux-only, empty on this target\n").unwrap();
|
||||
return;
|
||||
}
|
||||
|
||||
// Include paths from pkg-config (libavutil for the hwcontext header; the Vulkan
|
||||
// headers usually live in /usr/include, but honor a registered vulkan.pc too).
|
||||
// PF_FFVK_VULKAN_INCLUDE prepends an explicit Vulkan-Headers include dir — for
|
||||
// cross builds and boxes without the system package.
|
||||
println!("cargo:rerun-if-env-changed=PF_FFVK_VULKAN_INCLUDE");
|
||||
let mut includes: Vec<PathBuf> = Vec::new();
|
||||
if let Ok(dir) = env::var("PF_FFVK_VULKAN_INCLUDE") {
|
||||
includes.push(PathBuf::from(dir));
|
||||
}
|
||||
let avutil = pkg_config::Config::new()
|
||||
.cargo_metadata(false)
|
||||
.probe("libavutil")
|
||||
.expect("pkg-config: libavutil not found — install the FFmpeg dev package");
|
||||
includes.extend(avutil.include_paths);
|
||||
if let Ok(vk) = pkg_config::Config::new()
|
||||
.cargo_metadata(false)
|
||||
.probe("vulkan")
|
||||
{
|
||||
includes.extend(vk.include_paths);
|
||||
}
|
||||
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
|
||||
let includes = match target_os.as_str() {
|
||||
"linux" => linux_includes(),
|
||||
"windows" => windows_includes(),
|
||||
_ => {
|
||||
std::fs::write(
|
||||
&out,
|
||||
"// pf-ffvk: Linux/Windows-only, empty on this target\n",
|
||||
)
|
||||
.unwrap();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut builder = bindgen::Builder::default()
|
||||
.header("wrapper.h")
|
||||
@@ -81,6 +73,67 @@ fn main() {
|
||||
|
||||
// The av_vk_* symbols live in libavutil, which ffmpeg-sys-next already links into
|
||||
// every consumer of this crate; no extra link flags needed. Emitting the lib anyway
|
||||
// keeps `cargo test -p pf-ffvk` linking standalone.
|
||||
// keeps `cargo test -p pf-ffvk` linking standalone — which on Windows also needs the
|
||||
// import-lib search path (there is no system linker path for FFmpeg there).
|
||||
if target_os == "windows" {
|
||||
// windows_includes() already required FFMPEG_DIR.
|
||||
let ff = PathBuf::from(env::var("FFMPEG_DIR").unwrap());
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}",
|
||||
ff.join("lib").display()
|
||||
);
|
||||
}
|
||||
println!("cargo:rustc-link-lib=avutil");
|
||||
}
|
||||
|
||||
/// Include paths from pkg-config (libavutil for the hwcontext header; the Vulkan
|
||||
/// headers usually live in /usr/include, but honor a registered vulkan.pc too).
|
||||
/// PF_FFVK_VULKAN_INCLUDE prepends an explicit Vulkan-Headers include dir — for
|
||||
/// cross builds and boxes without the system package.
|
||||
fn linux_includes() -> Vec<PathBuf> {
|
||||
let mut includes: Vec<PathBuf> = Vec::new();
|
||||
if let Ok(dir) = env::var("PF_FFVK_VULKAN_INCLUDE") {
|
||||
includes.push(PathBuf::from(dir));
|
||||
}
|
||||
let avutil = pkg_config::Config::new()
|
||||
.cargo_metadata(false)
|
||||
.probe("libavutil")
|
||||
.expect("pkg-config: libavutil not found — install the FFmpeg dev package");
|
||||
includes.extend(avutil.include_paths);
|
||||
if let Ok(vk) = pkg_config::Config::new()
|
||||
.cargo_metadata(false)
|
||||
.probe("vulkan")
|
||||
{
|
||||
includes.extend(vk.include_paths);
|
||||
}
|
||||
includes
|
||||
}
|
||||
|
||||
/// No pkg-config on Windows: headers come from the FFMPEG_DIR tree (the same BtbN
|
||||
/// lgpl-shared tree ffmpeg-sys-next links against) plus an explicit Vulkan-Headers
|
||||
/// dir — PF_FFVK_VULKAN_INCLUDE (provision-windows-punktfunk-extras.ps1 stages
|
||||
/// C:\Users\Public\vulkan-headers) or an installed Vulkan SDK. Only headers are
|
||||
/// needed at build time; the loader (vulkan-1.dll) is a GPU-driver component and is
|
||||
/// never linked here.
|
||||
fn windows_includes() -> Vec<PathBuf> {
|
||||
println!("cargo:rerun-if-env-changed=FFMPEG_DIR");
|
||||
println!("cargo:rerun-if-env-changed=VULKAN_SDK");
|
||||
let mut includes: Vec<PathBuf> = Vec::new();
|
||||
if let Ok(dir) = env::var("PF_FFVK_VULKAN_INCLUDE") {
|
||||
includes.push(PathBuf::from(dir));
|
||||
} else if let Ok(sdk) = env::var("VULKAN_SDK") {
|
||||
includes.push(PathBuf::from(sdk).join("Include"));
|
||||
} else {
|
||||
panic!(
|
||||
"pf-ffvk: no Vulkan headers — set PF_FFVK_VULKAN_INCLUDE to a Vulkan-Headers \
|
||||
include dir (scripts/ci/provision-windows-punktfunk-extras.ps1 stages \
|
||||
C:\\Users\\Public\\vulkan-headers\\include) or install the Vulkan SDK (VULKAN_SDK)"
|
||||
);
|
||||
}
|
||||
let ff = env::var("FFMPEG_DIR").expect(
|
||||
"pf-ffvk: FFMPEG_DIR not set — point it at the FFmpeg tree \
|
||||
(scripts/ci/provision-windows-punktfunk-extras.ps1 stages C:\\Users\\Public\\ffmpeg)",
|
||||
);
|
||||
includes.push(PathBuf::from(ff).join("include"));
|
||||
includes
|
||||
}
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
#![allow(deref_nullptr)]
|
||||
#![allow(unnecessary_transmutes)]
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(any(target_os = "linux", windows))]
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
|
||||
/// Conversions between the generated vulkan.h handle types and ash's.
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(any(target_os = "linux", windows))]
|
||||
pub mod ashx {
|
||||
use super::*;
|
||||
use ash::vk::Handle as _;
|
||||
@@ -33,6 +33,9 @@ pub mod ashx {
|
||||
ash::vk::Semaphore::from_raw(h as u64)
|
||||
}
|
||||
|
||||
// bindgen's enum repr is target-dependent: u32 on Linux (clang default), i32 on
|
||||
// MSVC — so the cast is required on one target and a same-type no-op on the other.
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
pub fn image_layout(l: VkImageLayout) -> ash::vk::ImageLayout {
|
||||
ash::vk::ImageLayout::from_raw(l as i32)
|
||||
}
|
||||
@@ -64,7 +67,7 @@ pub mod ashx {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, target_os = "linux"))]
|
||||
#[cfg(all(test, any(target_os = "linux", windows)))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user