style(encode): rustfmt the direct-SDK NVENC Linux backend
ci / web (push) Successful in 49s
ci / docs-site (push) Successful in 52s
decky / build-publish (push) Successful in 17s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 7s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 6s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 7s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 6s
ci / bench (push) Successful in 6m1s
docker / deploy-docs (push) Successful in 20s
arch / build-publish (push) Successful in 12m23s
android / android (push) Successful in 13m26s
deb / build-publish (push) Successful in 14m27s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 15m22s
ci / rust (push) Successful in 18m25s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m14s
windows-host / package (push) Successful in 14m15s
apple / swift (push) Successful in 4m49s
apple / screenshots (push) Successful in 20m11s

`cargo fmt` was not run on the Phase 5.2 additions (nvenc_cuda.rs + the encode.rs
dispatcher fork), failing the ci.yml rust fmt job. Whitespace/wrapping only — no
behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-12 11:35:07 +02:00
parent 93093f3cf9
commit ad532b08a0
2 changed files with 64 additions and 27 deletions
+12 -4
View File
@@ -768,7 +768,15 @@ fn open_nvenc_probed(
"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,
codec,
format,
width,
height,
fps,
bitrate_bps,
cuda,
bit_depth,
chroma,
)?) as Box<dyn Encoder>);
}
const MIN_PROBE_BPS: u64 = 50_000_000;
@@ -1160,12 +1168,12 @@ mod linux;
// + 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;
#[cfg(all(target_os = "linux", feature = "nvenc"))]
#[path = "encode/linux/nvenc_cuda.rs"]
mod nvenc_cuda;
// Software (openh264) H.264 encoder — the GPU-less path on BOTH Windows and Linux (a headless /
// GPU-less test box, or a fallback when no hardware encoder is available). Platform-agnostic: it
// consumes CPU RGB `CapturedFrame`s and the statically-bundled openh264 build.
@@ -148,9 +148,11 @@ fn load_api() -> std::result::Result<EncodeApi, String> {
let lib = libloading::Library::new("libnvidia-encode.so.1")
.or_else(|_| libloading::Library::new("libnvidia-encode.so"))
.map_err(|e| format!("libnvidia-encode.so.1 not loadable (no NVIDIA driver?): {e}"))?;
let get_version: libloading::Symbol<unsafe extern "C" fn(*mut u32) -> nv::NVENCSTATUS> = lib
.get(b"NvEncodeAPIGetMaxSupportedVersion\0")
.map_err(|e| format!("libnvidia-encode exports no NvEncodeAPIGetMaxSupportedVersion: {e}"))?;
let get_version: libloading::Symbol<unsafe extern "C" fn(*mut u32) -> nv::NVENCSTATUS> =
lib.get(b"NvEncodeAPIGetMaxSupportedVersion\0")
.map_err(|e| {
format!("libnvidia-encode exports no NvEncodeAPIGetMaxSupportedVersion: {e}")
})?;
let create_instance: libloading::Symbol<
unsafe extern "C" fn(*mut nv::NV_ENCODE_API_FUNCTION_LIST) -> nv::NVENCSTATUS,
> = lib
@@ -421,7 +423,9 @@ impl NvencCudaEncoder {
let mut enc: *mut c_void = ptr::null_mut();
(api().open_encode_session_ex)(&mut params, &mut enc)
.nv_ok()
.map_err(|e| anyhow!("NVENC open_encode_session_ex (caps probe): {e:?} (no NVIDIA GPU?)"))?;
.map_err(|e| {
anyhow!("NVENC open_encode_session_ex (caps probe): {e:?} (no NVIDIA GPU?)")
})?;
let wmax = self.get_cap(enc, nv::NV_ENC_CAPS::NV_ENC_CAPS_WIDTH_MAX);
let hmax = self.get_cap(enc, nv::NV_ENC_CAPS::NV_ENC_CAPS_HEIGHT_MAX);
let yuv444 = self.get_cap(enc, nv::NV_ENC_CAPS::NV_ENC_CAPS_SUPPORT_YUV444_ENCODE);
@@ -463,11 +467,7 @@ impl NvencCudaEncoder {
/// Open + configure + initialize ONE NVENC CUDA session at `bitrate` (bps) and `split_mode`.
/// Returns the session handle, or destroys it and returns the error.
unsafe fn try_open_session(
&self,
bitrate: u64,
split_mode: u32,
) -> Result<*mut c_void> {
unsafe fn try_open_session(&self, bitrate: u64, split_mode: u32) -> Result<*mut c_void> {
let mut params = nv::NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS {
version: nv::NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER,
deviceType: nv::NV_ENC_DEVICE_TYPE::NV_ENC_DEVICE_TYPE_CUDA,
@@ -529,8 +529,10 @@ impl NvencCudaEncoder {
// Chroma + bit depth. 4:4:4 (HEVC Range Extensions, chromaFormatIDC=3) engages on a YUV444
// input; 10-bit Main10 is ported but never engages on Linux yet (input is 8-bit).
let yuv444_input =
matches!(self.buffer_fmt, nv::NV_ENC_BUFFER_FORMAT::NV_ENC_BUFFER_FORMAT_YUV444);
let yuv444_input = matches!(
self.buffer_fmt,
nv::NV_ENC_BUFFER_FORMAT::NV_ENC_BUFFER_FORMAT_YUV444
);
if self.chroma_444 && yuv444_input {
cfg.profileGUID = nv::NV_ENC_HEVC_PROFILE_FREXT_GUID;
cfg.encodeCodecConfig.hevcConfig.set_chromaFormatIDC(3);
@@ -545,7 +547,9 @@ impl NvencCudaEncoder {
}
Codec::Av1 => {
cfg.encodeCodecConfig.av1Config.set_pixelBitDepthMinus8(2);
cfg.encodeCodecConfig.av1Config.set_inputPixelBitDepthMinus8(0);
cfg.encodeCodecConfig
.av1Config
.set_inputPixelBitDepthMinus8(0);
}
Codec::H264 => {}
}
@@ -665,7 +669,8 @@ impl NvencCudaEncoder {
// 2-way NVENC split-frame encoding (Ada dual-NVENC) above ~1 Gpix/s; env override
// PUNKTFUNK_SPLIT_ENCODE = 0/disable | 1/auto | 2 | 3. HEVC/AV1 only.
let pixel_rate = self.width as u64 * self.height as u64 * self.fps.max(1) as u64;
let mut split_mode: u32 = match std::env::var("PUNKTFUNK_SPLIT_ENCODE").ok().as_deref() {
let mut split_mode: u32 = match std::env::var("PUNKTFUNK_SPLIT_ENCODE").ok().as_deref()
{
Some("0") | Some("disable") => {
nv::NV_ENC_SPLIT_ENCODE_MODE::NV_ENC_SPLIT_DISABLE_MODE as u32
}
@@ -691,7 +696,9 @@ impl NvencCudaEncoder {
if probe.is_err() && split_on {
let no_split = nv::NV_ENC_SPLIT_ENCODE_MODE::NV_ENC_SPLIT_DISABLE_MODE as u32;
if let Ok(e) = self.try_open_session(requested_bps, no_split) {
tracing::warn!("NVENC (Linux): split-encode rejected by codec/config — disabled");
tracing::warn!(
"NVENC (Linux): split-encode rejected by codec/config — disabled"
);
split_mode = no_split;
probe = Ok(e);
}
@@ -728,7 +735,9 @@ impl NvencCudaEncoder {
best = self
.try_open_session(FLOOR_BPS, split_mode)
.or_else(|_| self.try_open_session(FLOOR_BPS, no_split))
.context("NVENC initialize_encoder rejected even at the floor bitrate")?;
.context(
"NVENC initialize_encoder rejected even at the floor bitrate",
)?;
best_bps = FLOOR_BPS;
}
tracing::warn!(
@@ -933,8 +942,12 @@ impl Encoder for NvencCudaEncoder {
// In-band HDR10 SEI on every IDR when in HDR mode (inert on Linux until Phase 5.1 lands a
// 10-bit input, but ported so the wiring is complete). HEVC/H.264 carry SEI; AV1 = OBUs.
let is_idr = flags != 0 || opening;
let mastering_sei = self.hdr_meta.map(|m| crate::hdr::hevc_mastering_display_sei(&m));
let cll_sei = self.hdr_meta.map(|m| crate::hdr::hevc_content_light_level_sei(&m));
let mastering_sei = self
.hdr_meta
.map(|m| crate::hdr::hevc_mastering_display_sei(&m));
let cll_sei = self
.hdr_meta
.map(|m| crate::hdr::hevc_content_light_level_sei(&m));
let mut sei: Vec<nv::NV_ENC_SEI_PAYLOAD> = Vec::new();
if is_idr && self.hdr {
if let Some(p) = mastering_sei.as_ref() {
@@ -1174,7 +1187,10 @@ mod tests {
}
}
assert!(aus > 0, "no AUs produced");
assert!(first_key, "first AU must be a keyframe (session opening IDR)");
assert!(
first_key,
"first AU must be a keyframe (session opening IDR)"
);
assert!(enc.caps().supports_rfi, "RTX NVENC must advertise RFI");
// Invalidate a recent, still-in-DPB range (frames 5..=6; DPB depth is RFI_DPB=5, so 3..=7 are
@@ -1196,10 +1212,18 @@ mod tests {
anchor_was_keyframe = au.keyframe;
}
}
assert!(saw_anchor, "the post-RFI AU must carry recovery_anchor (the F2 fix)");
assert!(!anchor_was_keyframe, "RFI re-anchor must be a P-frame, not an IDR");
assert!(
saw_anchor,
"the post-RFI AU must carry recovery_anchor (the F2 fix)"
);
assert!(
!anchor_was_keyframe,
"RFI re-anchor must be a P-frame, not an IDR"
);
enc.flush().ok();
println!("nvenc_cuda smoke: {aus} AUs, RFI succeeded, recovery-anchor tagged on the P-frame");
println!(
"nvenc_cuda smoke: {aus} AUs, RFI succeeded, recovery-anchor tagged on the P-frame"
);
}
/// ON-HARDWARE (RTX box `.21`): the 4:4:4 path — a planar-YUV444 `DeviceBuffer` through an HEVC
@@ -1262,12 +1286,17 @@ mod tests {
8,
ChromaFormat::Yuv420,
) else {
eprintln!("skipping rfi_declines_impossible_ranges: NVENC unavailable (no NVIDIA driver)");
eprintln!(
"skipping rfi_declines_impossible_ranges: NVENC unavailable (no NVIDIA driver)"
);
return;
};
// No live session yet (lazy init on first frame) → every RFI request declines.
assert!(!enc.invalidate_ref_frames(0, 0), "no session → decline");
assert!(!enc.invalidate_ref_frames(10, 5), "first > last → decline");
assert!(!enc.invalidate_ref_frames(-1, 3), "negative first → decline");
assert!(
!enc.invalidate_ref_frames(-1, 3),
"negative first → decline"
);
}
}