fix(client): vendor-aware Windows decode order — D3D11VA first on Intel
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 24s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 10s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 10s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 53s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 6m9s
ci / bench (push) Successful in 6m51s
apple / screenshots (push) Successful in 6m14s
flatpak / build-publish (push) Successful in 6m57s
deb / build-publish-host (push) Successful in 10m19s
docker / deploy-docs (push) Successful in 28s
deb / build-publish (push) Successful in 11m30s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 6m38s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 8m56s
arch / build-publish (push) Successful in 15m27s
android / android (push) Successful in 16m10s
ci / web (push) Successful in 58s
ci / rust (push) Successful in 20m46s
ci / docs-site (push) Successful in 1m2s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 8m0s
apple / swift (push) Successful in 1m19s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 16m15s
decky / build-publish (push) Successful in 19s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 13m58s

Intel's Windows driver advertises Vulkan Video (Arc drivers since 2023), so
the capability gate alone no longer keeps Intel off FFmpeg-Vulkan — and that
combination is broken in the field (B580 report: strobing + 7.2 ms p50
decodes at 4K120) and on glass (Arc Pro iGPU: 29-33 ms p50 decodes at 4K60
where D3D11VA does 2.5 ms on the SAME GPU). Mirror the Linux vendor order on
Windows: NVIDIA/AMD keep Vulkan Video first, Intel/unknown take D3D11VA
first; vulkan stays reachable by explicit preference and as auto's fallback.

Also:
- Vulkan→D3D11VA mid-session demotion rung (the Windows analog of Linux's
  Vulkan→VAAPI rung) — a failing Vulkan backend lands on hardware, not on
  software, which cannot survive 4K120.
- The demotion streak now needs 1 s of age as well as 3 consecutive errors:
  a startup loss burst produced 3 errors in 20 ms and stranded the session
  on software decode (one-way) before the IDR requested on the first error
  could possibly arrive — live-hit on the Intel iGPU leg.
- Stale "Intel's Windows driver has no Vulkan Video" comments corrected,
  docs updated to the per-vendor order, vendor-order test extended with
  discrete/iGPU Arc cases.

