feat(encode): direct-SDK NVENC on Linux (CUDA input) with real RFI
ci / rust (push) Failing after 1m1s
ci / web (push) Successful in 1m3s
ci / docs-site (push) Successful in 1m9s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
decky / build-publish (push) Successful in 17s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 8s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
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 57s
apple / swift (push) Successful in 4m42s
ci / bench (push) Successful in 7m37s
docker / deploy-docs (push) Successful in 22s
flatpak / build-publish (push) Successful in 6m51s
windows-host / package (push) Successful in 14m26s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 12m59s
arch / build-publish (push) Successful in 16m48s
deb / build-publish (push) Successful in 17m14s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m49s
android / android (push) Successful in 18m24s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 18m28s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m36s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m1s
release / apple (push) Successful in 22m56s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m40s
apple / screenshots (push) Successful in 18m53s

Phase 5.2 of design/encoder-recovery-hardening.md (design/linux-direct-nvenc.md).
The Linux NVIDIA host encodes through libavcodec `hevc_nvenc`, which structurally
cannot express `nvEncInvalidateRefFrames` — so every FEC-unrecoverable loss is a
full IDR and, since the client freezes-until-reanchor, a per-loss freeze for
RTT+IDR-encode. This ports the Windows raw-NVENC backend to
NV_ENC_DEVICE_TYPE_CUDA over the shared CUcontext so Linux NVIDIA gets the same
real RFI + F2 recovery-anchor + reset() stall lever + HDR-SEI/Main10 plumbing.

New `encode/linux/nvenc_cuda.rs` (`NvencCudaEncoder`):
- runtime-loaded entry table via `dlopen libnvidia-encode.so.1` (never link-time,
  mirroring the zerocopy::cuda libcuda loader) — one binary still starts on
  AMD/Intel Linux boxes and falls through to VAAPI/software;
- session on the shared CUcontext (zerocopy::cuda::context());
- an encoder-owned ring of registered CUDADEVICEPTR input surfaces
  (zerocopy::cuda::InputSurface + a contiguous-NV12 allocator), each captured
  DeviceBuffer device→device copied in via the existing copy_* helpers — mirrors
  the libav recycled-hwframe-pool copy, so zero regression vs today;
- config/RFI/anchor/reset ported from the Windows backend; sync-only (NVENC async
  is Windows-only, so that whole subsystem is dropped);
- Main10/HDR-SEI wired but inert until a Linux P010 capture path (Phase 5.1).

Wired behind PUNKTFUNK_NVENC_DIRECT (default OFF) in open_nvenc_probed; the Windows
path is untouched (no shared extraction in v1). Two on-hardware `#[ignore]` smokes
added.

Validated on .21 (RTX 5070 Ti, driver 610.43.03): builds on Linux under ci-check,
clippy-clean, full host suite 272/0, NV12 smoke (8 AUs, real invalidate_ref_frames
+ recovery_anchor on a P-frame) and YUV444 FREXT smoke (6 AUs, chroma_444) green;
Windows compile unaffected. Owed: the client-in-the-loop matrix (RFI-survives-ABR,
reset() heal, A/B vs libav) and the default flip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-12 11:18:21 +02:00
parent fdda7144ed
commit 93093f3cf9
4 changed files with 1395 additions and 0 deletions
+32
View File
@@ -757,6 +757,20 @@ fn open_nvenc_probed(
bit_depth: u8,
chroma: ChromaFormat,
) -> Result<Box<dyn Encoder>> {
// Direct-SDK NVENC (design/linux-direct-nvenc.md): opt-in via PUNKTFUNK_NVENC_DIRECT, and only
// for a CUDA capture payload (it registers CUDADEVICEPTR inputs — a CPU/dmabuf frame can't feed
// it, so those keep the libav path). It self-clamps the bitrate internally (its own level-ceiling
// binary search at session open), so it skips the probe-loop stepping below.
#[cfg(feature = "nvenc")]
if cuda && nvenc_direct_enabled() {
tracing::info!(
codec = codec.nvenc_name(),
"Linux direct-SDK NVENC enabled (PUNKTFUNK_NVENC_DIRECT) — real RFI + recovery anchor"
);
return Ok(Box::new(nvenc_cuda::NvencCudaEncoder::open(
codec, format, width, height, fps, bitrate_bps, cuda, bit_depth, chroma,
)?) as Box<dyn Encoder>);
}
const MIN_PROBE_BPS: u64 = 50_000_000;
let mut candidates = vec![bitrate_bps];
let cap = codec.max_bitrate_bps();
@@ -794,6 +808,17 @@ fn open_nvenc_probed(
Err(last.unwrap_or_else(|| anyhow::anyhow!("encoder open failed at every probed bitrate")))
}
/// Whether the operator opted into the direct-SDK NVENC path (`PUNKTFUNK_NVENC_DIRECT` truthy).
/// OFF by default until the on-glass matrix (design/linux-direct-nvenc.md §9) is green; then the
/// default flips and this becomes the libav escape hatch (`=0`). Only meaningful with `--features
/// nvenc`.
#[cfg(all(target_os = "linux", feature = "nvenc"))]
fn nvenc_direct_enabled() -> bool {
std::env::var("PUNKTFUNK_NVENC_DIRECT")
.map(|v| matches!(v.trim(), "1" | "true" | "yes" | "on"))
.unwrap_or(false)
}
/// Cheap, side-effect-free NVIDIA-presence probe for the `auto` backend selector: the NVIDIA
/// kernel driver exposes these device nodes, AMD/Intel boxes have neither. Deliberately does NOT
/// create a CUDA context (that would allocate GPU state on every host that merely *might* be
@@ -1131,6 +1156,13 @@ mod amf;
mod ffmpeg_win;
#[cfg(target_os = "linux")]
mod linux;
// Direct-SDK NVENC on Linux (CUDA input; design/linux-direct-nvenc.md) — real RFI + recovery anchor
// + reset() lever the libavcodec `linux::NvencEncoder` can't express. Opt-in behind
// `PUNKTFUNK_NVENC_DIRECT` until on-glass validated; the `.so` resolves at runtime like the Windows
// path, so `--features nvenc` stays safe on a driver-less/AMD Linux box.
#[cfg(all(target_os = "linux", feature = "nvenc"))]
#[path = "encode/linux/nvenc_cuda.rs"]
mod nvenc_cuda;
#[cfg(all(target_os = "windows", feature = "nvenc"))]
#[path = "encode/windows/nvenc.rs"]
mod nvenc;