refactor(host): hoist the direct-NVENC init-params authoring into nvenc_core

Both backends' build_init_params authored an identical NV_ENC_INITIALIZE_PARAMS
(P1/ULL preset, PTD, session dimensions/rate, split-encode mode) — the only
difference was the Windows-only enableEncodeAsync flag (Linux is sync-only).
Hoist it to nvenc_core::build_init_params(codec_guid, w, h, fps, cfg, split_mode,
enable_async); Linux's two call sites pass enable_async=false (the field stays 0
as before), Windows passes its session_async through. Keeps open and in-place
reconfigure presenting the SAME init params, now guaranteed identical across
platforms too.

Verified on BOTH platforms: Linux clippy 0/0 (nvenc,vulkan-encode,pyrowave, RTX
5070 Ti) and Windows clippy 0/0 (nvenc,amf-qsv, RTX 4090 / .173).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 14:58:03 +02:00
parent 2ae5cf98ee
commit 5748706631
3 changed files with 70 additions and 67 deletions
@@ -32,7 +32,7 @@
#![deny(clippy::undocumented_unsafe_blocks)] #![deny(clippy::undocumented_unsafe_blocks)]
use super::nvenc_core::{ use super::nvenc_core::{
apply_low_latency_config, codec_guid, LowLatencyConfig, NvStatusExt, RFI_DPB, apply_low_latency_config, build_init_params, codec_guid, LowLatencyConfig, NvStatusExt, RFI_DPB,
}; };
use super::nvenc_status; use super::nvenc_status;
use super::{ChromaFormat, Codec, EncodedFrame, Encoder, EncoderCaps}; use super::{ChromaFormat, Codec, EncodedFrame, Encoder, EncoderCaps};
@@ -599,34 +599,6 @@ impl NvencCudaEncoder {
Ok(cfg) Ok(cfg)
} }
/// Author the `NV_ENC_INITIALIZE_PARAMS` pointing at `cfg`. Shared by [`try_open_session`]
/// and [`Encoder::reconfigure_bitrate`] — a reconfigure must present the SAME init params as
/// the open. The returned struct borrows `cfg` raw; the caller keeps `cfg` alive across the
/// NVENC call it feeds this into.
fn build_init_params(
&self,
cfg: &mut nv::NV_ENC_CONFIG,
split_mode: u32,
) -> nv::NV_ENC_INITIALIZE_PARAMS {
let mut init = nv::NV_ENC_INITIALIZE_PARAMS {
version: nv::NV_ENC_INITIALIZE_PARAMS_VER,
encodeGUID: self.codec_guid,
presetGUID: nv::NV_ENC_PRESET_P1_GUID,
tuningInfo: nv::NV_ENC_TUNING_INFO::NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY,
encodeWidth: self.width,
encodeHeight: self.height,
darWidth: self.width,
darHeight: self.height,
frameRateNum: self.fps,
frameRateDen: 1,
enablePTD: 1,
encodeConfig: cfg,
..Default::default()
};
init.set_splitEncodeMode(split_mode);
init
}
/// Open + configure + initialize ONE NVENC CUDA session at `bitrate` (bps) and `split_mode`. /// Open + configure + initialize ONE NVENC CUDA session at `bitrate` (bps) and `split_mode`.
/// Returns the session handle, or destroys it and returns the error. /// 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> {
@@ -654,7 +626,15 @@ impl NvencCudaEncoder {
return Err(e); return Err(e);
} }
}; };
let mut init = self.build_init_params(&mut cfg, split_mode); let mut init = build_init_params(
self.codec_guid,
self.width,
self.height,
self.fps,
&mut cfg,
split_mode,
false,
);
match (api().initialize_encoder)(enc, &mut init).nv_ok() { match (api().initialize_encoder)(enc, &mut init).nv_ok() {
Ok(()) => Ok(enc), Ok(()) => Ok(enc),
@@ -1225,7 +1205,15 @@ impl Encoder for NvencCudaEncoder {
}; };
let mut params = nv::NV_ENC_RECONFIGURE_PARAMS { let mut params = nv::NV_ENC_RECONFIGURE_PARAMS {
version: nv::NV_ENC_RECONFIGURE_PARAMS_VER, version: nv::NV_ENC_RECONFIGURE_PARAMS_VER,
reInitEncodeParams: self.build_init_params(&mut cfg, self.split_mode), reInitEncodeParams: build_init_params(
self.codec_guid,
self.width,
self.height,
self.fps,
&mut cfg,
self.split_mode,
false,
),
..Default::default() ..Default::default()
}; };
// Keep the encoder's RC state and reference chain: no reset, no IDR — the in-flight // Keep the encoder's RC state and reference chain: no reset, no IDR — the in-flight
@@ -68,6 +68,42 @@ pub(super) struct LowLatencyConfig {
pub rfi_supported: bool, pub rfi_supported: bool,
} }
/// Author the shared `NV_ENC_INITIALIZE_PARAMS` (P1/ULL preset, PTD, the session dimensions/rate)
/// pointing at `cfg`. `enable_async` drives the Windows two-thread async retrieve — Linux is
/// sync-only and passes `false`, leaving `enableEncodeAsync` at 0 as before. The returned struct
/// borrows `cfg` as a raw pointer; the caller must keep `cfg` alive across the NVENC call it feeds
/// this into. Used at open and at in-place reconfigure, which must present the SAME init params.
#[allow(clippy::too_many_arguments)]
pub(super) fn build_init_params(
codec_guid: nv::GUID,
width: u32,
height: u32,
fps: u32,
cfg: &mut nv::NV_ENC_CONFIG,
split_mode: u32,
enable_async: bool,
) -> nv::NV_ENC_INITIALIZE_PARAMS {
let mut init = nv::NV_ENC_INITIALIZE_PARAMS {
version: nv::NV_ENC_INITIALIZE_PARAMS_VER,
encodeGUID: codec_guid,
presetGUID: nv::NV_ENC_PRESET_P1_GUID,
tuningInfo: nv::NV_ENC_TUNING_INFO::NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY,
encodeWidth: width,
encodeHeight: height,
darWidth: width,
darHeight: height,
frameRateNum: fps,
frameRateDen: 1,
enablePTD: 1,
enableEncodeAsync: enable_async as u32,
encodeConfig: cfg,
..Default::default()
};
// splitEncodeMode is a C bitfield — set via the generated accessor, not a struct field.
init.set_splitEncodeMode(split_mode);
init
}
/// Author the shared low-latency NVENC config onto a **preset-seeded** `cfg`: CBR + infinite GOP + /// Author the shared low-latency NVENC config onto a **preset-seeded** `cfg`: CBR + infinite GOP +
/// P-only + ~1-frame VBV, per-codec tier/level, chroma + bit depth, unconditional colour signaling, /// P-only + ~1-frame VBV, per-codec tier/level, chroma + bit depth, unconditional colour signaling,
/// and the RFI DPB. The caller seeds `cfg` from the P1/ULL preset first (that call needs the /// and the RFI DPB. The caller seeds `cfg` from the P1/ULL preset first (that call needs the
@@ -37,7 +37,7 @@
#![deny(clippy::undocumented_unsafe_blocks)] #![deny(clippy::undocumented_unsafe_blocks)]
use super::nvenc_core::{ use super::nvenc_core::{
apply_low_latency_config, codec_guid, LowLatencyConfig, NvStatusExt, RFI_DPB, apply_low_latency_config, build_init_params, codec_guid, LowLatencyConfig, NvStatusExt, RFI_DPB,
}; };
use super::nvenc_status; use super::nvenc_status;
use super::{ChromaFormat, Codec, EncodedFrame, Encoder, EncoderCaps}; use super::{ChromaFormat, Codec, EncodedFrame, Encoder, EncoderCaps};
@@ -742,39 +742,6 @@ impl NvencD3d11Encoder {
Ok(cfg) Ok(cfg)
} }
/// Author the `NV_ENC_INITIALIZE_PARAMS` pointing at `cfg`. Shared by [`try_open_session`]
/// and [`Encoder::reconfigure_bitrate`] — a reconfigure must present the SAME init params as
/// the open. The returned struct borrows `cfg` raw; the caller keeps `cfg` alive across the
/// NVENC call it feeds this into.
fn build_init_params(
&self,
cfg: &mut nv::NV_ENC_CONFIG,
split_mode: u32,
enable_async: bool,
) -> nv::NV_ENC_INITIALIZE_PARAMS {
let mut init = nv::NV_ENC_INITIALIZE_PARAMS {
version: nv::NV_ENC_INITIALIZE_PARAMS_VER,
encodeGUID: self.codec_guid,
presetGUID: nv::NV_ENC_PRESET_P1_GUID,
tuningInfo: nv::NV_ENC_TUNING_INFO::NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY,
encodeWidth: self.width,
encodeHeight: self.height,
darWidth: self.width,
darHeight: self.height,
frameRateNum: self.fps,
frameRateDen: 1,
enablePTD: 1,
// Two-thread async retrieve (§5.B): completion events signal the retrieve thread
// instead of `lock_bitstream` blocking the submit thread.
enableEncodeAsync: enable_async as u32,
encodeConfig: cfg,
..Default::default()
};
// splitEncodeMode is a C bitfield — set via the generated accessor, not a struct field.
init.set_splitEncodeMode(split_mode);
init
}
/// Open + configure + initialize ONE NVENC session at `bitrate` (bps) and `split_mode`. Returns /// Open + configure + initialize ONE NVENC session at `bitrate` (bps) and `split_mode`. Returns
/// the session handle, or destroys it and returns the error. NVENC has no re-init after a failed /// the session handle, or destroys it and returns the error. NVENC has no re-init after a failed
/// `initialize_encoder`, so the bitrate-clamp search in `init_session` calls this once per probe. /// `initialize_encoder`, so the bitrate-clamp search in `init_session` calls this once per probe.
@@ -809,7 +776,15 @@ impl NvencD3d11Encoder {
return Err(e); return Err(e);
} }
}; };
let mut init = self.build_init_params(&mut cfg, split_mode, enable_async); let mut init = build_init_params(
self.codec_guid,
self.width,
self.height,
self.fps,
&mut cfg,
split_mode,
enable_async,
);
match (api().initialize_encoder)(enc, &mut init).nv_ok() { match (api().initialize_encoder)(enc, &mut init).nv_ok() {
Ok(()) => Ok(enc), Ok(()) => Ok(enc),
@@ -1541,7 +1516,11 @@ impl Encoder for NvencD3d11Encoder {
}; };
let mut params = nv::NV_ENC_RECONFIGURE_PARAMS { let mut params = nv::NV_ENC_RECONFIGURE_PARAMS {
version: nv::NV_ENC_RECONFIGURE_PARAMS_VER, version: nv::NV_ENC_RECONFIGURE_PARAMS_VER,
reInitEncodeParams: self.build_init_params( reInitEncodeParams: build_init_params(
self.codec_guid,
self.width,
self.height,
self.fps,
&mut cfg, &mut cfg,
self.split_mode, self.split_mode,
self.session_async, self.session_async,