Files
punktfunk/crates/pf-ffvk/build.rs
T
enricobuehler d6647b9183 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>
2026-07-08 23:21:36 +02:00

140 lines
6.4 KiB
Rust

//! Generate bindings for `libavutil/hwcontext_vulkan.h` against the SYSTEM headers.
//!
//! ffmpeg-sys-next binds a curated header list that omits every hwcontext_*.h; the
//! Vulkan hwcontext structs (`AVVulkanDeviceContext`, `AVVkFrame`) are what let us run
//! FFmpeg's Vulkan Video decoder on the presenter's own VkDevice and read the decoded
//! VkImages back. Their layout depends on compile-time FF_API_* deprecation gates in
//! 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.
//!
//! 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");
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")
// The whole point of this crate: the Vulkan hwcontext surface…
.allowlist_type("AVVulkan.*")
.allowlist_type("AVVkFrame.*")
.allowlist_function("av_vk_frame_alloc")
.allowlist_function("av_vkfmt_from_pixfmt")
// The feature structs chained into AVVulkanDeviceContext.device_features (plain
// vulkan.h types; generating them here keeps the chain in one type system).
.allowlist_type("VkPhysicalDeviceVulkan11Features")
.allowlist_type("VkPhysicalDeviceVulkan12Features")
.allowlist_type("VkPhysicalDeviceVulkan13Features")
// AVVulkanFramesContext.img_flags values (plane views need MUTABLE_FORMAT).
.allowlist_type("VkImageCreateFlagBits")
// Timeline-semaphore wait — the pump measures true GPU decode completion.
.allowlist_type("VkSemaphoreWaitInfo")
.allowlist_type("PFN_vkWaitSemaphores")
.allowlist_type("PFN_vkGetDeviceProcAddr")
// …plus nothing else of FFmpeg: the core types these structs reference only
// ever appear behind pointers here, so keep them opaque instead of duplicating
// ffmpeg-sys-next's definitions (callers cast pointers between the crates).
.opaque_type("AVHWDeviceContext")
.opaque_type("AVHWFramesContext")
.opaque_type("AVBufferRef")
.opaque_type("AVFrame")
.derive_debug(false)
.layout_tests(true);
for dir in &includes {
builder = builder.clang_arg(format!("-I{}", dir.display()));
}
let bindings = builder.generate().expect(
"bindgen over libavutil/hwcontext_vulkan.h failed — is `vulkan-headers` installed? \
(the header includes <vulkan/vulkan.h>)",
);
bindings.write_to_file(&out).unwrap();
// 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 — 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
}