Verified on an Arc Pro iGPU + RTX 3500 Ada laptop against a CachyOS/NVENC
host: auto now picks d3d11va on Intel (60 fps, 2.5-2.8 ms decode, e2e 26 ms
vs 50 ms before) and still picks vulkan on NVIDIA. 27/27 pf-client-core
tests, clippy clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-19 14:04:32 +02:00
parent 2149673f89
commit 40030e90c8
5 changed files with 160 additions and 48 deletions
+150 -43
View File
@@ -1,9 +1,10 @@
//! Video decode: reassembled HEVC access units → frames for the presenter.
//!
//! Three backends, picked at session start (auto on Linux: vaapi → vulkan → software on
//! desktop Mesa, vulkan first on NVIDIA/VanGogh — see
//! [`VulkanDecodeDevice::prefer_vulkan_over_vaapi`];
//! override: `PUNKTFUNK_DECODER=vulkan|vaapi|software`):
//! Three backends, picked at session start (auto is vendor-ordered on BOTH desktop OSes —
//! see [`VulkanDecodeDevice::prefer_vulkan_first`]. Linux: vaapi → vulkan → software on
//! desktop Mesa, vulkan first on NVIDIA/VanGogh. Windows: d3d11va → vulkan → software on
//! Intel/unknown, vulkan first on NVIDIA/AMD.
//! Override: `PUNKTFUNK_DECODER=vulkan|vaapi|d3d11va|software`):
//!
//! * **Vulkan Video**: FFmpeg's Vulkan decoder running on the PRESENTER's own VkDevice
//! (its handles arrive via [`VulkanDecodeDevice`]) — the decoded VkImage feeds the
@@ -22,9 +23,12 @@
//! B-frames, in-band parameter sets on every IDR), so decode is strictly one-in/one-out.
//!
//! On Windows the VAAPI/dmabuf backend does not exist (DRM-PRIME is a Linux concept); the
//! chain there is Vulkan **D3D11VA** (`crate::video_d3d11` — the vendor-agnostic DXVA
//! path, which is how Intel's Windows driver gets hardware decode without Vulkan Video)
//! → software. Everything dmabuf-shaped is `cfg(target_os = "linux")`-gated inline.
//! hardware pair there is Vulkan Video and **D3D11VA** (`crate::video_d3d11` — the
//! vendor-agnostic DXVA path every Windows video player exercises), ordered per vendor:
//! Intel's driver DOES advertise Vulkan Video (Arc drivers since 2023), but FFmpeg-Vulkan
//! on it strobes and burns the frame budget (B580 field report, 2026-07) where D3D11VA
//! streams clean — so Intel/unknown take D3D11VA first and NVIDIA/AMD keep Vulkan first.
//! Everything dmabuf-shaped is `cfg(target_os = "linux")`-gated inline.
// bindgen's C-enum repr is target-dependent (u32 on Linux/clang, i32 on MSVC), so the
// pf-ffvk Vulkan flag/enum casts below are required on one platform and no-ops on the
@@ -250,16 +254,39 @@ pub struct Decoder {
/// (e.g. a reference-missing frame after packet loss) shouldn't cost the whole
/// session its hardware decoder.
vaapi_fails: u32,
/// When the current error streak started. Demotion needs the streak to be OLD as well
/// as long: one startup loss burst produces 3+ consecutive failing AUs within
/// milliseconds — demoting on count alone (live-hit: Intel iGPU, 2026-07-19, three
/// errors in 20 ms → software forever) never gives the IDR requested on the FIRST
/// error (~100300 ms round trip) a chance to rescue the hardware decoder.
first_fail: Option<std::time::Instant>,
/// Set when the decoder needs a fresh IDR to resynchronize (after an error or a demotion).
/// The pump drains it and asks the host — under the infinite GOP there is no periodic
/// keyframe, so a rebuilt/erroring decoder would otherwise stay gray/frozen forever.
want_keyframe: bool,
/// The presenter has the win32 external-memory import path, so D3D11VA frames can reach
/// the screen — kept for the mid-session Vulkan→D3D11VA demotion rung (the Windows
/// analog of Linux's Vulkan→VAAPI rung).
#[cfg(windows)]
d3d11_import: bool,
/// The presenter adapter's LUID (see [`VulkanDecodeDevice::adapter_luid`]) so a demotion
/// rebuild lands on the SAME GPU.
#[cfg(windows)]
adapter_luid: Option<[u8; 8]>,
}
/// Demote VAAPI→software only after this many consecutive hardware decode errors; a lone
/// transient error just re-requests an IDR and keeps the hardware decoder.
/// Demote a hardware backend (Vulkan→VAAPI/D3D11VA, VAAPI/D3D11VA→software) only after
/// this many consecutive decode errors; a lone transient error just re-requests an IDR
/// and keeps the hardware decoder.
const VAAPI_DEMOTE_AFTER: u32 = 3;
/// ...AND only when the streak has lasted this long. Every error re-requests an IDR, and
/// one arriving + decoding resets the streak — so a genuinely broken driver (errors keep
/// flowing through multiple IDR cycles) still demotes ~a second in, while a burst of
/// consecutive bad AUs from a single loss event no longer strands the session on
/// software before the first requested IDR could even arrive.
const HW_DEMOTE_MIN_STREAK: std::time::Duration = std::time::Duration::from_millis(1000);
/// Map a negotiated `quic` codec bit to the FFmpeg decoder id the client opens.
pub fn ffmpeg_codec_id(wire: u8) -> ffmpeg::codec::Id {
match wire {
@@ -328,10 +355,12 @@ impl Decoder {
/// Vulkan Video decoder — decode lands as VkImages the presenter samples directly.
/// Precedence: the `PUNKTFUNK_DECODER` env override wins (support/debug escape
/// hatch, and the documented knob), then the setting; both default to auto.
/// Auto's hardware order on Linux depends on the device
/// ([`VulkanDecodeDevice::prefer_vulkan_over_vaapi`]): VAAPI → Vulkan → software on
/// Auto's hardware order depends on the device on BOTH desktop OSes
/// ([`VulkanDecodeDevice::prefer_vulkan_first`]). Linux: VAAPI → Vulkan → software on
/// desktop Mesa (AMD/Intel), Vulkan → VAAPI → software on NVIDIA and the Deck's
/// VanGogh. Windows is Vulkan → D3D11VA → software (no VAAPI there).
/// VanGogh. Windows (no VAAPI there): Vulkan → D3D11VA → software on NVIDIA/AMD,
/// D3D11VA → Vulkan → software on Intel/unknown (Intel's driver advertises Vulkan
/// Video, but FFmpeg-Vulkan on it strobes/overruns the budget — B580 field report).
pub fn new(
codec_id: ffmpeg::codec::Id,
pref: &str,
@@ -343,12 +372,22 @@ impl Decoder {
.ok()
.filter(|v| !v.is_empty())
.unwrap_or_else(|| pref.to_string());
#[cfg(windows)]
let (d3d11_import, adapter_luid) = (
vk.is_some_and(|v| v.d3d11_import),
vk.and_then(|v| v.adapter_luid),
);
let done = |backend| {
Ok(Decoder {
backend,
codec_id,
vaapi_fails: 0,
first_fail: None,
want_keyframe: false,
#[cfg(windows)]
d3d11_import,
#[cfg(windows)]
adapter_luid,
})
};
// Linux `auto`: try VAAPI FIRST unless this device is one where Vulkan Video is
@@ -363,7 +402,7 @@ impl Decoder {
if matches!(choice.as_str(), "auto" | "" | "hardware")
&& !vk
.filter(|v| v.video_decode)
.is_some_and(|v| v.prefer_vulkan_over_vaapi())
.is_some_and(|v| v.prefer_vulkan_first())
{
vaapi_tried = true;
match VaapiDecoder::new(codec_id) {
@@ -376,6 +415,40 @@ impl Decoder {
}
}
}
// Windows `auto`: D3D11VA FIRST unless this device is one where Vulkan Video is
// the established right answer (NVIDIA/AMD). Intel's Windows driver advertises
// Vulkan Video (Arc drivers since 2023) so the capability gate alone no longer
// keeps Intel off FFmpeg-Vulkan — and that combination is field-broken (B580,
// 2026-07: strobing between clean anchors and corrupt inter frames that never
// trips the error-streak demotion, 7 ms p50 decodes blowing the 120 Hz budget)
// where D3D11VA — the DXVA path every Windows video player exercises, and what
// this backend was built for — streams clean. Vulkan stays reachable below by
// explicit preference and as auto's fallback when D3D11VA can't be built.
#[cfg(windows)]
let mut d3d11_tried = false;
#[cfg(windows)]
if matches!(choice.as_str(), "auto" | "" | "hardware")
&& !vk
.filter(|v| v.video_decode)
.is_some_and(|v| v.prefer_vulkan_first())
{
if let Some(v) = vk.filter(|v| v.d3d11_import) {
d3d11_tried = true;
match crate::video_d3d11::D3d11vaDecoder::new(codec_id, v.adapter_luid) {
Ok(d) => {
tracing::info!(
?codec_id,
"D3D11VA hardware decode active (shared-texture hand-off)"
);
return done(Backend::D3d11va(d));
}
Err(e) => {
tracing::info!(reason = %format!("{e:#}"),
"D3D11VA unavailable — trying Vulkan Video");
}
}
}
}
if matches!(choice.as_str(), "auto" | "" | "vulkan" | "hardware") {
// `video_decode` gates the Vulkan Video attempt: the presenter now exports its
// handle bundle even when the device has no decode queue (Windows D3D11 interop
@@ -426,11 +499,13 @@ impl Decoder {
}
}
}
// Windows: D3D11VA is the vendor-agnostic DXVA fallback when Vulkan Video isn't
// available (Intel's Windows driver foremost) — gated on the presenter having the
// win32 external-memory import path, else its frames could never reach the screen.
// Windows: D3D11VA as the fallback rung for NVIDIA/AMD auto (Vulkan Video missing
// or failed to open) and the explicit `d3d11va` preference — gated on the presenter
// having the win32 external-memory import path, else its frames could never reach
// the screen. (On Intel/unknown auto it was already tried above — `d3d11_tried`
// skips the repeat.)
#[cfg(windows)]
if choice != "software" && choice != "vulkan" {
if choice != "software" && choice != "vulkan" && !d3d11_tried {
match vk.filter(|v| v.d3d11_import) {
Some(v) => {
match crate::video_d3d11::D3d11vaDecoder::new(codec_id, v.adapter_luid) {
@@ -505,6 +580,7 @@ impl Decoder {
)?)),
codec_id: ffmpeg::codec::Id::HEVC,
vaapi_fails: 0,
first_fail: None,
want_keyframe: false,
})
}
@@ -524,6 +600,7 @@ impl Decoder {
tracing::warn!("presenter can't display hardware frames — demoting to software decode");
self.backend = Backend::Software(SoftwareDecoder::new(self.codec_id)?);
self.vaapi_fails = 0;
self.first_fail = None;
self.want_keyframe = true;
Ok(())
}
@@ -578,6 +655,7 @@ impl Decoder {
match result {
Ok(f) => {
self.vaapi_fails = 0;
self.first_fail = None;
Ok(f)
}
Err(e) => {
@@ -589,7 +667,9 @@ impl Decoder {
};
self.vaapi_fails += 1;
self.want_keyframe = true;
if self.vaapi_fails >= VAAPI_DEMOTE_AFTER {
let first = *self.first_fail.get_or_insert_with(std::time::Instant::now);
if self.vaapi_fails >= VAAPI_DEMOTE_AFTER && first.elapsed() >= HW_DEMOTE_MIN_STREAK
{
// A failing Vulkan backend still has a hardware rung below it on
// Linux — demote to VAAPI first (user-reported: FFmpeg-Vulkan-on-Mesa
// error-streaking where VAAPI streams perfectly); only when that
@@ -602,16 +682,38 @@ impl Decoder {
"Vulkan Video decode failing repeatedly — demoting to VAAPI");
self.backend = Backend::Vaapi(v);
self.vaapi_fails = 0;
self.first_fail = None;
return Ok(None);
}
Err(va) => tracing::info!(reason = %va,
"VAAPI unavailable for demotion — software decode"),
}
}
// Windows' hardware rung below Vulkan is D3D11VA (a 4K120 stream is
// not survivable on software) — same-GPU rebuild via the stashed LUID.
#[cfg(windows)]
if matches!(self.backend, Backend::Vulkan(_)) && self.d3d11_import {
match crate::video_d3d11::D3d11vaDecoder::new(
self.codec_id,
self.adapter_luid,
) {
Ok(d) => {
tracing::warn!(error = %e, fails = self.vaapi_fails,
"Vulkan Video decode failing repeatedly — demoting to D3D11VA");
self.backend = Backend::D3d11va(d);
self.vaapi_fails = 0;
self.first_fail = None;
return Ok(None);
}
Err(dx) => tracing::info!(reason = %dx,
"D3D11VA unavailable for demotion — software decode"),
}
}
tracing::warn!(error = %e, fails = self.vaapi_fails,
"{which} decode failing repeatedly — demoting to software");
self.backend = Backend::Software(SoftwareDecoder::new(self.codec_id)?);
self.vaapi_fails = 0;
self.first_fail = None;
} else {
tracing::debug!(backend = which, error = %e,
"decode error — requesting keyframe, keeping hardware decode");
@@ -718,10 +820,10 @@ pub struct VulkanDecodeDevice {
pub physical_device: usize,
pub device: usize,
/// PCI vendor of the presenter's physical device (0x10DE NVIDIA, 0x1002 AMD,
/// 0x8086 Intel) — drives [`Self::prefer_vulkan_over_vaapi`].
/// 0x8086 Intel) — drives [`Self::prefer_vulkan_first`].
pub vendor_id: u32,
/// The driver's device-name string (e.g. "AMD RADV VANGOGH") — the VanGogh/Deck
/// detection for [`Self::prefer_vulkan_over_vaapi`].
/// detection for [`Self::prefer_vulkan_first`].
pub device_name: String,
/// The presenter's graphics+present family (FFmpeg's "required" tx/comp family too).
pub graphics_qf: u32,
@@ -775,9 +877,11 @@ pub struct VulkanDecodeDevice {
}
impl VulkanDecodeDevice {
/// Should `auto` try Vulkan Video BEFORE VAAPI on this device?
/// * **NVIDIA** — Vulkan is its only hardware path (no usable VAAPI; the
/// nvidia-vaapi-driver is broken for this, Moonlight blacklists it).
/// Should `auto` try Vulkan Video BEFORE the platform's other hardware path (VAAPI on
/// Linux, D3D11VA on Windows) on this device?
/// * **NVIDIA** — Vulkan Video is the proven path (on Linux the only one: no usable
/// VAAPI — the nvidia-vaapi-driver is broken for this, Moonlight blacklists it;
/// on Windows it's the validated zero-copy default, 4K@144 with 0.1 ms decode).
/// * **AMD (RADV, VanGogh included)** — Vulkan decode outperforms VAAPI on RADV
/// (on-glass verdict), and on VanGogh VAAPI's separate-plane dmabuf import
/// additionally shows chroma fringing; the session binary opts RADV into
@@ -785,10 +889,11 @@ impl VulkanDecodeDevice {
/// because a mid-session Vulkan failure streak demotes to VAAPI (not software),
/// so a broken Mesa Vulkan path still lands on the working driver.
///
/// Intel (ANV) and unknown vendors keep the battle-tested zero-copy VAAPI first —
/// ANV's Vulkan Video is the least-proven Mesa path and VAAPI is what every other
/// Linux client uses there.
pub fn prefer_vulkan_over_vaapi(&self) -> bool {
/// Intel and unknown vendors take the battle-tested path first: VAAPI on Linux (ANV's
/// Vulkan Video is the least-proven Mesa path), D3D11VA on Windows — Intel's Windows
/// driver advertises Vulkan Video (Arc drivers since 2023), but FFmpeg-Vulkan on it is
/// field-broken (B580, 2026-07: strobing + ~7 ms decodes) where DXVA streams clean.
pub fn prefer_vulkan_first(&self) -> bool {
const VENDOR_NVIDIA: u32 = 0x10DE;
const VENDOR_AMD: u32 = 0x1002;
self.vendor_id == VENDOR_NVIDIA || self.vendor_id == VENDOR_AMD
@@ -850,25 +955,27 @@ mod tests {
}
}
/// Auto's Linux hardware order: Vulkan-first on NVIDIA (no usable VAAPI) and ALL AMD
/// (Vulkan decode outperforms VAAPI on RADV — on-glass verdict; VanGogh additionally
/// chroma-fringes over VAAPI); Intel/unknown keep VAAPI first (ANV's Vulkan Video is
/// the least-proven Mesa path). A Vulkan failure streak still demotes to VAAPI, so
/// Vulkan-first can never strand a box on software decode.
/// Auto's hardware order (both OSes): Vulkan-first on NVIDIA (on Linux: no usable
/// VAAPI) and ALL AMD (Vulkan decode outperforms VAAPI on RADV — on-glass verdict;
/// VanGogh additionally chroma-fringes over VAAPI); Intel/unknown take the proven
/// path first — VAAPI on Linux (ANV's Vulkan Video is the least-proven Mesa path),
/// D3D11VA on Windows (Intel's driver advertises Vulkan Video since 2023, but
/// FFmpeg-Vulkan on it strobes — B580 field report). A Vulkan failure streak still
/// demotes to hardware (VAAPI/D3D11VA), so Vulkan-first can never strand a box on
/// software decode.
#[test]
fn vulkan_over_vaapi_on_nvidia_and_amd() {
assert!(decode_device(0x10DE, "NVIDIA GeForce RTX 5070 Ti").prefer_vulkan_over_vaapi());
assert!(decode_device(0x1002, "AMD RADV VANGOGH").prefer_vulkan_over_vaapi());
fn vulkan_first_on_nvidia_and_amd_only() {
assert!(decode_device(0x10DE, "NVIDIA GeForce RTX 5070 Ti").prefer_vulkan_first());
assert!(decode_device(0x1002, "AMD RADV VANGOGH").prefer_vulkan_first());
assert!(decode_device(0x1002, "AMD Custom GPU 0405 (RADV VANGOGH)").prefer_vulkan_first());
assert!(decode_device(0x1002, "AMD Radeon RX 7800 XT (RADV NAVI32)").prefer_vulkan_first());
assert!(
decode_device(0x1002, "AMD Custom GPU 0405 (RADV VANGOGH)").prefer_vulkan_over_vaapi()
);
assert!(
decode_device(0x1002, "AMD Radeon RX 7800 XT (RADV NAVI32)").prefer_vulkan_over_vaapi()
);
assert!(
!decode_device(0x8086, "Intel(R) Arc(tm) A770 Graphics (DG2)")
.prefer_vulkan_over_vaapi()
!decode_device(0x8086, "Intel(R) Arc(tm) A770 Graphics (DG2)").prefer_vulkan_first()
);
// The Windows-side motivation: discrete Arc advertises Vulkan Video and must
// still land on D3D11VA in auto.
assert!(!decode_device(0x8086, "Intel(R) Arc(TM) B580 Graphics").prefer_vulkan_first());
assert!(!decode_device(0x8086, "Intel(R) Arc(TM) Pro Graphics").prefer_vulkan_first());
}
/// Lock the DRM FourCC magic numbers against typos — these are the exact values
+5 -2
View File
@@ -1,6 +1,9 @@
//! D3D11VA hardware decode (Windows) for the Vulkan presenter — the vendor-agnostic DXVA
//! path that covers what Vulkan Video can't (Intel's Windows driver foremost, which has no
//! video-decode queue and previously landed on CPU decode).
//! path, and auto's FIRST choice on Intel/unknown vendors. Intel's Windows driver DOES
//! advertise Vulkan Video (Arc drivers since 2023 — don't trust the capability gate to
//! keep Intel off it), but FFmpeg-Vulkan on it is field-broken (B580, 2026-07: strobing +
//! ~7 ms decodes) where this path streams clean; on NVIDIA/AMD it is the fallback rung
//! below Vulkan Video, in `auto` and via mid-session demotion.
//!
//! Ported from the retired in-process WinUI presenter's decoder (`clients/windows/src/video.rs`)
//! with one structural change: that presenter sampled D3D11 textures directly, while ours draws
+3 -1
View File
@@ -75,7 +75,9 @@ The app is in **Google Play Internal Testing** — request a tester invite on ou
`punktfunk-client` for Windows (`clients/windows`) is the native graphical client for Windows — pure
Rust, the same `punktfunk/1` core as the Apple, Linux, and Android apps, with a **WinUI 3** UI (host
list, settings, PIN pairing); the stream itself runs in punktfunk's Vulkan presenter. Its decoder
tries **Vulkan Video, then D3D11VA, then software**, with 10-bit/HDR present, WASAPI audio + mic,
order is per-vendor: **Vulkan Video, then D3D11VA, then software** on NVIDIA and AMD, and
**D3D11VA first** on Intel and other GPUs (Intel's driver advertises Vulkan Video, but DXVA is the
proven path there), with 10-bit/HDR present, WASAPI audio + mic,
SDL3 controllers (rumble, lightbar, DualSense), network discovery, and the full PIN-pairing trust
surface. It builds for both `x86_64` and `aarch64` and ships as a **signed MSIX**. Launch it and
pick a host from the list, just like the other native apps.
+1 -1
View File
@@ -161,7 +161,7 @@ A few knobs are read by the native **clients**, not the host:
| Setting | Values | Meaning |
|---|---|---|
| `PUNKTFUNK_DECODER` | `software` · `vaapi` · `vulkan` (Linux) · `d3d11va` (Windows) | Force the decode path. Default auto-selects hardware (VAAPI on Intel/AMD, Vulkan Video on NVIDIA and the Steam Deck, D3D11VA/Vulkan on Windows) with a software fallback. |
| `PUNKTFUNK_DECODER` | `software` · `vaapi` · `vulkan` (Linux) · `d3d11va` (Windows) | Force the decode path. Default auto-selects hardware per vendor (Linux: VAAPI on Intel/AMD, Vulkan Video on NVIDIA and the Steam Deck; Windows: Vulkan Video on NVIDIA/AMD, D3D11VA on Intel and others) with a software fallback. |
## Bitrate
+1 -1
View File
@@ -60,7 +60,7 @@ host is newer than the Linux host.)
|---|---|
| **macOS / iOS / iPadOS / tvOS** | VideoToolbox HEVC + AV1 decode (AV1 on hardware that decodes it — M3-class Macs, A17 Pro-class iPhones), GameController capture, full DualSense feedback, mDNS discovery, PIN pairing + TOFU, network speed test, latency HUD. Stage-2 presenter (`VTDecompressionSession``CAMetalLayer`, ~11 ms p50 capture→present) is validated on glass and is the default (stage 1 remains the fallback when Metal is unavailable). Ships as one universal TestFlight build / App Store listing. |
| **Linux** (`punktfunk-client`) | relm4/GTK4 shell; streams run in the spawned Vulkan session presenter. Hardware decode via Vulkan Video (all vendors, incl. NVIDIA) with VAAPI dmabuf and software fallbacks, PipeWire audio + mic, SDL3 gamepads incl. DualSense, mDNS discovery, PIN pairing + TOFU, speed test, Skia console UI + tiered stats overlay. Ships as Flatpak, apt, rpm, and Arch packages. |
| **Windows** (`punktfunk-client`) | WinUI 3 shell; streams run in the Vulkan session presenter with a Vulkan Video → D3D11VA → software decode chain, HDR10, WASAPI audio + mic, SDL3 gamepads incl. DualSense, mDNS discovery, and the full PIN/TOFU trust surface. Ships as a signed MSIX (x86_64 + ARM64). **Hardware decode validated on NVIDIA and Intel; HDR present pending on-glass validation.** |
| **Windows** (`punktfunk-client`) | WinUI 3 shell; streams run in the Vulkan session presenter with a per-vendor decode chain — Vulkan Video → D3D11VA → software on NVIDIA/AMD, D3D11VA → Vulkan Video → software on Intel and others — plus HDR10, WASAPI audio + mic, SDL3 gamepads incl. DualSense, mDNS discovery, and the full PIN/TOFU trust surface. Ships as a signed MSIX (x86_64 + ARM64). **Hardware decode validated on NVIDIA and Intel; HDR present pending on-glass validation.** |
| **Android** (phone + Android TV) | Kotlin app with a Rust core over JNI. NDK `AMediaCodec` hardware HEVC decode + HDR10 (Main10/BT.2020 PQ), Opus/Oboe audio + mic, gamepad input with rumble/HID feedback, mDNS discovery, PIN pairing + TOFU (Keystore identity), tiered stats overlay (three-finger tap), custom resolutions, and D-pad/controller focus navigation for TV. Ships to the Google Play Internal Testing track. |
`punktfunk-probe` is a headless reference and measurement client (for testing and