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
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:
@@ -687,6 +687,88 @@ fn alloc_pitched_nv12(
|
||||
Ok(((y_ptr, y_pitch), (uv_ptr, uv_pitch)))
|
||||
}
|
||||
|
||||
/// Allocate ONE pitched buffer holding a *contiguous* NV12 surface — Y rows `[0, H)` immediately
|
||||
/// followed by interleaved-chroma rows `[H, 3H/2)`, all at the driver's single pitch. Unlike
|
||||
/// [`alloc_pitched_nv12`] (two separate allocations, the capture/IPC layout) this is the layout the
|
||||
/// direct-SDK NVENC encoder registers as a single `CUDADEVICEPTR` input: NVENC reads the UV plane
|
||||
/// at `ptr + pitch*height`. Used only by [`InputSurface`] (encode side), never the wire.
|
||||
fn alloc_pitched_nv12_contiguous(width: u32, height: u32) -> Result<(CUdeviceptr, usize)> {
|
||||
let mut ptr: CUdeviceptr = 0;
|
||||
let mut pitch: usize = 0;
|
||||
// Y is `width` bytes/row × H rows; the interleaved chroma plane is W/2 samples × 2 bytes =
|
||||
// `width` bytes/row × H/2 rows. One allocation of `H + H/2` rows keeps them contiguous under a
|
||||
// single pitch so NVENC finds UV at `ptr + pitch*H`.
|
||||
let rows = height as usize + (height as usize / 2).max(1);
|
||||
// SAFETY: `cuMemAllocPitch_v2` (wrapper → live table) writes the allocation pointer and pitch
|
||||
// into the two live, distinct stack out-params `&mut ptr`/`&mut pitch`, which outlive the
|
||||
// synchronous call; width/rows/element-size are by-value ints. No aliasing.
|
||||
unsafe {
|
||||
ck(
|
||||
cuMemAllocPitch_v2(&mut ptr, &mut pitch, width as usize, rows, 16),
|
||||
"cuMemAllocPitch_v2(NV12 contiguous)",
|
||||
)?;
|
||||
}
|
||||
Ok((ptr, pitch))
|
||||
}
|
||||
|
||||
/// An encoder-owned, contiguous pitched CUDA surface that the direct-SDK NVENC Linux backend
|
||||
/// (`encode/linux/nvenc_cuda.rs`, design/linux-direct-nvenc.md) registers **once** as a
|
||||
/// `NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR` input and copies each captured frame into (via the
|
||||
/// `copy_*_to_device` helpers) before `encode_picture`. Distinct from [`DeviceBuffer`]: these are
|
||||
/// laid out exactly as NVENC's single-pointer register expects — NV12 = Y then interleaved-UV under
|
||||
/// one pitch, YUV444 = Y|U|V stacked, RGB = packed 4-byte — and are never pooled or sent on the
|
||||
/// wire. Frees its allocation on drop (context made current first, since drop may run off-thread).
|
||||
pub struct InputSurface {
|
||||
/// Base device pointer NVENC registers. For NV12 the chroma plane lives at `ptr + pitch*height`;
|
||||
/// for YUV444 the U/V planes at `ptr + pitch*height` / `ptr + 2*pitch*height`.
|
||||
pub ptr: CUdeviceptr,
|
||||
/// Row stride in bytes (the driver's pitch), shared by every plane of the surface.
|
||||
pub pitch: usize,
|
||||
/// Luma height in rows — the plane stride multiplier NVENC / the copy helpers key off.
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
impl InputSurface {
|
||||
/// Contiguous NV12 (8-bit 4:2:0): one allocation, Y then interleaved UV under one pitch.
|
||||
pub fn alloc_nv12(width: u32, height: u32) -> Result<InputSurface> {
|
||||
let (ptr, pitch) = alloc_pitched_nv12_contiguous(width, height)?;
|
||||
Ok(InputSurface { ptr, pitch, height })
|
||||
}
|
||||
|
||||
/// Planar YUV444 (8-bit 4:4:4): one allocation, Y|U|V full-res planes stacked (see
|
||||
/// [`alloc_pitched_yuv444`]).
|
||||
pub fn alloc_yuv444(width: u32, height: u32) -> Result<InputSurface> {
|
||||
let (ptr, pitch) = alloc_pitched_yuv444(width, height)?;
|
||||
Ok(InputSurface { ptr, pitch, height })
|
||||
}
|
||||
|
||||
/// Packed 4-byte RGB/BGRx: one contiguous pitched allocation (NVENC does the internal CSC when
|
||||
/// registered as an `ABGR`/`ARGB` input).
|
||||
pub fn alloc_rgb(width: u32, height: u32) -> Result<InputSurface> {
|
||||
let (ptr, pitch) = alloc_pitched(width, height)?;
|
||||
Ok(InputSurface { ptr, pitch, height })
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for InputSurface {
|
||||
fn drop(&mut self) {
|
||||
if self.ptr == 0 {
|
||||
return;
|
||||
}
|
||||
// SAFETY: this surface exclusively owns `self.ptr` (a single `cuMemAllocPitch_v2` allocation
|
||||
// from one of the constructors above), freed exactly once here — `drop` runs once and the
|
||||
// `ptr == 0` guard skips a moved-out/empty surface, so no double-free. The shared context is
|
||||
// made current first because drop may run on a thread where it isn't, and `cuMemFree_v2`
|
||||
// needs it. Wrapper → live table; result ignored (best-effort teardown).
|
||||
unsafe {
|
||||
if let Some(c) = CONTEXT.get() {
|
||||
let _ = cuCtxSetCurrent(c.0);
|
||||
}
|
||||
let _ = cuMemFree_v2(self.ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Free-list of recycled device allocations for one resolution. Shared (via `Arc`) between the
|
||||
/// capture thread that hands out buffers and the encode thread where a [`DeviceBuffer`] drops and
|
||||
/// returns its allocation here. Bulk-freed when the last reference drops. For NV12 each free entry
|
||||
|
||||
Reference in New Issue
Block a user