1#![allow(unsafe_op_in_unsafe_fn)]
26#![allow(non_camel_case_types)]
27#![allow(non_snake_case)]
28
29use std::collections::{HashMap, VecDeque};
30use std::ffi::{CStr, CString, c_char, c_void};
31use std::os::unix::io::AsRawFd;
32use std::ptr;
33use std::sync::Arc;
34use std::sync::atomic::{AtomicBool, Ordering};
35
36use libloading::{Library, Symbol};
37use smithay::backend::allocator::{Buffer, Fourcc, dmabuf::Dmabuf};
38
39use super::codec::{
40 Codec, FRAME_ANCHOR, FRAME_DELTA, FRAME_INTRA, FRAME_KEY, Hardware, VIDEO_HEADER_LEN,
41 av1_level, h264_dpb_frames, h264_level, h265_dpb_frames, h265_level, h265_tier,
42 push_video_header,
43};
44use super::dmabuf_identity::DmaBufIdentity;
45use super::frame_rate::FrameRate;
46use super::reference::{ANCHORS, Invalidation, Reference, ReferenceReport, ReferenceWindow};
47use super::session::{PeakBucket, RateSettings};
48use super::sps::{WideFrameNum, h264_frame_num_range};
49use crate::CaptureSettings;
50use nvcodec_sys::cuda::*;
51use nvcodec_sys::*;
52
53type CUmodule = *mut c_void;
56type CUfunction = *mut c_void;
57
58const ARGB_TO_NV12_PTX: &[u8] = include_bytes!("argb_to_nv12.ptx");
63
64unsafe fn load_kernels(cuda: &CudaFunctions) -> Option<CUmodule> {
66 let mut ptx = ARGB_TO_NV12_PTX.to_vec();
67 ptx.push(0);
68 let mut module: CUmodule = ptr::null_mut();
69 ((cuda.cuModuleLoadData)(&mut module, ptx.as_ptr() as *const c_void) == CUresult::CUDA_SUCCESS)
70 .then_some(module)
71}
72
73type EGLDisplay = *const c_void;
76type EGLImageKHR = *mut c_void;
77type EGLint = i32;
78type EGLenum = u32;
79type EGLBoolean = u32;
80
81const EGL_NO_IMAGE_KHR: EGLImageKHR = ptr::null_mut();
82const EGL_LINUX_DMA_BUF_EXT: u32 = 0x3270;
83const EGL_DMA_BUF_PLANE0_FD_EXT: EGLint = 0x3272;
84const EGL_DMA_BUF_PLANE0_OFFSET_EXT: EGLint = 0x3273;
85const EGL_DMA_BUF_PLANE0_PITCH_EXT: EGLint = 0x3274;
86const EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT: EGLint = 0x3443;
87const EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT: EGLint = 0x3444;
88const EGL_WIDTH: EGLint = 0x3057;
89const EGL_HEIGHT: EGLint = 0x3056;
90const EGL_LINUX_DRM_FOURCC_EXT: EGLint = 0x3271;
91const EGL_NONE: EGLint = 0x3038;
92
93type CUgraphicsResource = *mut c_void;
96
97const CU_EGL_FRAME_TYPE_ARRAY: u32 = 0;
100const CU_EGL_FRAME_TYPE_PITCH: u32 = 1;
101const CU_AD_FORMAT_U8: u32 = 1;
103
104const CU_TRSF_READ_AS_INTEGER: u32 = 1;
107
108#[repr(C)]
111#[derive(Clone, Copy)]
112struct CUeglFrame {
113 frame: CUeglFrameUnion,
114 width: u32,
115 height: u32,
116 depth: u32,
117 pitch: u32,
118 plane_count: u32,
119 num_channels: u32,
120 frame_type: u32,
121 egl_color_format: u32,
122 cu_format: u32,
123}
124
125#[repr(C)]
128#[derive(Clone, Copy)]
129union CUeglFrameUnion {
130 p_array: [CUarray; 3],
131 p_pitch: [*mut c_void; 3],
132}
133
134type EglCreateImageKhrFn = unsafe extern "C" fn(
135 dpy: EGLDisplay,
136 ctx: *mut c_void,
137 target: EGLenum,
138 buffer: *mut c_void,
139 attrib_list: *const EGLint,
140) -> EGLImageKHR;
141type EglDestroyImageKhrFn = unsafe extern "C" fn(dpy: EGLDisplay, image: EGLImageKHR) -> EGLBoolean;
142
143struct EglFunctions {
146 _lib: Library,
147 eglGetProcAddress: unsafe extern "C" fn(procname: *const c_char) -> *mut c_void,
148 eglCreateImageKHR: EglCreateImageKhrFn,
149 eglDestroyImageKHR: EglDestroyImageKhrFn,
150}
151
152struct CudaFunctions {
155 _lib: Library,
156 cuInit: unsafe extern "C" fn(flags: u32) -> CUresult,
157 cuDeviceGet: unsafe extern "C" fn(device: *mut CUdevice, ordinal: i32) -> CUresult,
158 cuDeviceGetByPCIBusId:
159 unsafe extern "C" fn(dev: *mut CUdevice, pciBusId: *const c_char) -> CUresult,
160 cuDevicePrimaryCtxRetain: unsafe extern "C" fn(pctx: *mut CUcontext, dev: CUdevice) -> CUresult,
161 cuCtxPushCurrent_v2: unsafe extern "C" fn(ctx: CUcontext) -> CUresult,
162 cuCtxPopCurrent_v2: unsafe extern "C" fn(pctx: *mut CUcontext) -> CUresult,
163 cuDevicePrimaryCtxRelease_v2: unsafe extern "C" fn(dev: CUdevice) -> CUresult,
164 cuMemAlloc_v2: unsafe extern "C" fn(dptr: *mut CUdeviceptr, bytesize: usize) -> CUresult,
165 cuMemAllocPitch_v2: unsafe extern "C" fn(
166 dptr: *mut CUdeviceptr,
167 pPitch: *mut usize,
168 WidthInBytes: usize,
169 Height: usize,
170 ElementSizeBytes: u32,
171 ) -> CUresult,
172 cuMemFree_v2: unsafe extern "C" fn(dptr: CUdeviceptr) -> CUresult,
173 cuMemcpyHtoD_v2: unsafe extern "C" fn(
174 dstDevice: CUdeviceptr,
175 srcHost: *const c_void,
176 ByteCount: usize,
177 ) -> CUresult,
178 cuMemcpyDtoH_v2: unsafe extern "C" fn(
179 dstHost: *mut c_void,
180 srcDevice: CUdeviceptr,
181 ByteCount: usize,
182 ) -> CUresult,
183 cuMemcpy2D_v2: unsafe extern "C" fn(pCopy: *const CUDA_MEMCPY2D) -> CUresult,
184 cuMemcpy2DAsync_v2:
185 unsafe extern "C" fn(pCopy: *const CUDA_MEMCPY2D, hStream: CUstream) -> CUresult,
186 cuStreamSynchronize: unsafe extern "C" fn(hStream: CUstream) -> CUresult,
187 cuModuleLoadData: unsafe extern "C" fn(module: *mut CUmodule, image: *const c_void) -> CUresult,
188 cuModuleGetFunction: unsafe extern "C" fn(
189 hfunc: *mut CUfunction,
190 hmod: CUmodule,
191 name: *const c_char,
192 ) -> CUresult,
193 cuModuleUnload: unsafe extern "C" fn(hmod: CUmodule) -> CUresult,
194 cuTexObjectCreate: unsafe extern "C" fn(
195 tex: *mut CUtexObject,
196 res: *const CUDA_RESOURCE_DESC,
197 sampling: *const CUDA_TEXTURE_DESC,
198 view: *const c_void,
199 ) -> CUresult,
200 cuTexObjectDestroy: unsafe extern "C" fn(tex: CUtexObject) -> CUresult,
201 #[allow(clippy::type_complexity)]
202 cuLaunchKernel: unsafe extern "C" fn(
203 f: CUfunction,
204 gx: u32,
205 gy: u32,
206 gz: u32,
207 bx: u32,
208 by: u32,
209 bz: u32,
210 shared: u32,
211 stream: CUstream,
212 params: *mut *mut c_void,
213 extra: *mut *mut c_void,
214 ) -> CUresult,
215 cuMemHostRegister_v2:
216 unsafe extern "C" fn(p: *mut c_void, bytesize: usize, flags: u32) -> CUresult,
217 cuMemHostUnregister: unsafe extern "C" fn(p: *mut c_void) -> CUresult,
218 cuGraphicsEGLRegisterImage: unsafe extern "C" fn(
219 pCudaResource: *mut CUgraphicsResource,
220 image: EGLImageKHR,
221 flags: u32,
222 ) -> CUresult,
223 cuGraphicsUnregisterResource: unsafe extern "C" fn(resource: CUgraphicsResource) -> CUresult,
224 cuGraphicsResourceGetMappedEglFrame: unsafe extern "C" fn(
225 pEglFrame: *mut CUeglFrame,
226 resource: CUgraphicsResource,
227 index: u32,
228 mipLevel: u32,
229 ) -> CUresult,
230 cuDeviceGetCount: unsafe extern "C" fn(count: *mut i32) -> CUresult,
231 cuDeviceGetName: unsafe extern "C" fn(name: *mut c_char, len: i32, dev: CUdevice) -> CUresult,
232 cuDeviceGetUuid: unsafe extern "C" fn(uuid: *mut CUuuid, dev: CUdevice) -> CUresult,
233 cuGetErrorName: unsafe extern "C" fn(error: CUresult, pStr: *mut *const c_char) -> CUresult,
234 cuImportExternalSemaphore: Option<
237 unsafe extern "C" fn(
238 out: *mut CUexternalSemaphore,
239 desc: *const CUDA_EXTERNAL_SEMAPHORE_HANDLE_DESC,
240 ) -> CUresult,
241 >,
242 cuWaitExternalSemaphoresAsync: Option<
243 unsafe extern "C" fn(
244 semaphores: *const CUexternalSemaphore,
245 params: *const CUDA_EXTERNAL_SEMAPHORE_WAIT_PARAMS,
246 count: u32,
247 stream: CUstream,
248 ) -> CUresult,
249 >,
250 cuDestroyExternalSemaphore: Option<unsafe extern "C" fn(sem: CUexternalSemaphore) -> CUresult>,
251}
252
253struct NvencLibrary {
263 _lib: Library,
264 create_instance:
265 unsafe extern "C" fn(functionList: *mut NV_ENCODE_API_FUNCTION_LIST) -> NVENCSTATUS,
266 get_max_version: Option<unsafe extern "C" fn(*mut u32) -> NVENCSTATUS>,
267}
268
269static NVENC_NEG_VER: std::sync::OnceLock<(u32, u32)> = std::sync::OnceLock::new();
272
273#[derive(Clone, Copy, Debug)]
286enum NvStruct {
287 FunctionList,
288 OpenSessionExParams,
289 Config,
290 RcParams,
291 PresetConfig,
292 InitializeParams,
293 ReconfigureParams,
294 RegisterResource,
295 MapInputResource,
296 CreateBitstreamBuffer,
297 PicParams,
298 LockBitstream,
299 CapsParam,
300}
301
302impl NvStruct {
303 fn rev(self, api: u32) -> (u32, bool) {
315 match self {
316 NvStruct::FunctionList => (2, false),
317 NvStruct::OpenSessionExParams => (1, false),
318 NvStruct::Config => match api {
319 0xC2.. => (9, true),
320 0xC0..=0xC1 => (8, true),
321 _ => (7, true),
322 },
323 NvStruct::RcParams => (1, false),
324 NvStruct::PresetConfig => (if api >= 0xC2 { 5 } else { 4 }, true),
325 NvStruct::InitializeParams => match api {
326 0xC2.. => (7, true),
327 0xC1 => (6, true),
328 _ => (5, true),
329 },
330 NvStruct::ReconfigureParams => (if api >= 0xC2 { 2 } else { 1 }, true),
331 NvStruct::RegisterResource => match api {
332 0xC2.. => (5, false),
333 0xC0..=0xC1 => (4, false),
334 _ => (3, false),
335 },
336 NvStruct::MapInputResource => (4, false),
337 NvStruct::CreateBitstreamBuffer => (1, false),
338 NvStruct::PicParams => match api {
339 0xC2.. => (7, true),
340 0xC0..=0xC1 => (6, true),
341 _ => (4, true),
342 },
343 NvStruct::LockBitstream => match api {
344 0xC2.. => (2, true),
345 0xC1 => (1, true),
346 0xC0 => (2, false),
347 _ => (1, false),
348 },
349 NvStruct::CapsParam => (1, false),
350 }
351 }
352}
353
354fn nvenc_struct_ver(s: NvStruct, maj: u32, min: u32) -> u32 {
367 let (rev, high_bit) = s.rev((maj << 4) | (min & 0xF));
368 (maj & 0xFF) | ((min & 0xF) << 24) | (rev << 16) | (0x7 << 28) | (u32::from(high_bit) << 31)
369}
370
371#[inline]
375fn nvenc_cur_ver() -> (u32, u32) {
376 NVENC_NEG_VER
377 .get()
378 .copied()
379 .unwrap_or((NVENCAPI_VERSION & 0xFF, (NVENCAPI_VERSION >> 24) & 0xFF))
380}
381
382#[inline]
385fn sv(s: NvStruct) -> u32 {
386 let (m, n) = nvenc_cur_ver();
387 nvenc_struct_ver(s, m, n)
388}
389
390#[inline]
394fn neg_api() -> u32 {
395 let (m, n) = nvenc_cur_ver();
396 m | (n << 24)
397}
398
399#[repr(C)]
404struct Negotiated<T> {
405 value: T,
406 tail: [u32; 2],
407}
408
409impl<T> Negotiated<T> {
410 fn new(value: T) -> Self {
411 Self {
412 value,
413 tail: [0; 2],
414 }
415 }
416}
417
418fn reconfigure_params(
421 init: &NV_ENC_INITIALIZE_PARAMS,
422 (maj, min): (u32, u32),
423 reset: bool,
424 force_idr: bool,
425) -> Negotiated<NV_ENC_RECONFIGURE_PARAMS> {
426 let mut p = Negotiated::new(NV_ENC_RECONFIGURE_PARAMS {
427 version: nvenc_struct_ver(NvStruct::ReconfigureParams, maj, min),
428 reInitEncodeParams: *init,
429 ..Default::default()
430 });
431 if (maj << 4) | min < 0xC2 {
432 p.tail[0] = u32::from(reset) | u32::from(force_idr) << 1;
433 } else {
434 p.value.set_resetEncoder(u32::from(reset));
435 p.value.set_forceIDR(u32::from(force_idr));
436 }
437 p
438}
439
440fn nvenc_negotiate(lib: &NvencLibrary) {
462 NVENC_NEG_VER.get_or_init(|| {
463 let pinned = (NVENCAPI_VERSION & 0xFF, (NVENCAPI_VERSION >> 24) & 0xFF);
464 let mut drv_max: u32 = 0;
465 if let Some(get_max) = lib.get_max_version {
466 let mut m: u32 = 0;
467 if unsafe { get_max(&mut m) } == NVENCSTATUS::NV_ENC_SUCCESS {
468 drv_max = m;
469 }
470 }
471 if let Ok(cap) = std::env::var("PIXELFLUX_NVENC_MAX_API") {
472 let mut it = cap.split('.');
473 if let (Some(a), Some(b)) = (it.next(), it.next())
474 && let (Ok(cm), Ok(cn)) = (a.parse::<u32>(), b.parse::<u32>())
475 {
476 let capv = (cm << 4) | (cn & 0xF);
477 if capv != 0 && (drv_max == 0 || capv < drv_max) {
478 drv_max = capv;
479 }
480 }
481 }
482 let candidates = [pinned, (12, 1), (12, 0), (11, 1), (11, 0), (10, 0)];
483 for (maj, min) in candidates {
484 let vv = (maj << 4) | min;
485 if drv_max != 0 && vv > drv_max {
486 continue;
487 }
488 let mut probe = NV_ENCODE_API_FUNCTION_LIST {
489 version: nvenc_struct_ver(NvStruct::FunctionList, maj, min),
490 ..Default::default()
491 };
492 let st = unsafe { (lib.create_instance)(&mut probe) };
493 if st == NVENCSTATUS::NV_ENC_SUCCESS
494 && probe.nvEncOpenEncodeSessionEx.is_some()
495 && probe.nvEncInitializeEncoder.is_some()
496 && probe.nvEncGetEncodePresetConfigEx.is_some()
497 && probe.nvEncEncodePicture.is_some()
498 && probe.nvEncLockBitstream.is_some()
499 {
500 crate::log::debug!("[pixelflux] NVENC API version negotiated: {}.{}", maj, min);
501 return (maj, min);
502 }
503 }
504 pinned
505 });
506}
507
508struct CachedDmaBuf {
513 identity: DmaBufIdentity,
514 last_use: u64,
516 egl_image: EGLImageKHR,
517 cuda_resource: CUgraphicsResource,
518 egl_frame: CUeglFrame,
519 input: DmaBufInput,
520 tex: CUtexObject,
523}
524
525pub(crate) const EXTERNAL_INPUTS: usize = 3;
527
528const DMABUF_IMPORTS: usize = 16;
532
533#[derive(Clone, Copy)]
541struct ExternalInput {
542 device_ptr: CUdeviceptr,
543 pitch: usize,
544 width: u32,
545 height: u32,
546 format: NV_ENC_BUFFER_FORMAT,
547 registered: NV_ENC_REGISTERED_PTR,
548 mapped: NV_ENC_INPUT_PTR,
549}
550
551#[derive(Clone, Copy)]
559enum DmaBufInput {
560 Direct {
561 registered: NV_ENC_REGISTERED_PTR,
562 mapped: NV_ENC_INPUT_PTR,
563 format: NV_ENC_BUFFER_FORMAT,
564 },
565 Copy,
566}
567
568fn egl_import_fourcc(code: Fourcc) -> Fourcc {
575 match code {
576 Fourcc::Xrgb8888 => Fourcc::Argb8888,
577 Fourcc::Xbgr8888 => Fourcc::Abgr8888,
578 other => other,
579 }
580}
581
582fn fourcc_nvenc_format(code: Fourcc) -> Option<NV_ENC_BUFFER_FORMAT> {
583 match code {
584 Fourcc::Argb8888 | Fourcc::Xrgb8888 => {
585 Some(NV_ENC_BUFFER_FORMAT::NV_ENC_BUFFER_FORMAT_ARGB)
586 }
587 Fourcc::Abgr8888 | Fourcc::Xbgr8888 => {
588 Some(NV_ENC_BUFFER_FORMAT::NV_ENC_BUFFER_FORMAT_ABGR)
589 }
590 _ => None,
591 }
592}
593
594#[derive(Debug, Clone, Copy, PartialEq, Eq)]
597enum DirectPlane {
598 Pitch(u32),
600 Array(u32),
604}
605
606fn direct_plane(frame: &CUeglFrame, width: u32, height: u32) -> Option<DirectPlane> {
614 if frame.plane_count < 1 || width == 0 || height == 0 {
615 return None;
616 }
617 if frame.width < width || frame.height < height {
618 return None;
619 }
620 match frame.frame_type {
621 CU_EGL_FRAME_TYPE_PITCH => {
622 let plane = unsafe { frame.frame.p_pitch[0] };
623 let pitch_ok = frame.pitch >= width.saturating_mul(4) && frame.pitch.is_multiple_of(4);
624 (!plane.is_null() && pitch_ok).then_some(DirectPlane::Pitch(frame.pitch))
625 }
626 CU_EGL_FRAME_TYPE_ARRAY => {
627 let array = unsafe { frame.frame.p_array[0] };
628 let packed_8bit = frame.cu_format == CU_AD_FORMAT_U8 && frame.num_channels == 4;
629 (!array.is_null() && packed_8bit).then_some(DirectPlane::Array(frame.width * 4))
630 }
631 _ => None,
632 }
633}
634
635#[derive(Debug, Clone, Copy, PartialEq, Eq)]
647struct CapsDecision {
648 fullcolor: bool,
649 downgraded_color: bool,
650 too_large: Option<(i32, i32)>,
651}
652
653fn decide_caps(
655 req_fullcolor: bool,
656 req_w: i32,
657 req_h: i32,
658 cap_yuv444: Option<i32>,
659 cap_width_max: Option<i32>,
660 cap_height_max: Option<i32>,
661) -> CapsDecision {
662 let downgraded_color = req_fullcolor && cap_yuv444 == Some(0);
663 let fullcolor = req_fullcolor && !downgraded_color;
664 let exceeds = |req: i32, cap: Option<i32>| cap.is_some_and(|m| m > 0 && req > m);
665 let too_large = if exceeds(req_w, cap_width_max) || exceeds(req_h, cap_height_max) {
666 Some((cap_width_max.unwrap_or(0), cap_height_max.unwrap_or(0)))
667 } else {
668 None
669 };
670 CapsDecision {
671 fullcolor,
672 downgraded_color,
673 too_large,
674 }
675}
676
677const HEADROOM_WIDTH: u32 = 4096;
682const HEADROOM_HEIGHT: u32 = 2160;
683
684const HEADROOM_FPS: u32 = 60;
687
688fn window_frames(codec: Codec, dpb: u32, anchored: bool) -> u32 {
694 dpb + u32::from(codec == Codec::H265 && anchored)
695}
696
697fn anchor_count(
708 codec: Codec,
709 api_major: u32,
710 invalidation: bool,
711 ltr: Option<i32>,
712 dpb: u32,
713 acknowledged: bool,
714) -> usize {
715 let fits =
716 |n: usize| invalidation && ltr.is_some_and(|l| l >= n as i32) && dpb as usize >= n + 2;
717 let count = match codec {
718 Codec::Av1 if api_major < 13 => 0,
719 Codec::H265 => ANCHORS,
720 Codec::Av1 if acknowledged && fits(ANCHORS) => ANCHORS,
721 _ => 1,
722 };
723 if fits(count) { count } else { 0 }
724}
725
726fn nvenc_headroom(size: u32, floor: u32, cap: Option<i32>) -> u32 {
730 let want = size.max(floor);
731 match cap {
732 Some(m) if m > 0 => want.min(m as u32),
733 _ => want,
734 }
735}
736
737const NVENC_AV1_RATE: (u64, u64) = (2, 3);
740
741static NVENC_AV1_WEIGHTED: AtomicBool = AtomicBool::new(false);
745
746static NVENC_SINGLE_PASS: AtomicBool = AtomicBool::new(false);
751
752fn level_rate(codec: Codec, bps: u64, weighted: bool) -> u64 {
755 if codec == Codec::Av1 && weighted {
756 (bps * NVENC_AV1_RATE.1).div_ceil(NVENC_AV1_RATE.0)
757 } else {
758 bps
759 }
760}
761
762fn nvenc_rate_ceiling(codec: Codec) -> u32 {
767 match codec {
768 Codec::Av1 => (160_000_000 * NVENC_AV1_RATE.0 / NVENC_AV1_RATE.1) as u32,
769 Codec::H265 => 800_000_000,
770 _ => 1_000_000_000,
771 }
772}
773
774fn split_mode(
789 codec: Codec,
790 width: u32,
791 height: u32,
792 engines: Option<i32>,
793 api: (u32, u32),
794) -> NV_ENC_SPLIT_ENCODE_MODE {
795 let forced = api >= (12, 1)
796 && engines.is_some_and(|n| n > 1)
797 && match codec {
798 Codec::Av1 => true,
799 Codec::H265 => u64::from(width) * u64::from(height) >= 3840 * 2160,
800 _ => false,
801 };
802 match (forced, codec, engines) {
803 (true, Codec::Av1, Some(3)) => NV_ENC_SPLIT_ENCODE_MODE::NV_ENC_SPLIT_THREE_FORCED_MODE,
804 (true, ..) => NV_ENC_SPLIT_ENCODE_MODE::NV_ENC_SPLIT_AUTO_FORCED_MODE,
805 _ => NV_ENC_SPLIT_ENCODE_MODE::NV_ENC_SPLIT_AUTO_MODE,
806 }
807}
808
809fn cbr_bps(settings: &CaptureSettings) -> u32 {
812 (settings.video_bitrate_kbps.max(0) as u32)
813 .saturating_mul(1000)
814 .min(nvenc_rate_ceiling(settings.codec))
815}
816
817fn log_held_rate(settings: &CaptureSettings, bps: u32) {
819 let asked = settings.video_bitrate_kbps.max(0) as u64 * 1000;
820 if u64::from(bps) < asked {
821 eprintln!(
822 "[NVENC] {} opens at {} kbps at most, its top level's ceiling: {} kbps is held there",
823 settings.codec.display(),
824 bps / 1000,
825 asked / 1000
826 );
827 }
828}
829
830fn cbr_vbv(settings: &CaptureSettings, bps: u32) -> u32 {
833 crate::encoders::vbv_bits(
834 bps,
835 settings.target_fps,
836 settings.keyframe_interval_s,
837 settings.video_vbv_multiplier,
838 )
839}
840
841fn set_cbr_rate(rc: &mut NV_ENC_RC_PARAMS, bps: u32, vbv: u32) {
846 rc.averageBitRate = bps;
847 rc.maxBitRate = bps;
848 rc.vbvBufferSize = vbv;
849 rc.vbvInitialDelay = vbv;
850}
851
852fn peak_of(settings: &CaptureSettings) -> Option<PeakBucket> {
863 RateSettings::new(settings).capped.then(|| {
864 let bps = cbr_bps(settings);
865 PeakBucket::new(bps, cbr_vbv(settings, bps), settings.target_fps)
866 })
867}
868
869pub const REFINE_FINER: u32 = 3;
876
877const REFIT_SHARE: f64 = 0.8;
880
881const BAND_LOG_FRAMES: usize = 512;
884
885fn reach_back(
890 log: &mut VecDeque<(u64, f64, f64)>,
891 pts: u64,
892 held: Option<(f64, f64)>,
893 holds: bool,
894) -> (Option<(f64, f64)>, bool) {
895 let from = log
896 .iter()
897 .filter(|b| b.0 >= pts)
898 .map(|b| b.1)
899 .reduce(f64::min);
900 log.retain(|b| b.0 < pts);
901 match (from, held) {
902 (Some(from), Some((a, b))) => (Some((a.min(from), b)), false),
903 (Some(_), None) => (None, !holds),
904 (None, held) => (held, false),
905 }
906}
907
908fn fill_band_map(
913 map: &mut Vec<i8>,
914 codec: Codec,
915 width: u32,
916 height: u32,
917 (from, to): (f64, f64),
918 delta: i32,
919) -> usize {
920 let block = match codec {
921 Codec::H264 => 16,
922 Codec::H265 => 32,
923 _ => 64,
924 };
925 let blocks = (width.div_ceil(block) * height.div_ceil(block)) as usize;
926 let first = ((from.clamp(0.0, 1.0) * blocks as f64).floor() as usize).min(blocks);
927 let last = ((to.clamp(0.0, 1.0) * blocks as f64).ceil() as usize).clamp(first, blocks);
928 map.clear();
929 map.resize(blocks, 0);
930 map[first..last].fill(delta.clamp(-(codec.quantizer_max().min(128) as i32), 0) as i8);
931 first
932}
933
934fn point_at_qp_map(pic: &mut NV_ENC_PIC_PARAMS, map: &mut [i8], api: (u32, u32)) {
938 if api < (12, 0) {
939 pic.reserved5 = [
940 map.as_mut_ptr().cast(),
941 ptr::without_provenance_mut(map.len()),
942 ];
943 } else {
944 pic.qpDeltaMap = map.as_mut_ptr();
945 pic.qpDeltaMapSize = map.len() as u32;
946 }
947}
948
949unsafe fn last_error(funcs: &NV_ENCODE_API_FUNCTION_LIST, session: *mut c_void) -> String {
953 funcs
954 .nvEncGetLastErrorString
955 .map(|f| f(session))
956 .filter(|p| !p.is_null())
957 .map(|p| CStr::from_ptr(p).to_string_lossy().into_owned())
958 .filter(|s| !s.is_empty())
959 .unwrap_or_else(|| "no error string".to_string())
960}
961
962unsafe fn query_cap(
966 funcs: &NV_ENCODE_API_FUNCTION_LIST,
967 session: *mut c_void,
968 codec: GUID,
969 cap: NV_ENC_CAPS,
970) -> Option<i32> {
971 let get = funcs.nvEncGetEncodeCaps?;
972 let mut param = NV_ENC_CAPS_PARAM {
973 version: sv(NvStruct::CapsParam),
974 capsToQuery: cap,
975 reserved: [0u32; 62],
976 };
977 let mut val: i32 = 0;
978 if get(session, codec, &mut param, &mut val) == NVENCSTATUS::NV_ENC_SUCCESS {
979 Some(val)
980 } else {
981 None
982 }
983}
984
985const NV_ENC_H264_PROFILE_HIGH_GUID: GUID = GUID {
987 Data1: 0x205b553d,
988 Data2: 0x5f01,
989 Data3: 0x4d9e,
990 Data4: [0x91, 0x84, 0xda, 0x32, 0x77, 0x5b, 0x55, 0x9b],
991};
992
993const NV_ENC_H264_PROFILE_HIGH_444_GUID: GUID = GUID {
995 Data1: 0x7ac663cb,
996 Data2: 0xa598,
997 Data3: 0x4960,
998 Data4: [0xb8, 0x44, 0x33, 0x9b, 0x26, 0x1a, 0x7d, 0x5c],
999};
1000
1001fn guid_eq(a: &GUID, b: &GUID) -> bool {
1003 a.Data1 == b.Data1 && a.Data2 == b.Data2 && a.Data3 == b.Data3 && a.Data4 == b.Data4
1004}
1005
1006fn codec_guid(codec: Codec) -> Option<GUID> {
1009 match codec {
1010 Codec::H264 => Some(NV_ENC_CODEC_H264_GUID),
1011 Codec::H265 => Some(NV_ENC_CODEC_HEVC_GUID),
1012 Codec::Av1 => Some(NV_ENC_CODEC_AV1_GUID),
1013 Codec::Jpeg | Codec::Vp8 | Codec::Vp9 => None,
1014 }
1015}
1016
1017pub(crate) fn probe_codecs(encode_node_index: i32) -> Result<Vec<(Codec, super::Formats)>, String> {
1026 let cuda = std::mem::ManuallyDrop::new(NvencEncoder::load_cuda()?);
1027 let nvenc_lib = std::mem::ManuallyDrop::new(NvencEncoder::load_nvenc()?);
1028 nvenc_negotiate(&nvenc_lib);
1029 crate::nvgpufilter::install();
1030 unsafe {
1031 let res = (cuda.cuInit)(0);
1032 if res != CUresult::CUDA_SUCCESS {
1033 return Err(format!(
1034 "Init CUDA failed: {}",
1035 NvencEncoder::get_error_string(&cuda, res)
1036 ));
1037 }
1038 let mut cu_device: CUdevice = 0;
1039 let bound = NvencEncoder::get_pci_bus_id(encode_node_index.max(0))
1040 .and_then(|id| CString::new(id).ok())
1041 .is_some_and(|id| {
1042 (cuda.cuDeviceGetByPCIBusId)(&mut cu_device, id.as_ptr()) == CUresult::CUDA_SUCCESS
1043 });
1044 if !bound && (cuda.cuDeviceGet)(&mut cu_device, 0) != CUresult::CUDA_SUCCESS {
1045 return Err("Failed to get default CUDA device".into());
1046 }
1047 let mut cu_context: CUcontext = ptr::null_mut();
1048 if (cuda.cuDevicePrimaryCtxRetain)(&mut cu_context, cu_device) != CUresult::CUDA_SUCCESS {
1049 return Err("Failed to retain the device's primary CUDA context".into());
1050 }
1051 if (cuda.cuCtxPushCurrent_v2)(cu_context) != CUresult::CUDA_SUCCESS {
1052 (cuda.cuDevicePrimaryCtxRelease_v2)(cu_device);
1053 return Err("Failed to make the primary CUDA context current".into());
1054 }
1055 let result = probe_session_codecs(&nvenc_lib, cu_context);
1056 (cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
1057 (cuda.cuDevicePrimaryCtxRelease_v2)(cu_device);
1058 result
1059 }
1060}
1061
1062pub(crate) const SESSIONS_TAKEN: &str = "the device has no NVENC session to spare";
1065
1066fn session_refusal(status: NVENCSTATUS) -> String {
1070 match status {
1071 NVENCSTATUS::NV_ENC_ERR_INCOMPATIBLE_CLIENT_KEY | NVENCSTATUS::NV_ENC_ERR_OUT_OF_MEMORY => {
1072 SESSIONS_TAKEN.into()
1073 }
1074 _ => "Failed to open NVENC session".into(),
1075 }
1076}
1077
1078unsafe fn codes_ten_bit(
1083 function_list: &NV_ENCODE_API_FUNCTION_LIST,
1084 session: *mut c_void,
1085 codec: Codec,
1086 guid: GUID,
1087) -> bool {
1088 matches!(codec, Codec::H265 | Codec::Av1)
1089 && query_cap(
1090 function_list,
1091 session,
1092 guid,
1093 NV_ENC_CAPS::NV_ENC_CAPS_SUPPORT_10BIT_ENCODE,
1094 ) == Some(1)
1095}
1096
1097fn set_bit_depth(config: &mut NV_ENC_CONFIG, codec: Codec, bit_depth: u32, api: (u32, u32)) {
1103 let depth = if bit_depth > 8 {
1104 NV_ENC_BIT_DEPTH::NV_ENC_BIT_DEPTH_10
1105 } else {
1106 NV_ENC_BIT_DEPTH::NV_ENC_BIT_DEPTH_8
1107 };
1108 let minus8 = u64::from(bit_depth.saturating_sub(8));
1109 unsafe {
1110 match codec {
1111 Codec::H265 if api >= (12, 2) => {
1112 let c = &mut config.encodeCodecConfig.hevcConfig;
1113 c.inputBitDepth = depth;
1114 c.outputBitDepth = depth;
1115 }
1116 Codec::H265 => config
1117 .encodeCodecConfig
1118 .hevcConfig
1119 ._bitfield_1
1120 .set(11, 3, minus8),
1121 Codec::Av1 if api >= (12, 2) => {
1122 let c = &mut config.encodeCodecConfig.av1Config;
1123 c.inputBitDepth = depth;
1124 c.outputBitDepth = depth;
1125 }
1126 Codec::Av1 => {
1127 let bits = &mut config.encodeCodecConfig.av1Config._bitfield_1;
1128 bits.set(12, 3, minus8);
1129 bits.set(15, 3, minus8);
1130 }
1131 _ => {}
1132 }
1133 }
1134}
1135
1136unsafe fn probe_session_codecs(
1139 nvenc_lib: &NvencLibrary,
1140 cu_context: CUcontext,
1141) -> Result<Vec<(Codec, super::Formats)>, String> {
1142 let mut function_list = NV_ENCODE_API_FUNCTION_LIST {
1143 version: sv(NvStruct::FunctionList),
1144 ..Default::default()
1145 };
1146 if (nvenc_lib.create_instance)(&mut function_list) != NVENCSTATUS::NV_ENC_SUCCESS {
1147 return Err("NvEncodeAPICreateInstance failed".into());
1148 }
1149 let (Some(open_fn), Some(destroy_fn)) = (
1150 function_list.nvEncOpenEncodeSessionEx,
1151 function_list.nvEncDestroyEncoder,
1152 ) else {
1153 return Err("the driver's NVENC function list has no session entry points".into());
1154 };
1155 let mut session_params = NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS {
1156 version: sv(NvStruct::OpenSessionExParams),
1157 deviceType: NV_ENC_DEVICE_TYPE::NV_ENC_DEVICE_TYPE_CUDA,
1158 device: cu_context as *mut c_void,
1159 apiVersion: neg_api(),
1160 ..Default::default()
1161 };
1162 let mut session: *mut c_void = ptr::null_mut();
1163 match open_fn(&mut session_params, &mut session) {
1164 NVENCSTATUS::NV_ENC_SUCCESS => {}
1165 status => return Err(session_refusal(status)),
1166 }
1167 let codecs = Codec::VIDEO
1168 .into_iter()
1169 .filter_map(|codec| {
1170 let guid = codec_guid(codec)
1171 .filter(|guid| NvencEncoder::device_encodes(&function_list, session, guid))?;
1172 let fullcolor = codec.fullcolor()
1173 && query_cap(
1174 &function_list,
1175 session,
1176 guid,
1177 NV_ENC_CAPS::NV_ENC_CAPS_SUPPORT_YUV444_ENCODE,
1178 ) == Some(1);
1179 let ten_bit = codes_ten_bit(&function_list, session, codec, guid);
1180 Some((
1181 codec,
1182 super::Formats {
1183 fullcolor,
1184 ten_bit: [ten_bit, ten_bit && fullcolor],
1185 },
1186 ))
1187 })
1188 .collect();
1189 destroy_fn(session);
1190 Ok(codecs)
1191}
1192
1193const SLICE_MODE_COUNT: u32 = 3;
1196
1197const SLICES_PER_FRAME: u32 = 4;
1205
1206const BAND_SLICES_MAX: usize = 8;
1211
1212const AV1_REFERENCES: u32 = 4;
1217
1218const BITSTREAM_BUFFERS: usize = 1;
1224
1225#[derive(Clone, Copy, Debug)]
1239pub(crate) struct NvencTuning {
1240 pub preset: GUID,
1241 pub multipass: NV_ENC_MULTI_PASS,
1242 pub spatial_aq: bool,
1243 pub temporal_aq: bool,
1247 pub hevc_high_tier: bool,
1251 pub slices: Option<u32>,
1255 #[cfg(test)]
1259 pub hardware_csc: bool,
1260 #[cfg(test)]
1263 pub ref_l0: Option<NV_ENC_NUM_REF_FRAMES>,
1264 #[cfg(test)]
1266 pub split: Option<NV_ENC_SPLIT_ENCODE_MODE>,
1267}
1268
1269impl Default for NvencTuning {
1270 fn default() -> Self {
1271 Self {
1272 preset: NV_ENC_PRESET_P3_GUID,
1273 multipass: NV_ENC_MULTI_PASS::NV_ENC_TWO_PASS_QUARTER_RESOLUTION,
1274 spatial_aq: false,
1275 temporal_aq: false,
1276 hevc_high_tier: true,
1277 slices: None,
1278 #[cfg(test)]
1279 hardware_csc: false,
1280 #[cfg(test)]
1281 ref_l0: None,
1282 #[cfg(test)]
1283 split: None,
1284 }
1285 }
1286}
1287
1288fn profile_guid(codec: Codec, fullcolor: bool, bit_depth: u32) -> GUID {
1292 match (codec, fullcolor) {
1293 (Codec::H264, true) => NV_ENC_H264_PROFILE_HIGH_444_GUID,
1294 (Codec::H264, false) => NV_ENC_H264_PROFILE_HIGH_GUID,
1295 (Codec::H265, true) => NV_ENC_HEVC_PROFILE_FREXT_GUID,
1296 (Codec::H265, false) if bit_depth > 8 => NV_ENC_HEVC_PROFILE_MAIN10_GUID,
1297 (Codec::H265, false) => NV_ENC_HEVC_PROFILE_MAIN_GUID,
1298 _ => NV_ENC_AV1_PROFILE_MAIN_GUID,
1299 }
1300}
1301
1302#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1320enum ConvertLayout {
1321 Nv12,
1322 P010,
1323 Yuv444P10,
1324}
1325
1326impl ConvertLayout {
1327 fn of(fullcolor: bool, bit_depth: u32) -> Option<Self> {
1330 match (fullcolor, bit_depth > 8) {
1331 (false, false) => Some(Self::Nv12),
1332 (false, true) => Some(Self::P010),
1333 (true, true) => Some(Self::Yuv444P10),
1334 (true, false) => None,
1335 }
1336 }
1337
1338 fn kernels(self) -> (&'static CStr, &'static CStr) {
1340 match self {
1341 Self::Nv12 => (c"argb_to_nv12", c"argb_tex_to_nv12"),
1342 Self::P010 => (c"argb_to_p010", c"argb_tex_to_p010"),
1343 Self::Yuv444P10 => (c"argb_to_yuv444p10", c"argb_tex_to_yuv444p10"),
1344 }
1345 }
1346
1347 fn extent(self, width: u32, height: u32) -> (usize, usize) {
1350 let (width, height) = (width as usize, height as usize);
1351 match self {
1352 Self::Nv12 => (width, height + height.div_ceil(2)),
1353 Self::P010 => (2 * width, height + height.div_ceil(2)),
1354 Self::Yuv444P10 => (2 * width, 3 * height),
1355 }
1356 }
1357
1358 fn format(self) -> NV_ENC_BUFFER_FORMAT {
1359 match self {
1360 Self::Nv12 => NV_ENC_BUFFER_FORMAT::NV_ENC_BUFFER_FORMAT_NV12,
1361 Self::P010 => NV_ENC_BUFFER_FORMAT::NV_ENC_BUFFER_FORMAT_YUV420_10BIT,
1362 Self::Yuv444P10 => NV_ENC_BUFFER_FORMAT::NV_ENC_BUFFER_FORMAT_YUV444_10BIT,
1363 }
1364 }
1365}
1366
1367struct ChromaConvert {
1368 module: CUmodule,
1369 kernel: CUfunction,
1370 kernel_tex: CUfunction,
1371 layout: ConvertLayout,
1372 surface: CUdeviceptr,
1373 pitch: usize,
1374 registered: NV_ENC_REGISTERED_PTR,
1375 mapped: NV_ENC_INPUT_PTR,
1376}
1377
1378impl ChromaConvert {
1379 unsafe fn new(
1384 cuda: &CudaFunctions,
1385 funcs: &NV_ENCODE_API_FUNCTION_LIST,
1386 session: *mut c_void,
1387 width: u32,
1388 height: u32,
1389 layout: ConvertLayout,
1390 ) -> Option<Self> {
1391 let module = load_kernels(cuda)?;
1392 let mut kernel: CUfunction = ptr::null_mut();
1393 let mut kernel_tex: CUfunction = ptr::null_mut();
1394 let (linear, texture) = layout.kernels();
1395 if (cuda.cuModuleGetFunction)(&mut kernel, module, linear.as_ptr())
1396 != CUresult::CUDA_SUCCESS
1397 || (cuda.cuModuleGetFunction)(&mut kernel_tex, module, texture.as_ptr())
1398 != CUresult::CUDA_SUCCESS
1399 {
1400 (cuda.cuModuleUnload)(module);
1401 return None;
1402 }
1403 let (mut nv12, mut pitch): (CUdeviceptr, usize) = (0, 0);
1404 let (row_bytes, rows) = layout.extent(width, height);
1409 if (cuda.cuMemAllocPitch_v2)(&mut nv12, &mut pitch, row_bytes, rows, 4)
1410 != CUresult::CUDA_SUCCESS
1411 {
1412 (cuda.cuModuleUnload)(module);
1413 return None;
1414 }
1415 let mut reg = NV_ENC_REGISTER_RESOURCE {
1416 version: sv(NvStruct::RegisterResource),
1417 resourceType: NV_ENC_INPUT_RESOURCE_TYPE::NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR,
1418 width,
1419 height,
1420 resourceToRegister: nv12 as *mut c_void,
1421 pitch: pitch as u32,
1422 bufferFormat: layout.format(),
1423 bufferUsage: NV_ENC_BUFFER_USAGE::NV_ENC_INPUT_IMAGE,
1424 ..Default::default()
1425 };
1426 if (funcs.nvEncRegisterResource.unwrap())(session, &mut reg) != NVENCSTATUS::NV_ENC_SUCCESS
1427 {
1428 (cuda.cuMemFree_v2)(nv12);
1429 (cuda.cuModuleUnload)(module);
1430 return None;
1431 }
1432 let mut map = NV_ENC_MAP_INPUT_RESOURCE {
1433 version: sv(NvStruct::MapInputResource),
1434 registeredResource: reg.registeredResource,
1435 ..Default::default()
1436 };
1437 if (funcs.nvEncMapInputResource.unwrap())(session, &mut map) != NVENCSTATUS::NV_ENC_SUCCESS
1438 {
1439 (funcs.nvEncUnregisterResource.unwrap())(session, reg.registeredResource);
1440 (cuda.cuMemFree_v2)(nv12);
1441 (cuda.cuModuleUnload)(module);
1442 return None;
1443 }
1444 Some(ChromaConvert {
1445 module,
1446 kernel,
1447 kernel_tex,
1448 layout,
1449 surface: nv12,
1450 pitch,
1451 registered: reg.registeredResource,
1452 mapped: map.mappedResource,
1453 })
1454 }
1455
1456 fn input(&self) -> (NV_ENC_INPUT_PTR, NV_ENC_BUFFER_FORMAT) {
1458 (self.mapped, self.layout.format())
1459 }
1460
1461 unsafe fn run(
1465 &self,
1466 cuda: &CudaFunctions,
1467 src: CUdeviceptr,
1468 src_pitch: usize,
1469 width: u32,
1470 height: u32,
1471 swap_rb: bool,
1472 ) -> Result<(), String> {
1473 let (mut src, mut sp) = (src, src_pitch as i32);
1474 let (mut dst, mut dp) = (self.surface, self.pitch as i32);
1475 let (mut w, mut h, mut swap) = (width as i32, height as i32, i32::from(swap_rb));
1476 let mut params: [*mut c_void; 7] = [
1477 &mut src as *mut _ as *mut c_void,
1478 &mut sp as *mut _ as *mut c_void,
1479 &mut dst as *mut _ as *mut c_void,
1480 &mut dp as *mut _ as *mut c_void,
1481 &mut w as *mut _ as *mut c_void,
1482 &mut h as *mut _ as *mut c_void,
1483 &mut swap as *mut _ as *mut c_void,
1484 ];
1485 self.launch(cuda, self.kernel, &mut params, width, height)
1486 }
1487
1488 unsafe fn run_texture(
1491 &self,
1492 cuda: &CudaFunctions,
1493 tex: CUtexObject,
1494 width: u32,
1495 height: u32,
1496 swap_rb: bool,
1497 ) -> Result<(), String> {
1498 let mut tex = tex;
1499 let (mut dst, mut dp) = (self.surface, self.pitch as i32);
1500 let (mut w, mut h, mut swap) = (width as i32, height as i32, i32::from(swap_rb));
1501 let mut params: [*mut c_void; 6] = [
1502 &mut tex as *mut _ as *mut c_void,
1503 &mut dst as *mut _ as *mut c_void,
1504 &mut dp as *mut _ as *mut c_void,
1505 &mut w as *mut _ as *mut c_void,
1506 &mut h as *mut _ as *mut c_void,
1507 &mut swap as *mut _ as *mut c_void,
1508 ];
1509 self.launch(cuda, self.kernel_tex, &mut params, width, height)
1510 }
1511
1512 unsafe fn launch(
1515 &self,
1516 cuda: &CudaFunctions,
1517 kernel: CUfunction,
1518 params: &mut [*mut c_void],
1519 width: u32,
1520 height: u32,
1521 ) -> Result<(), String> {
1522 const BLOCK: u32 = 16;
1523 let grid = (
1524 width.div_ceil(2).div_ceil(BLOCK),
1525 height.div_ceil(2).div_ceil(BLOCK),
1526 );
1527 if (cuda.cuLaunchKernel)(
1528 kernel,
1529 grid.0,
1530 grid.1,
1531 1,
1532 BLOCK,
1533 BLOCK,
1534 1,
1535 0,
1536 ptr::null_mut(),
1537 params.as_mut_ptr(),
1538 ptr::null_mut(),
1539 ) != CUresult::CUDA_SUCCESS
1540 {
1541 return Err("the chroma convert kernel failed to launch".into());
1542 }
1543 Ok(())
1544 }
1545
1546 unsafe fn texture_for(cuda: &CudaFunctions, array: CUarray) -> CUtexObject {
1549 let mut res: CUDA_RESOURCE_DESC = std::mem::zeroed();
1550 res.resType = CUresourcetype::CU_RESOURCE_TYPE_ARRAY;
1551 res.res.array.hArray = array;
1552 let mut sampling: CUDA_TEXTURE_DESC = std::mem::zeroed();
1553 sampling.addressMode = [CUaddress_mode::CU_TR_ADDRESS_MODE_CLAMP; 3];
1554 sampling.filterMode = CUfilter_mode::CU_TR_FILTER_MODE_POINT;
1555 sampling.flags = CU_TRSF_READ_AS_INTEGER;
1556 let mut tex: CUtexObject = 0;
1557 if (cuda.cuTexObjectCreate)(&mut tex, &res, &sampling, ptr::null())
1558 != CUresult::CUDA_SUCCESS
1559 {
1560 return 0;
1561 }
1562 tex
1563 }
1564
1565 unsafe fn release(
1567 &self,
1568 cuda: &CudaFunctions,
1569 funcs: &NV_ENCODE_API_FUNCTION_LIST,
1570 session: *mut c_void,
1571 ) {
1572 (funcs.nvEncUnmapInputResource.unwrap())(session, self.mapped);
1573 (funcs.nvEncUnregisterResource.unwrap())(session, self.registered);
1574 (cuda.cuMemFree_v2)(self.surface);
1575 (cuda.cuModuleUnload)(self.module);
1576 }
1577}
1578
1579struct BandHash {
1582 module: CUmodule,
1583 kernel: CUfunction,
1584 sums: CUdeviceptr,
1585 capacity: usize,
1586}
1587
1588const HASH_THREADS: u32 = 256;
1593const HASH_SEGMENTS: u32 = 4;
1594
1595impl BandHash {
1596 unsafe fn new(cuda: &CudaFunctions) -> Option<Self> {
1597 let module = load_kernels(cuda)?;
1598 let mut kernel: CUfunction = ptr::null_mut();
1599 if (cuda.cuModuleGetFunction)(&mut kernel, module, c"band_hash".as_ptr())
1600 != CUresult::CUDA_SUCCESS
1601 {
1602 (cuda.cuModuleUnload)(module);
1603 return None;
1604 }
1605 Some(Self {
1606 module,
1607 kernel,
1608 sums: 0,
1609 capacity: 0,
1610 })
1611 }
1612
1613 unsafe fn run(
1616 &mut self,
1617 cuda: &CudaFunctions,
1618 src: CUdeviceptr,
1619 pitch: usize,
1620 width: u32,
1621 height: u32,
1622 rows: u32,
1623 ) -> Option<Vec<u64>> {
1624 let words = self.launch(cuda, src, pitch, width, height, rows)?;
1625 self.read(cuda, words)
1626 }
1627
1628 unsafe fn launch(
1631 &mut self,
1632 cuda: &CudaFunctions,
1633 src: CUdeviceptr,
1634 pitch: usize,
1635 width: u32,
1636 height: u32,
1637 rows: u32,
1638 ) -> Option<usize> {
1639 let bands = height.div_ceil(rows.max(1)) as usize;
1640 let words = bands * HASH_SEGMENTS as usize;
1641 if words > self.capacity {
1642 if self.sums != 0 {
1643 (cuda.cuMemFree_v2)(self.sums);
1644 }
1645 self.capacity = 0;
1646 if (cuda.cuMemAlloc_v2)(&mut self.sums, words * 8) != CUresult::CUDA_SUCCESS {
1647 self.sums = 0;
1648 return None;
1649 }
1650 self.capacity = words;
1651 }
1652 let (mut src, mut sp, mut out) = (src, pitch as i32, self.sums);
1653 let (mut w, mut h, mut r) = (width as i32, height as i32, rows.max(1) as i32);
1654 let mut params: [*mut c_void; 6] = [
1655 &mut src as *mut _ as *mut c_void,
1656 &mut sp as *mut _ as *mut c_void,
1657 &mut w as *mut _ as *mut c_void,
1658 &mut h as *mut _ as *mut c_void,
1659 &mut r as *mut _ as *mut c_void,
1660 &mut out as *mut _ as *mut c_void,
1661 ];
1662 ((cuda.cuLaunchKernel)(
1663 self.kernel,
1664 bands as u32,
1665 HASH_SEGMENTS,
1666 1,
1667 HASH_THREADS,
1668 1,
1669 1,
1670 0,
1671 ptr::null_mut(),
1672 params.as_mut_ptr(),
1673 ptr::null_mut(),
1674 ) == CUresult::CUDA_SUCCESS)
1675 .then_some(words)
1676 }
1677
1678 unsafe fn read(&mut self, cuda: &CudaFunctions, words: usize) -> Option<Vec<u64>> {
1680 let mut sums = vec![0u64; words];
1681 ((cuda.cuMemcpyDtoH_v2)(sums.as_mut_ptr() as *mut c_void, self.sums, words * 8)
1682 == CUresult::CUDA_SUCCESS)
1683 .then(|| {
1684 sums.chunks(HASH_SEGMENTS as usize)
1685 .map(|band| band.iter().fold(0u64, |a, &b| a.wrapping_add(b)))
1686 .collect()
1687 })
1688 }
1689
1690 unsafe fn release(self, cuda: &CudaFunctions) {
1691 if self.sums != 0 {
1692 (cuda.cuMemFree_v2)(self.sums);
1693 }
1694 (cuda.cuModuleUnload)(self.module);
1695 }
1696}
1697
1698pub struct NvencEncoder {
1724 encoder_session: *mut c_void,
1725 cuda_context: CUcontext,
1726 cuda_device: CUdevice,
1727 device_name: String,
1729 egl_display: EGLDisplay,
1730 codec: Codec,
1731 fullcolor: bool,
1732 bit_depth: u32,
1734 asked: (bool, bool),
1738 width: u32,
1739 height: u32,
1740 current_qp: u32,
1741 peak: Option<PeakBucket>,
1744 open_rc: NV_ENC_RC_PARAMS,
1745 last_avg_qp: u32,
1747 last_capped: bool,
1750 deferred: Option<crate::ledger::BlockPlan>,
1751 refused_bind: Option<u32>,
1753 held_qp: Option<u32>,
1757 held_band: Option<(f64, f64)>,
1758 slice_layout: (u32, u32),
1761 hold_refused: bool,
1762 band_log: VecDeque<(u64, f64, f64)>,
1766 lost_cleanup: bool,
1767 cap: Option<usize>,
1770 refit: Option<f64>,
1771 held_bytes: usize,
1773 qp_map: Vec<i8>,
1774 refinement: Option<crate::ledger::Refinement>,
1778 delta_mapped: bool,
1779 refine_refused: bool,
1780 last_quality: Option<u32>,
1783 last_bytes: Option<usize>,
1785 encode_config: NV_ENC_CONFIG,
1786 init_params: NV_ENC_INITIALIZE_PARAMS,
1787 input_device_ptr: CUdeviceptr,
1788 input_pitch: usize,
1789 input_format: NV_ENC_BUFFER_FORMAT,
1790 registered_input_resource: NV_ENC_REGISTERED_PTR,
1791 mapped_input_buffer: NV_ENC_INPUT_PTR,
1792 bitstream_buffers: Vec<NV_ENC_OUTPUT_PTR>,
1793 current_buffer_idx: usize,
1794 dmabuf_cache: HashMap<i32, CachedDmaBuf>,
1795 external_inputs: Vec<ExternalInput>,
1796 pinned_hosts: HashMap<usize, usize>,
1797 cuda: Arc<CudaFunctions>,
1798 egl: Option<Arc<EglFunctions>>,
1800 _nvenc_lib: Arc<NvencLibrary>,
1801 nvenc_funcs: NV_ENCODE_API_FUNCTION_LIST,
1802 omit_stripe_headers: bool,
1803 node_index: i32,
1804 pin_uploads: bool,
1807 direct_dmabuf: bool,
1810 csc: Option<ChromaConvert>,
1813 dpb: u32,
1816 engines: Option<i32>,
1818 references: Option<ReferenceWindow>,
1822 last_reference: Reference,
1823 frame_num: WideFrameNum,
1827 blit_semaphore: CUexternalSemaphore,
1830 band_hash: Option<Option<BandHash>>,
1833 upload_hash: Option<u32>,
1837 upload_words: Option<usize>,
1838 upload_hashes: Option<Vec<u64>>,
1839}
1840
1841unsafe impl Send for NvencEncoder {}
1842
1843impl Drop for NvencEncoder {
1860 fn drop(&mut self) {
1861 unsafe {
1862 let _ = (self.cuda.cuCtxPushCurrent_v2)(self.cuda_context);
1863
1864 self.unmap_external_inputs();
1865 if let Some(csc) = self.csc.take() {
1866 csc.release(&self.cuda, &self.nvenc_funcs, self.encoder_session);
1867 }
1868 if let Some(Some(hash)) = self.band_hash.take() {
1869 hash.release(&self.cuda);
1870 }
1871 if !self.mapped_input_buffer.is_null() {
1872 (self.nvenc_funcs.nvEncUnmapInputResource.unwrap())(
1873 self.encoder_session,
1874 self.mapped_input_buffer,
1875 );
1876 }
1877 if !self.registered_input_resource.is_null() {
1878 (self.nvenc_funcs.nvEncUnregisterResource.unwrap())(
1879 self.encoder_session,
1880 self.registered_input_resource,
1881 );
1882 }
1883 if self.input_device_ptr != 0 {
1884 (self.cuda.cuMemFree_v2)(self.input_device_ptr);
1885 }
1886
1887 for &bs in &self.bitstream_buffers {
1888 (self.nvenc_funcs.nvEncDestroyBitstreamBuffer.unwrap())(self.encoder_session, bs);
1889 }
1890
1891 let imports: Vec<CachedDmaBuf> = self.dmabuf_cache.drain().map(|(_, c)| c).collect();
1892 for cache in imports {
1893 self.release_dmabuf_import(cache);
1894 }
1895
1896 for (base, len) in &self.pinned_hosts {
1897 if *len > 0 {
1898 (self.cuda.cuMemHostUnregister)(*base as *mut c_void);
1899 }
1900 }
1901
1902 if !self.blit_semaphore.is_null()
1903 && let Some(destroy) = self.cuda.cuDestroyExternalSemaphore
1904 {
1905 destroy(self.blit_semaphore);
1906 }
1907
1908 if !self.encoder_session.is_null() {
1909 (self.nvenc_funcs.nvEncDestroyEncoder.unwrap())(self.encoder_session);
1910 }
1911
1912 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
1913 (self.cuda.cuDevicePrimaryCtxRelease_v2)(self.cuda_device);
1914 }
1915 }
1916}
1917
1918fn keep_primary_context(device: CUdevice) {
1925 static KEPT: std::sync::Mutex<Vec<CUdevice>> = std::sync::Mutex::new(Vec::new());
1926 static CUDA: std::sync::OnceLock<Option<CudaFunctions>> = std::sync::OnceLock::new();
1927 let mut kept = KEPT.lock().unwrap_or_else(|e| e.into_inner());
1928 if kept.contains(&device) {
1929 return;
1930 }
1931 let Some(cuda) = CUDA.get_or_init(|| NvencEncoder::load_cuda().ok()) else {
1932 return;
1933 };
1934 let mut context: CUcontext = ptr::null_mut();
1935 if unsafe { (cuda.cuDevicePrimaryCtxRetain)(&mut context, device) } == CUresult::CUDA_SUCCESS {
1936 kept.push(device);
1937 }
1938}
1939
1940fn nvenc_level(
1963 codec: Codec,
1964 width: u32,
1965 height: u32,
1966 fps: u32,
1967 bitrate_bps: u64,
1968 hevc_high_tier: bool,
1969) -> u32 {
1970 let fps = fps.max(HEADROOM_FPS);
1971 match codec {
1972 Codec::Av1 => av1_level(
1973 width.max(HEADROOM_WIDTH),
1974 height.max(HEADROOM_HEIGHT),
1975 fps,
1976 bitrate_bps,
1977 ),
1978 Codec::H265 => h265_level(
1979 width.next_multiple_of(32),
1980 height.next_multiple_of(32),
1981 fps,
1982 bitrate_bps,
1983 hevc_high_tier,
1984 ),
1985 _ => h264_level(width, height, fps, bitrate_bps),
1986 }
1987}
1988
1989impl NvencEncoder {
1990 fn load_egl() -> Result<EglFunctions, String> {
1997 unsafe {
1998 let lib_name = "libEGL.so.1";
1999 let lib = Library::new(lib_name)
2000 .or_else(|_| Library::new("libEGL.so"))
2001 .map_err(|e| format!("Could not load EGL library: {}", e))?;
2002
2003 let get_proc_addr_sym: Symbol<unsafe extern "C" fn(*const c_char) -> *mut c_void> = lib
2004 .get(b"eglGetProcAddress\0")
2005 .map_err(|e| format!("Missing symbol eglGetProcAddress: {}", e))?;
2006
2007 let eglGetProcAddress = *get_proc_addr_sym;
2008
2009 let load_extension = |name: &str| -> Result<*mut c_void, String> {
2010 let c_name = CString::new(name).unwrap();
2011 let addr = eglGetProcAddress(c_name.as_ptr());
2012 if addr.is_null() {
2013 Err(format!("EGL Extension not found: {}", name))
2014 } else {
2015 Ok(addr)
2016 }
2017 };
2018
2019 let create_addr = load_extension("eglCreateImageKHR")?;
2020 let destroy_addr = load_extension("eglDestroyImageKHR")?;
2021
2022 Ok(EglFunctions {
2023 _lib: lib,
2024 eglGetProcAddress,
2025 eglCreateImageKHR: std::mem::transmute::<*mut c_void, EglCreateImageKhrFn>(
2026 create_addr,
2027 ),
2028 eglDestroyImageKHR: std::mem::transmute::<*mut c_void, EglDestroyImageKhrFn>(
2029 destroy_addr,
2030 ),
2031 })
2032 }
2033 }
2034
2035 fn load_cuda() -> Result<CudaFunctions, String> {
2041 unsafe {
2042 let lib_name = if cfg!(windows) {
2043 "nvcuda.dll"
2044 } else {
2045 "libcuda.so.1"
2046 };
2047 let lib = Library::new(lib_name)
2048 .map_err(|e| format!("Could not load CUDA library ({}): {}", lib_name, e))?;
2049
2050 macro_rules! load {
2051 ($lib:expr, $name:expr) => {
2052 *$lib.get($name).map_err(|e| {
2053 format!(
2054 "Missing symbol {}: {}",
2055 std::str::from_utf8($name).unwrap(),
2056 e
2057 )
2058 })?
2059 };
2060 }
2061
2062 Ok(CudaFunctions {
2063 cuInit: load!(lib, b"cuInit\0"),
2064 cuDeviceGet: load!(lib, b"cuDeviceGet\0"),
2065 cuDeviceGetByPCIBusId: load!(lib, b"cuDeviceGetByPCIBusId\0"),
2066 cuDevicePrimaryCtxRetain: load!(lib, b"cuDevicePrimaryCtxRetain\0"),
2067 cuCtxPushCurrent_v2: load!(lib, b"cuCtxPushCurrent_v2\0"),
2068 cuCtxPopCurrent_v2: load!(lib, b"cuCtxPopCurrent_v2\0"),
2069 cuDevicePrimaryCtxRelease_v2: load!(lib, b"cuDevicePrimaryCtxRelease_v2\0"),
2070 cuMemAlloc_v2: load!(lib, b"cuMemAlloc_v2\0"),
2071 cuMemAllocPitch_v2: load!(lib, b"cuMemAllocPitch_v2\0"),
2072 cuMemFree_v2: load!(lib, b"cuMemFree_v2\0"),
2073 cuMemcpyHtoD_v2: load!(lib, b"cuMemcpyHtoD_v2\0"),
2074 cuMemcpyDtoH_v2: load!(lib, b"cuMemcpyDtoH_v2\0"),
2075 cuMemcpy2D_v2: load!(lib, b"cuMemcpy2D_v2\0"),
2076 cuMemcpy2DAsync_v2: load!(lib, b"cuMemcpy2DAsync_v2\0"),
2077 cuStreamSynchronize: load!(lib, b"cuStreamSynchronize\0"),
2078 cuModuleLoadData: load!(lib, b"cuModuleLoadData\0"),
2079 cuModuleGetFunction: load!(lib, b"cuModuleGetFunction\0"),
2080 cuModuleUnload: load!(lib, b"cuModuleUnload\0"),
2081 cuTexObjectCreate: load!(lib, b"cuTexObjectCreate\0"),
2082 cuTexObjectDestroy: load!(lib, b"cuTexObjectDestroy\0"),
2083 cuLaunchKernel: load!(lib, b"cuLaunchKernel\0"),
2084 cuMemHostRegister_v2: load!(lib, b"cuMemHostRegister_v2\0"),
2085 cuMemHostUnregister: load!(lib, b"cuMemHostUnregister\0"),
2086 cuGraphicsEGLRegisterImage: load!(lib, b"cuGraphicsEGLRegisterImage\0"),
2087 cuGraphicsUnregisterResource: load!(lib, b"cuGraphicsUnregisterResource\0"),
2088 cuGraphicsResourceGetMappedEglFrame: load!(
2089 lib,
2090 b"cuGraphicsResourceGetMappedEglFrame\0"
2091 ),
2092 cuDeviceGetCount: load!(lib, b"cuDeviceGetCount\0"),
2093 cuDeviceGetName: load!(lib, b"cuDeviceGetName\0"),
2094 cuDeviceGetUuid: load!(lib, b"cuDeviceGetUuid\0"),
2095 cuGetErrorName: load!(lib, b"cuGetErrorName\0"),
2096 cuImportExternalSemaphore: lib.get(b"cuImportExternalSemaphore\0").ok().map(|s| *s),
2097 cuWaitExternalSemaphoresAsync: lib
2098 .get(b"cuWaitExternalSemaphoresAsync\0")
2099 .ok()
2100 .map(|s| *s),
2101 cuDestroyExternalSemaphore: lib
2102 .get(b"cuDestroyExternalSemaphore\0")
2103 .ok()
2104 .map(|s| *s),
2105 _lib: lib,
2106 })
2107 }
2108 }
2109
2110 fn load_nvenc() -> Result<NvencLibrary, String> {
2116 unsafe {
2117 let lib_name = NVENC_DLL_NAME;
2118 let lib = Library::new(lib_name)
2119 .map_err(|e| format!("Could not load NVENC library ({}): {}", lib_name, e))?;
2120
2121 let create_instance = *lib
2122 .get(NV_ENCODE_API_CREATE_INSTANCE_FN_NAME)
2123 .map_err(|e| e.to_string())?;
2124 let get_max_version = lib
2125 .get::<NvEncodeApiGetMaxSupportedVersionFn>(
2126 NV_ENCODE_API_GET_MAX_SUPPORTED_VERSION_FN_NAME,
2127 )
2128 .map(|s| *s)
2129 .ok();
2130 Ok(NvencLibrary {
2131 create_instance,
2132 get_max_version,
2133 _lib: lib,
2134 })
2135 }
2136 }
2137
2138 unsafe fn get_error_string(cuda: &CudaFunctions, err: CUresult) -> String {
2142 let mut p_str: *const c_char = ptr::null();
2143 if (cuda.cuGetErrorName)(err, &mut p_str) == CUresult::CUDA_SUCCESS && !p_str.is_null() {
2144 CStr::from_ptr(p_str).to_string_lossy().into_owned()
2145 } else {
2146 format!("Unknown CUDA Error ({})", err.0)
2147 }
2148 }
2149
2150 unsafe fn probe_devices(cuda: &CudaFunctions) {
2153 let mut count = 0;
2154 if (cuda.cuDeviceGetCount)(&mut count) != CUresult::CUDA_SUCCESS {
2155 return;
2156 }
2157 crate::log::debug!("[NVENC] Found {} CUDA devices:", count);
2158 for i in 0..count {
2159 let mut dev = 0;
2160 (cuda.cuDeviceGet)(&mut dev, i);
2161 crate::log::debug!(
2162 "[NVENC] Device {}: {}",
2163 i,
2164 Self::device_name_of(cuda, dev)
2165 );
2166 }
2167 }
2168
2169 unsafe fn device_name_of(cuda: &CudaFunctions, dev: CUdevice) -> String {
2171 let mut name_buf = [0 as c_char; 256];
2172 if (cuda.cuDeviceGetName)(name_buf.as_mut_ptr(), 256, dev) != CUresult::CUDA_SUCCESS {
2173 return format!("CUDA device {dev}");
2174 }
2175 CStr::from_ptr(name_buf.as_ptr())
2176 .to_string_lossy()
2177 .into_owned()
2178 }
2179
2180 fn get_pci_bus_id(render_index: i32) -> Option<String> {
2183 let path = format!("/sys/class/drm/renderD{}/device", 128 + render_index);
2184 if let Ok(target) = std::fs::read_link(&path)
2185 && let Some(name) = target.file_name()
2186 && let Some(name_str) = name.to_str()
2187 {
2188 return Some(name_str.to_string());
2189 }
2190 None
2191 }
2192
2193 pub fn new(settings: &CaptureSettings, egl_display: *const c_void) -> Result<Self, String> {
2247 Self::new_tuned(settings, egl_display, NvencTuning::default())
2248 }
2249
2250 pub(crate) fn new_tuned(
2253 settings: &CaptureSettings,
2254 egl_display: *const c_void,
2255 tuning: NvencTuning,
2256 ) -> Result<Self, String> {
2257 let codec = settings.codec;
2258 let codec_guid =
2259 codec_guid(codec).ok_or_else(|| format!("NVENC has no {} encoder", codec.display()))?;
2260 crate::log::debug!("[NVENC] Initializing {}...", codec.display());
2261
2262 let egl = if egl_display.is_null() {
2263 None
2264 } else {
2265 Some(Arc::new(Self::load_egl()?))
2266 };
2267 let cuda = Arc::new(Self::load_cuda()?);
2268 let nvenc_lib = Arc::new(Self::load_nvenc()?);
2269 nvenc_negotiate(&nvenc_lib);
2270
2271 crate::nvgpufilter::install();
2272
2273 static LEAK_ONCE: std::sync::Once = std::sync::Once::new();
2274 LEAK_ONCE.call_once(|| {
2275 std::mem::forget(egl.clone());
2276 std::mem::forget(cuda.clone());
2277 std::mem::forget(nvenc_lib.clone());
2278 });
2279
2280 unsafe {
2281 let res = (cuda.cuInit)(0);
2282 if res != CUresult::CUDA_SUCCESS {
2283 return Err(format!(
2284 "Init CUDA failed: {}",
2285 Self::get_error_string(&cuda, res)
2286 ));
2287 }
2288
2289 Self::probe_devices(&cuda);
2290
2291 let mut cu_device: CUdevice = 0;
2292 let mut device_found = false;
2293
2294 if let Some(pci_bus_id) = Self::get_pci_bus_id(settings.encode_node_index.max(0)) {
2295 let c_pci_bus_id = CString::new(pci_bus_id.clone()).unwrap();
2296 if (cuda.cuDeviceGetByPCIBusId)(&mut cu_device, c_pci_bus_id.as_ptr())
2297 == CUresult::CUDA_SUCCESS
2298 {
2299 crate::log::debug!(
2300 "[NVENC] Bound to CUDA device via PCI Bus ID: {}",
2301 pci_bus_id
2302 );
2303 device_found = true;
2304 }
2305 }
2306
2307 if !device_found {
2308 let res = (cuda.cuDeviceGet)(&mut cu_device, 0);
2309 if res != CUresult::CUDA_SUCCESS {
2310 return Err("Failed to get default CUDA device".into());
2311 }
2312 }
2313 let device_name = Self::device_name_of(&cuda, cu_device);
2314
2315 let mut cu_context: CUcontext = ptr::null_mut();
2321 let res = (cuda.cuDevicePrimaryCtxRetain)(&mut cu_context, cu_device);
2322 if res != CUresult::CUDA_SUCCESS {
2323 return Err("Failed to retain the device's primary CUDA context".into());
2324 }
2325 if (cuda.cuCtxPushCurrent_v2)(cu_context) != CUresult::CUDA_SUCCESS {
2326 (cuda.cuDevicePrimaryCtxRelease_v2)(cu_device);
2327 return Err("Failed to make the primary CUDA context current".into());
2328 }
2329
2330 let width = settings.width as u32;
2331 let height = settings.height as u32;
2332 let mut input_device_ptr: CUdeviceptr = 0;
2333 let mut input_pitch: usize = 0;
2334
2335 let res = (cuda.cuMemAllocPitch_v2)(
2336 &mut input_device_ptr,
2337 &mut input_pitch,
2338 (width * 4) as usize,
2339 height as usize,
2340 16,
2341 );
2342 if res != CUresult::CUDA_SUCCESS {
2343 (cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
2344 (cuda.cuDevicePrimaryCtxRelease_v2)(cu_device);
2345 return Err("Failed to allocate ARGB input buffer on GPU".into());
2346 }
2347
2348 let mut function_list = NV_ENCODE_API_FUNCTION_LIST {
2349 version: sv(NvStruct::FunctionList),
2350 ..Default::default()
2351 };
2352 if (nvenc_lib.create_instance)(&mut function_list) != NVENCSTATUS::NV_ENC_SUCCESS {
2353 (cuda.cuMemFree_v2)(input_device_ptr);
2354 (cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
2355 (cuda.cuDevicePrimaryCtxRelease_v2)(cu_device);
2356 return Err("NvEncodeAPICreateInstance failed".into());
2357 }
2358
2359 let mut session_params = NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS {
2360 version: sv(NvStruct::OpenSessionExParams),
2361 deviceType: NV_ENC_DEVICE_TYPE::NV_ENC_DEVICE_TYPE_CUDA,
2362 device: cu_context as *mut c_void,
2363 apiVersion: neg_api(),
2364 ..Default::default()
2365 };
2366
2367 let mut encoder_session: *mut c_void = ptr::null_mut();
2368 let open_fn = function_list.nvEncOpenEncodeSessionEx.unwrap();
2369 let status = open_fn(&mut session_params, &mut encoder_session);
2370 if status != NVENCSTATUS::NV_ENC_SUCCESS {
2371 (cuda.cuMemFree_v2)(input_device_ptr);
2372 (cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
2373 (cuda.cuDevicePrimaryCtxRelease_v2)(cu_device);
2374 return Err(session_refusal(status));
2375 }
2376
2377 if !Self::device_encodes(&function_list, encoder_session, &codec_guid) {
2380 (function_list.nvEncDestroyEncoder.unwrap())(encoder_session);
2381 (cuda.cuMemFree_v2)(input_device_ptr);
2382 (cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
2383 (cuda.cuDevicePrimaryCtxRelease_v2)(cu_device);
2384 return Err(format!(
2385 "this GPU's NVENC has no {} engine",
2386 codec.display()
2387 ));
2388 }
2389
2390 let caps_444 = if codec.fullcolor() {
2394 query_cap(
2395 &function_list,
2396 encoder_session,
2397 codec_guid,
2398 NV_ENC_CAPS::NV_ENC_CAPS_SUPPORT_YUV444_ENCODE,
2399 )
2400 } else {
2401 Some(0)
2402 };
2403 let caps_wmax = query_cap(
2404 &function_list,
2405 encoder_session,
2406 codec_guid,
2407 NV_ENC_CAPS::NV_ENC_CAPS_WIDTH_MAX,
2408 );
2409 let caps_hmax = query_cap(
2410 &function_list,
2411 encoder_session,
2412 codec_guid,
2413 NV_ENC_CAPS::NV_ENC_CAPS_HEIGHT_MAX,
2414 );
2415 let caps = decide_caps(
2416 settings.video_fullcolor,
2417 width as i32,
2418 height as i32,
2419 caps_444,
2420 caps_wmax,
2421 caps_hmax,
2422 );
2423 if let Some((mw, mh)) = caps.too_large {
2424 (function_list.nvEncDestroyEncoder.unwrap())(encoder_session);
2425 (cuda.cuMemFree_v2)(input_device_ptr);
2426 (cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
2427 (cuda.cuDevicePrimaryCtxRelease_v2)(cu_device);
2428 return Err(format!(
2429 "NVENC maximum encode size {mw}x{mh} exceeded by {width}x{height}; using software"
2430 ));
2431 }
2432 if caps.downgraded_color && super::software_fullcolor(codec) {
2433 (function_list.nvEncDestroyEncoder.unwrap())(encoder_session);
2434 (cuda.cuMemFree_v2)(input_device_ptr);
2435 (cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
2436 (cuda.cuDevicePrimaryCtxRelease_v2)(cu_device);
2437 return Err(format!(
2438 "this GPU's NVENC engine encodes no {} 4:4:4, which the software encoder does",
2439 codec.display()
2440 ));
2441 }
2442 if caps.downgraded_color {
2443 eprintln!(
2444 "[NVENC] {} 4:4:4 (YUV444) encoding unsupported on this GPU; encoding 4:2:0.",
2445 codec.display()
2446 );
2447 }
2448 if tuning.temporal_aq
2449 && query_cap(
2450 &function_list,
2451 encoder_session,
2452 codec_guid,
2453 NV_ENC_CAPS::NV_ENC_CAPS_SUPPORT_TEMPORAL_AQ,
2454 ) == Some(0)
2455 {
2456 (function_list.nvEncDestroyEncoder.unwrap())(encoder_session);
2457 (cuda.cuMemFree_v2)(input_device_ptr);
2458 (cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
2459 (cuda.cuDevicePrimaryCtxRelease_v2)(cu_device);
2460 return Err(format!(
2461 "{} temporal AQ is not supported by this GPU",
2462 codec.display()
2463 ));
2464 }
2465
2466 let is_444 = caps.fullcolor;
2467 let bit_depth = if settings.video_bit_depth >= 10
2468 && codes_ten_bit(&function_list, encoder_session, codec, codec_guid)
2469 {
2470 10
2471 } else {
2472 8
2473 };
2474 let invalidation = query_cap(
2475 &function_list,
2476 encoder_session,
2477 codec_guid,
2478 NV_ENC_CAPS::NV_ENC_CAPS_SUPPORT_REF_PIC_INVALIDATION,
2479 ) == Some(1);
2480
2481 let mut config = NV_ENC_CONFIG {
2482 version: sv(NvStruct::Config),
2483 ..Default::default()
2484 };
2485 let mut preset_config = NV_ENC_PRESET_CONFIG {
2486 version: sv(NvStruct::PresetConfig),
2487 presetCfg: config,
2488 ..Default::default()
2489 };
2490
2491 let get_preset_ex = function_list.nvEncGetEncodePresetConfigEx.unwrap();
2492 let preset_status = get_preset_ex(
2493 encoder_session,
2494 codec_guid,
2495 tuning.preset,
2496 NV_ENC_TUNING_INFO::NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY,
2497 &mut preset_config,
2498 );
2499 if preset_status != NVENCSTATUS::NV_ENC_SUCCESS {
2500 eprintln!(
2501 "[NVENC] nvEncGetEncodePresetConfigEx failed ({preset_status:?}): {}",
2502 last_error(&function_list, encoder_session)
2503 );
2504 }
2505
2506 config = preset_config.presetCfg;
2507 config.version = sv(NvStruct::Config);
2508 config.profileGUID = profile_guid(codec, is_444, bit_depth);
2509 if settings.video_cbr_mode {
2510 let bps = cbr_bps(settings);
2511 log_held_rate(settings, bps);
2512 config.rcParams.rateControlMode = NV_ENC_PARAMS_RC_MODE::NV_ENC_PARAMS_RC_CBR;
2513 config.rcParams.multiPass = if tuning.multipass
2514 == NV_ENC_MULTI_PASS::NV_ENC_TWO_PASS_QUARTER_RESOLUTION
2515 && NVENC_SINGLE_PASS.load(Ordering::Relaxed)
2516 {
2517 NV_ENC_MULTI_PASS::NV_ENC_MULTI_PASS_DISABLED
2518 } else {
2519 tuning.multipass
2520 };
2521 set_cbr_rate(&mut config.rcParams, bps, cbr_vbv(settings, bps));
2522 let lo = codec.hardware_quantizer_bound(Hardware::Nvenc, settings.video_min_qp);
2523 if lo > 0 {
2524 config.rcParams.set_enableMinQP(1);
2525 config.rcParams.minQP.qpInterP = lo;
2526 config.rcParams.minQP.qpInterB = lo;
2527 config.rcParams.minQP.qpIntra = lo;
2528 }
2529 let hi = codec.hardware_quantizer_bound(Hardware::Nvenc, settings.video_max_qp);
2530 if hi > 0 {
2531 config.rcParams.set_enableMaxQP(1);
2532 config.rcParams.maxQP.qpInterP = hi;
2533 config.rcParams.maxQP.qpInterB = hi;
2534 config.rcParams.maxQP.qpIntra = hi;
2535 }
2536 } else {
2537 let q = codec.hardware_quantizer(Hardware::Nvenc, settings.video_crf);
2538 config.rcParams.rateControlMode = NV_ENC_PARAMS_RC_MODE::NV_ENC_PARAMS_RC_CONSTQP;
2539 config.rcParams.constQP.qpInterP = q;
2540 config.rcParams.constQP.qpInterB = q;
2541 config.rcParams.constQP.qpIntra = q;
2542 }
2543 config.rcParams.set_enableAQ(u32::from(tuning.spatial_aq));
2544 config
2545 .rcParams
2546 .set_enableTemporalAQ(u32::from(tuning.temporal_aq));
2547 config.frameIntervalP = 1;
2548 config.gopLength = 0xFFFFFFFF;
2549 config.rcParams.set_zeroReorderDelay(1);
2550 config.rcParams.set_strictGOPTarget(1);
2551 config.rcParams.set_enableLookahead(0);
2552 config.rcParams.lookaheadDepth = 0;
2553 let rate = FrameRate::of(settings.target_fps);
2554 let level_at = |weighted| {
2555 nvenc_level(
2556 codec,
2557 width,
2558 height,
2559 rate.ceil(),
2560 level_rate(codec, u64::from(config.rcParams.maxBitRate), weighted),
2561 tuning.hevc_high_tier,
2562 )
2563 };
2564 let level = level_at(NVENC_AV1_WEIGHTED.load(Ordering::Relaxed));
2565 let weighted_level = level_at(true);
2566 let dpb = match codec {
2567 Codec::H265 => h265_dpb_frames(level, width, height),
2568 Codec::Av1 => AV1_REFERENCES,
2569 _ => h264_dpb_frames(level, width, height),
2570 };
2571 let dpb = if codec == Codec::Av1 {
2572 dpb
2573 } else {
2574 dpb.min(crate::encoders::reference_frames(settings))
2575 };
2576 let ltr = query_cap(
2577 &function_list,
2578 encoder_session,
2579 codec_guid,
2580 NV_ENC_CAPS::NV_ENC_CAPS_NUM_MAX_LTR_FRAMES,
2581 );
2582 let anchors = anchor_count(
2583 codec,
2584 nvenc_cur_ver().0,
2585 invalidation,
2586 ltr,
2587 dpb,
2588 settings.acknowledge_references,
2589 );
2590 Self::configure_codec(
2591 &mut config,
2592 codec,
2593 is_444,
2594 bit_depth,
2595 level,
2596 dpb,
2597 anchors,
2598 &tuning,
2599 );
2600 #[cfg(test)]
2601 if let Some(refs) = tuning.ref_l0 {
2602 match codec {
2603 Codec::H265 => config.encodeCodecConfig.hevcConfig.numRefL0 = refs,
2604 Codec::Av1 => config.encodeCodecConfig.av1Config.numFwdRefs = refs,
2605 _ => config.encodeCodecConfig.h264Config.numRefL0 = refs,
2606 }
2607 }
2608 let engines = query_cap(
2609 &function_list,
2610 encoder_session,
2611 codec_guid,
2612 NV_ENC_CAPS::NV_ENC_CAPS_NUM_ENCODER_ENGINES,
2613 );
2614 let split = split_mode(codec, width, height, engines, nvenc_cur_ver());
2615 #[cfg(test)]
2616 let split = tuning.split.unwrap_or(split);
2617
2618 let mut init_params = NV_ENC_INITIALIZE_PARAMS {
2619 version: sv(NvStruct::InitializeParams),
2620 encodeGUID: codec_guid,
2621 presetGUID: tuning.preset,
2622 tuningInfo: NV_ENC_TUNING_INFO::NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY,
2623 encodeWidth: width,
2624 encodeHeight: height,
2625 darWidth: width,
2626 darHeight: height,
2627 frameRateNum: rate.num,
2628 frameRateDen: rate.den,
2629 enablePTD: 1,
2630 encodeConfig: &mut config,
2631 maxEncodeWidth: nvenc_headroom(width, HEADROOM_WIDTH, caps_wmax),
2632 maxEncodeHeight: nvenc_headroom(height, HEADROOM_HEIGHT, caps_hmax),
2633 ..Default::default()
2634 };
2635 init_params.set_splitEncodeMode(split as u32);
2636
2637 let init_fn = function_list.nvEncInitializeEncoder.unwrap();
2638 let mut headroom_status =
2639 init_fn(encoder_session, &mut Negotiated::new(init_params).value);
2640 if headroom_status != NVENCSTATUS::NV_ENC_SUCCESS && weighted_level != level {
2641 (function_list.nvEncDestroyEncoder.unwrap())(encoder_session);
2645 encoder_session = ptr::null_mut();
2646 if open_fn(&mut session_params, &mut encoder_session) != NVENCSTATUS::NV_ENC_SUCCESS
2647 {
2648 (cuda.cuMemFree_v2)(input_device_ptr);
2649 (cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
2650 (cuda.cuDevicePrimaryCtxRelease_v2)(cu_device);
2651 return Err("Failed to reopen the AV1 session at the weighted level".into());
2652 }
2653 config.encodeCodecConfig.av1Config.level = weighted_level;
2654 init_params.encodeConfig = &mut config;
2655 headroom_status = init_fn(encoder_session, &mut Negotiated::new(init_params).value);
2656 if headroom_status == NVENCSTATUS::NV_ENC_SUCCESS {
2657 NVENC_AV1_WEIGHTED.store(true, Ordering::Relaxed);
2658 eprintln!(
2659 "[NVENC] The driver refused AV1 level {level} at {} kbps; declaring {weighted_level}",
2660 config.rcParams.maxBitRate / 1000
2661 );
2662 }
2663 }
2664 if headroom_status == NVENCSTATUS::NV_ENC_ERR_INVALID_PARAM
2665 && config.rcParams.multiPass
2666 == NV_ENC_MULTI_PASS::NV_ENC_TWO_PASS_QUARTER_RESOLUTION
2667 {
2668 (function_list.nvEncDestroyEncoder.unwrap())(encoder_session);
2669 encoder_session = ptr::null_mut();
2670 if open_fn(&mut session_params, &mut encoder_session) != NVENCSTATUS::NV_ENC_SUCCESS
2671 {
2672 (cuda.cuMemFree_v2)(input_device_ptr);
2673 (cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
2674 (cuda.cuDevicePrimaryCtxRelease_v2)(cu_device);
2675 return Err("Failed to reopen the session for a single pass".into());
2676 }
2677 config.rcParams.multiPass = NV_ENC_MULTI_PASS::NV_ENC_MULTI_PASS_DISABLED;
2678 init_params.encodeConfig = &mut config;
2679 headroom_status = init_fn(encoder_session, &mut Negotiated::new(init_params).value);
2680 if headroom_status == NVENCSTATUS::NV_ENC_SUCCESS
2681 && !NVENC_SINGLE_PASS.swap(true, Ordering::Relaxed)
2682 {
2683 eprintln!(
2684 "[NVENC] The driver refused the quarter-resolution first pass; coding CBR in a single pass"
2685 );
2686 }
2687 }
2688 if headroom_status != NVENCSTATUS::NV_ENC_SUCCESS {
2689 eprintln!(
2690 "[NVENC] Init with {}x{} resize headroom failed ({headroom_status:?}): {}",
2691 init_params.maxEncodeWidth,
2692 init_params.maxEncodeHeight,
2693 last_error(&function_list, encoder_session)
2694 );
2695 (function_list.nvEncDestroyEncoder.unwrap())(encoder_session);
2698 encoder_session = ptr::null_mut();
2699 let reopened = open_fn(&mut session_params, &mut encoder_session)
2700 == NVENCSTATUS::NV_ENC_SUCCESS;
2701 init_params.maxEncodeWidth = width;
2702 init_params.maxEncodeHeight = height;
2703 init_params.encodeConfig = &mut config;
2704 let exact_status = if reopened {
2705 init_fn(encoder_session, &mut Negotiated::new(init_params).value)
2706 } else {
2707 NVENCSTATUS::NV_ENC_ERR_NO_ENCODE_DEVICE
2708 };
2709 if exact_status != NVENCSTATUS::NV_ENC_SUCCESS {
2710 let detail = if reopened {
2711 let d = last_error(&function_list, encoder_session);
2712 (function_list.nvEncDestroyEncoder.unwrap())(encoder_session);
2713 d
2714 } else {
2715 "could not reopen the session".to_string()
2716 };
2717 (cuda.cuMemFree_v2)(input_device_ptr);
2718 (cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
2719 (cuda.cuDevicePrimaryCtxRelease_v2)(cu_device);
2720 return Err(format!(
2721 "Failed to initialize {} encoder at {width}x{height} ({exact_status:?}): {detail}",
2722 codec.display()
2723 ));
2724 }
2725 eprintln!("[NVENC] Running without resize headroom.");
2726 }
2727
2728 init_params.encodeConfig = ptr::null_mut();
2729
2730 let mut reg_res = NV_ENC_REGISTER_RESOURCE {
2731 version: sv(NvStruct::RegisterResource),
2732 resourceType: NV_ENC_INPUT_RESOURCE_TYPE::NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR,
2733 width,
2734 height,
2735 resourceToRegister: input_device_ptr as *mut c_void,
2736 pitch: input_pitch as u32,
2737 bufferFormat: NV_ENC_BUFFER_FORMAT::NV_ENC_BUFFER_FORMAT_ARGB,
2738 bufferUsage: NV_ENC_BUFFER_USAGE::NV_ENC_INPUT_IMAGE,
2739 ..Default::default()
2740 };
2741
2742 let register_fn = function_list.nvEncRegisterResource.unwrap();
2743 if register_fn(encoder_session, &mut reg_res) != NVENCSTATUS::NV_ENC_SUCCESS {
2744 (function_list.nvEncDestroyEncoder.unwrap())(encoder_session);
2745 (cuda.cuMemFree_v2)(input_device_ptr);
2746 (cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
2747 (cuda.cuDevicePrimaryCtxRelease_v2)(cu_device);
2748 return Err("Failed to register input buffer".into());
2749 }
2750
2751 let mut map_params = NV_ENC_MAP_INPUT_RESOURCE {
2752 version: sv(NvStruct::MapInputResource),
2753 registeredResource: reg_res.registeredResource,
2754 ..Default::default()
2755 };
2756 let map_fn = function_list.nvEncMapInputResource.unwrap();
2757 if map_fn(encoder_session, &mut map_params) != NVENCSTATUS::NV_ENC_SUCCESS {
2758 (function_list.nvEncUnregisterResource.unwrap())(
2759 encoder_session,
2760 reg_res.registeredResource,
2761 );
2762 (function_list.nvEncDestroyEncoder.unwrap())(encoder_session);
2763 (cuda.cuMemFree_v2)(input_device_ptr);
2764 (cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
2765 (cuda.cuDevicePrimaryCtxRelease_v2)(cu_device);
2766 return Err("Failed to map input buffer".into());
2767 }
2768
2769 let mut bitstream_buffers = Vec::new();
2770 let create_bs_fn = function_list.nvEncCreateBitstreamBuffer.unwrap();
2771 for _ in 0..BITSTREAM_BUFFERS {
2772 let mut bitstream_params = NV_ENC_CREATE_BITSTREAM_BUFFER {
2773 version: sv(NvStruct::CreateBitstreamBuffer),
2774 ..Default::default()
2775 };
2776 if create_bs_fn(encoder_session, &mut bitstream_params)
2777 != NVENCSTATUS::NV_ENC_SUCCESS
2778 {
2779 for &bs in &bitstream_buffers {
2780 (function_list.nvEncDestroyBitstreamBuffer.unwrap())(encoder_session, bs);
2781 }
2782 (function_list.nvEncUnmapInputResource.unwrap())(
2783 encoder_session,
2784 map_params.mappedResource,
2785 );
2786 (function_list.nvEncUnregisterResource.unwrap())(
2787 encoder_session,
2788 reg_res.registeredResource,
2789 );
2790 (function_list.nvEncDestroyEncoder.unwrap())(encoder_session);
2791 (cuda.cuMemFree_v2)(input_device_ptr);
2792 (cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
2793 (cuda.cuDevicePrimaryCtxRelease_v2)(cu_device);
2794 return Err("Failed to create bitstream buffer".into());
2795 }
2796 bitstream_buffers.push(bitstream_params.bitstreamBuffer);
2797 }
2798
2799 let layout = ConvertLayout::of(is_444, bit_depth);
2804 #[cfg(test)]
2805 let layout = layout.filter(|_| !tuning.hardware_csc);
2806 let csc = layout.and_then(|layout| {
2807 ChromaConvert::new(
2808 &cuda,
2809 &function_list,
2810 encoder_session,
2811 width,
2812 height,
2813 layout,
2814 )
2815 });
2816 if bit_depth > 8 && csc.is_none() {
2817 for buffer in &bitstream_buffers {
2818 (function_list.nvEncDestroyBitstreamBuffer.unwrap())(encoder_session, *buffer);
2819 }
2820 (function_list.nvEncUnmapInputResource.unwrap())(
2821 encoder_session,
2822 map_params.mappedResource,
2823 );
2824 (function_list.nvEncUnregisterResource.unwrap())(
2825 encoder_session,
2826 reg_res.registeredResource,
2827 );
2828 (function_list.nvEncDestroyEncoder.unwrap())(encoder_session);
2829 (cuda.cuMemFree_v2)(input_device_ptr);
2830 (cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
2831 (cuda.cuDevicePrimaryCtxRelease_v2)(cu_device);
2832 return Err("the driver took no 10-bit convert kernel".into());
2833 }
2834 keep_primary_context(cu_device);
2835 crate::log::debug!(
2836 "[NVENC] {} initialized (4:4:4 mode: {}, chroma convert: {}).",
2837 codec.display(),
2838 is_444,
2839 if csc.is_some() { "kernel" } else { "hardware" }
2840 );
2841
2842 Ok(Self {
2843 encoder_session,
2844 cuda_context: cu_context,
2845 cuda_device: cu_device,
2846 device_name,
2847 egl_display: egl_display as EGLDisplay,
2848 codec,
2849 fullcolor: is_444,
2850 bit_depth,
2851 asked: (
2852 settings.video_fullcolor && codec.fullcolor(),
2853 settings.video_bit_depth >= 10,
2854 ),
2855 width,
2856 height,
2857 current_qp: codec.hardware_quantizer(Hardware::Nvenc, settings.video_crf),
2858 peak: peak_of(settings),
2859 open_rc: config.rcParams,
2860 last_avg_qp: 0,
2861 last_capped: false,
2862 deferred: None,
2863 refused_bind: None,
2864 held_qp: None,
2865 held_band: None,
2866 slice_layout: tuning.slices.map_or((0, 0), |n| (SLICE_MODE_COUNT, n)),
2867 cap: None,
2868 refit: None,
2869 hold_refused: false,
2870 band_log: VecDeque::new(),
2871 lost_cleanup: false,
2872 held_bytes: 0,
2873 qp_map: Vec::new(),
2874 refinement: None,
2875 delta_mapped: false,
2876 refine_refused: false,
2877 last_quality: None,
2878 last_bytes: None,
2879 encode_config: config,
2880 init_params,
2881 input_device_ptr,
2882 input_pitch,
2883 input_format: NV_ENC_BUFFER_FORMAT::NV_ENC_BUFFER_FORMAT_ARGB,
2884 registered_input_resource: reg_res.registeredResource,
2885 mapped_input_buffer: map_params.mappedResource,
2886 bitstream_buffers,
2887 current_buffer_idx: 0,
2888 dmabuf_cache: HashMap::new(),
2889 external_inputs: Vec::new(),
2890 pinned_hosts: HashMap::new(),
2891 cuda,
2892 egl,
2893 _nvenc_lib: nvenc_lib,
2894 nvenc_funcs: function_list,
2895 omit_stripe_headers: settings.omit_stripe_headers,
2896 node_index: settings.encode_node_index.max(0),
2897 pin_uploads: std::env::var("PIXELFLUX_NVENC_PIN").as_deref() != Ok("0"),
2898 direct_dmabuf: std::env::var("PIXELFLUX_NVENC_DIRECT").as_deref() != Ok("0"),
2899 csc,
2900 dpb,
2901 engines,
2902 references: invalidation.then(|| {
2903 if anchors > 0 {
2904 let mut w =
2905 ReferenceWindow::with_anchors(window_frames(codec, dpb, true), anchors);
2906 if settings.acknowledge_references {
2907 w.set_acknowledged();
2908 }
2909 if codec == Codec::Av1 {
2910 w.lend_free_anchors();
2911 }
2912 w
2913 } else {
2914 ReferenceWindow::new(dpb)
2915 }
2916 }),
2917 last_reference: Reference::Untracked,
2918 frame_num: WideFrameNum::default(),
2919 blit_semaphore: ptr::null_mut(),
2920 band_hash: None,
2921 upload_hash: None,
2922 upload_words: None,
2923 upload_hashes: None,
2924 })
2925 }
2926 }
2927
2928 unsafe fn device_encodes(
2930 funcs: &NV_ENCODE_API_FUNCTION_LIST,
2931 session: *mut c_void,
2932 codec: &GUID,
2933 ) -> bool {
2934 let (Some(count_fn), Some(list_fn)) =
2935 (funcs.nvEncGetEncodeGUIDCount, funcs.nvEncGetEncodeGUIDs)
2936 else {
2937 return true;
2938 };
2939 let mut count = 0u32;
2940 if count_fn(session, &mut count) != NVENCSTATUS::NV_ENC_SUCCESS || count == 0 {
2941 return false;
2942 }
2943 let mut guids = vec![GUID::default(); count as usize];
2944 let mut listed = 0u32;
2945 if list_fn(session, guids.as_mut_ptr(), count, &mut listed) != NVENCSTATUS::NV_ENC_SUCCESS {
2946 return false;
2947 }
2948 guids
2949 .iter()
2950 .take(listed as usize)
2951 .any(|g| guid_eq(g, codec))
2952 }
2953
2954 #[allow(clippy::too_many_arguments)]
2978 fn configure_codec(
2979 config: &mut NV_ENC_CONFIG,
2980 codec: Codec,
2981 fullcolor: bool,
2982 bit_depth: u32,
2983 level: u32,
2984 dpb: u32,
2985 anchors: usize,
2986 tuning: &NvencTuning,
2987 ) {
2988 let primaries = NV_ENC_VUI_COLOR_PRIMARIES::NV_ENC_VUI_COLOR_PRIMARIES_BT709;
2989 let transfer = NV_ENC_VUI_TRANSFER_CHARACTERISTIC::NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT709;
2990 let matrix = NV_ENC_VUI_MATRIX_COEFFS::NV_ENC_VUI_MATRIX_COEFFS_BT709;
2991 let vui = |vui: &mut NV_ENC_CONFIG_H264_VUI_PARAMETERS| {
2992 vui.videoSignalTypePresentFlag = 1;
2993 vui.videoFormat = NV_ENC_VUI_VIDEO_FORMAT::NV_ENC_VUI_VIDEO_FORMAT_UNSPECIFIED;
2994 vui.colourDescriptionPresentFlag = 1;
2995 vui.colourPrimaries = primaries;
2996 vui.transferCharacteristics = transfer;
2997 vui.colourMatrix = matrix;
2998 vui.videoFullRangeFlag = 0;
2999 };
3000 unsafe {
3001 match codec {
3002 Codec::H265 => {
3003 let c = &mut config.encodeCodecConfig.hevcConfig;
3004 c.level = level;
3005 c.maxNumRefFramesInDPB = dpb;
3006 if anchors > 0 {
3007 c.set_enableLTR(1);
3008 c.ltrTrustMode = 0;
3009 c.ltrNumFrames = anchors as u32;
3010 }
3011 c.tier = if tuning.hevc_high_tier {
3012 h265_tier(level)
3013 } else {
3014 0
3015 };
3016 c.set_disableDeblockAcrossSliceBoundary(1);
3017 (c.sliceMode, c.sliceModeData) =
3018 tuning.slices.map_or((0, 0), |n| (SLICE_MODE_COUNT, n));
3019 c.idrPeriod = 0xFFFFFFFF;
3020 c.set_chromaFormatIDC(if fullcolor { 3 } else { 1 });
3021 c.set_repeatSPSPPS(1);
3022 c.set_outputAUD(0);
3023 vui(&mut c.hevcVUIParameters);
3024 }
3025 Codec::Av1 => {
3026 let c = &mut config.encodeCodecConfig.av1Config;
3027 c.level = level;
3028 c.tier = 0;
3029 c.idrPeriod = 0xFFFFFFFF;
3030 c.set_chromaFormatIDC(1);
3031 c.maxNumRefFramesInDPB = dpb;
3032 if anchors > 0 {
3033 c.set_enableLTR(1);
3034 c.ltrNumFrames = anchors as u32;
3035 }
3036 c.set_repeatSeqHdr(1);
3037 c.set_outputAnnexBFormat(0);
3038 c.set_enableBitstreamPadding(0);
3039 c.numTileColumns = 1;
3040 c.numTileRows = 1;
3041 c.colorPrimaries = primaries;
3042 c.transferCharacteristics = transfer;
3043 c.matrixCoefficients = matrix;
3044 c.colorRange = 0;
3045 }
3046 _ => {
3047 let c = &mut config.encodeCodecConfig.h264Config;
3048 c.level = level;
3049 c.maxNumRefFrames = dpb;
3050 if anchors > 0 {
3051 c.set_enableLTR(1);
3052 c.ltrTrustMode = 0;
3053 c.ltrNumFrames = anchors as u32;
3054 }
3055 c.sliceMode = SLICE_MODE_COUNT;
3056 c.sliceModeData = tuning.slices.unwrap_or(SLICES_PER_FRAME);
3057 c.idrPeriod = 0xFFFFFFFF;
3058 c.chromaFormatIDC = if fullcolor { 3 } else { 1 };
3059 c.set_repeatSPSPPS(1);
3060 c.entropyCodingMode =
3061 NV_ENC_H264_ENTROPY_CODING_MODE::NV_ENC_H264_ENTROPY_CODING_MODE_CABAC;
3062 c.set_outputAUD(0);
3063 c.h264VUIParameters.bitstreamRestrictionFlag = 1;
3064 vui(&mut c.h264VUIParameters);
3065 }
3066 }
3067 }
3068 set_bit_depth(config, codec, bit_depth, nvenc_cur_ver());
3069 }
3070
3071 fn declared_level(&self) -> u32 {
3073 let config = &self.encode_config.encodeCodecConfig;
3074 unsafe {
3075 match self.codec {
3076 Codec::H265 => config.hevcConfig.level,
3077 Codec::Av1 => config.av1Config.level,
3078 _ => config.h264Config.level,
3079 }
3080 }
3081 }
3082
3083 fn level_for(&self, width: u32, height: u32, fps: u32) -> u32 {
3086 let high_tier = unsafe { self.encode_config.encodeCodecConfig.hevcConfig.tier == 1 };
3087 nvenc_level(
3088 self.codec,
3089 width,
3090 height,
3091 fps,
3092 level_rate(
3093 self.codec,
3094 u64::from(self.encode_config.rcParams.maxBitRate),
3095 NVENC_AV1_WEIGHTED.load(Ordering::Relaxed),
3096 ),
3097 high_tier,
3098 )
3099 }
3100
3101 fn set_level(&mut self, width: u32, height: u32, fps: u32) {
3103 let level = self.level_for(width, height, fps);
3104 match self.codec {
3105 Codec::H265 => self.encode_config.encodeCodecConfig.hevcConfig.level = level,
3106 Codec::Av1 => self.encode_config.encodeCodecConfig.av1Config.level = level,
3107 _ => self.encode_config.encodeCodecConfig.h264Config.level = level,
3108 }
3109 }
3110
3111 fn dpb_frames_at(&self, width: u32, height: u32, fps: u32) -> u32 {
3114 let level = self.level_for(width, height, fps);
3115 match self.codec {
3116 Codec::H265 => h265_dpb_frames(level, width, height),
3117 Codec::Av1 => AV1_REFERENCES,
3118 _ => h264_dpb_frames(level, width, height),
3119 }
3120 }
3121
3122 pub fn last_reference(&self) -> Reference {
3124 self.last_reference
3125 }
3126
3127 pub fn invalidate_reference(&mut self, frame_id: u16) -> bool {
3130 let Some(references) = &mut self.references else {
3131 return false;
3132 };
3133 match references.invalidate(frame_id) {
3134 Invalidation::Forget(pts) => unsafe {
3135 (self.nvenc_funcs.nvEncInvalidateRefFrames.unwrap())(self.encoder_session, pts)
3136 == NVENCSTATUS::NV_ENC_SUCCESS
3137 },
3138 Invalidation::KeyFrame | Invalidation::Ignored => true,
3139 }
3140 }
3141
3142 pub fn acknowledge_reference(&mut self, frame_id: u16, held: bool) {
3145 if let Some(references) = &mut self.references {
3146 references.acknowledge(frame_id, held);
3147 }
3148 }
3149
3150 pub fn take_report(&mut self, report: ReferenceReport) -> bool {
3153 match report {
3154 ReferenceReport::Lost(frame_id) | ReferenceReport::Withheld(frame_id) => {
3155 self.band_log.clear();
3157 self.invalidate_reference(frame_id)
3158 }
3159 ReferenceReport::Held(frame_id) => {
3160 self.acknowledge_reference(frame_id, true);
3161 true
3162 }
3163 ReferenceReport::Sent(frame_id) => {
3164 self.acknowledge_reference(frame_id, false);
3165 true
3166 }
3167 }
3168 }
3169
3170 fn retract_last(&mut self) -> bool {
3174 let (funcs, session) = (&self.nvenc_funcs, self.encoder_session);
3175 self.references.as_mut().is_some_and(|r| {
3176 r.retract(|pts| unsafe {
3177 (funcs.nvEncInvalidateRefFrames.unwrap())(session, pts)
3178 == NVENCSTATUS::NV_ENC_SUCCESS
3179 })
3180 })
3181 }
3182
3183 pub fn split_summary(&self) -> String {
3186 let forced = [
3187 NV_ENC_SPLIT_ENCODE_MODE::NV_ENC_SPLIT_AUTO_FORCED_MODE,
3188 NV_ENC_SPLIT_ENCODE_MODE::NV_ENC_SPLIT_THREE_FORCED_MODE,
3189 ]
3190 .map(|m| m as u32)
3191 .contains(&self.init_params.splitEncodeMode());
3192 match self.engines {
3193 Some(n) if n > 1 && forced => format!("split across {n} engines"),
3194 Some(n) if n > 1 && self.codec != Codec::H264 => {
3195 format!("split as the driver picks, {n} engines")
3196 }
3197 Some(n) => format!("{n} engine{}", if n == 1 { "" } else { "s" }),
3198 None => "engines unreported".into(),
3199 }
3200 }
3201
3202 pub fn device_name(&self) -> &str {
3205 &self.device_name
3206 }
3207
3208 pub fn codec(&self) -> Codec {
3209 self.codec
3210 }
3211
3212 pub fn is_full_range(&self) -> bool {
3214 false
3215 }
3216
3217 pub fn is_fullcolor(&self) -> bool {
3219 self.fullcolor
3220 }
3221
3222 pub fn bit_depth(&self) -> u32 {
3224 self.bit_depth
3225 }
3226
3227 pub fn reconfigure_resolution(&mut self, settings: &CaptureSettings) -> Result<bool, String> {
3258 let new_w = settings.width as u32;
3259 let new_h = settings.height as u32;
3260 let is_cbr = self.encode_config.rcParams.rateControlMode
3261 == NV_ENC_PARAMS_RC_MODE::NV_ENC_PARAMS_RC_CBR;
3262 if settings.encode_node_index.max(0) != self.node_index {
3263 return Err("encode device changed".into());
3264 }
3265 if settings.codec != self.codec {
3266 return Err("codec changed".into());
3267 }
3268 let fullcolor = settings.video_fullcolor && self.codec.fullcolor();
3269 if fullcolor != self.asked.0 && fullcolor != self.fullcolor {
3270 return Err("chroma format changed".into());
3271 }
3272 let ten_bit = settings.video_bit_depth >= 10;
3273 if ten_bit != self.asked.1
3274 && ten_bit != (self.bit_depth > 8)
3275 && matches!(self.codec, Codec::H265 | Codec::Av1)
3276 {
3277 return Err("bit depth changed".into());
3278 }
3279 if settings.video_cbr_mode != is_cbr {
3280 return Err("rate-control mode changed".into());
3281 }
3282 if new_w == 0
3283 || new_h == 0
3284 || new_w > self.init_params.maxEncodeWidth
3285 || new_h > self.init_params.maxEncodeHeight
3286 {
3287 return Err(format!(
3288 "{}x{} outside reconfigure headroom {}x{}",
3289 new_w, new_h, self.init_params.maxEncodeWidth, self.init_params.maxEncodeHeight
3290 ));
3291 }
3292 if (new_w, new_h) == (self.width, self.height) {
3293 self.release_pinned_hosts();
3294 self.reconfigure_rate(settings);
3295 self.omit_stripe_headers = settings.omit_stripe_headers;
3296 return Ok(false);
3297 }
3298 let rate = FrameRate::of(settings.target_fps);
3299 let dpb = self.dpb_frames_at(new_w, new_h, rate.ceil()).min(self.dpb);
3300
3301 unsafe {
3302 let _ = (self.cuda.cuCtxPushCurrent_v2)(self.cuda_context);
3303 self.unmap_external_inputs();
3304 if let Some(csc) = self.csc.take() {
3305 csc.release(&self.cuda, &self.nvenc_funcs, self.encoder_session);
3306 }
3307 if !self.mapped_input_buffer.is_null() {
3308 (self.nvenc_funcs.nvEncUnmapInputResource.unwrap())(
3309 self.encoder_session,
3310 self.mapped_input_buffer,
3311 );
3312 self.mapped_input_buffer = ptr::null_mut();
3313 }
3314 if !self.registered_input_resource.is_null() {
3315 (self.nvenc_funcs.nvEncUnregisterResource.unwrap())(
3316 self.encoder_session,
3317 self.registered_input_resource,
3318 );
3319 self.registered_input_resource = ptr::null_mut();
3320 }
3321 if self.input_device_ptr != 0 {
3322 (self.cuda.cuMemFree_v2)(self.input_device_ptr);
3323 self.input_device_ptr = 0;
3324 }
3325 let imports: Vec<CachedDmaBuf> = self.dmabuf_cache.drain().map(|(_, c)| c).collect();
3326 for cache in imports {
3327 self.release_dmabuf_import(cache);
3328 }
3329 for (base, len) in self.pinned_hosts.drain() {
3330 if len > 0 {
3331 (self.cuda.cuMemHostUnregister)(base as *mut c_void);
3332 }
3333 }
3334
3335 if is_cbr {
3336 let bps = cbr_bps(settings);
3337 set_cbr_rate(
3338 &mut self.encode_config.rcParams,
3339 bps,
3340 cbr_vbv(settings, bps),
3341 );
3342 } else {
3343 let qp = self
3344 .codec
3345 .hardware_quantizer(Hardware::Nvenc, settings.video_crf);
3346 self.encode_config.rcParams.constQP.qpInterP = qp;
3347 self.encode_config.rcParams.constQP.qpInterB = qp;
3348 self.encode_config.rcParams.constQP.qpIntra = qp;
3349 self.current_qp = qp;
3350 }
3351 self.set_level(new_w, new_h, rate.ceil());
3352 match self.codec {
3353 Codec::H265 => {
3354 self.encode_config
3355 .encodeCodecConfig
3356 .hevcConfig
3357 .maxNumRefFramesInDPB = dpb
3358 }
3359 Codec::Av1 => {
3360 self.encode_config
3361 .encodeCodecConfig
3362 .av1Config
3363 .maxNumRefFramesInDPB = dpb
3364 }
3365 _ => {
3366 self.encode_config
3367 .encodeCodecConfig
3368 .h264Config
3369 .maxNumRefFrames = dpb
3370 }
3371 }
3372 self.init_params.encodeWidth = new_w;
3373 self.init_params.encodeHeight = new_h;
3374 self.init_params.darWidth = new_w;
3375 self.init_params.darHeight = new_h;
3376 self.init_params.frameRateNum = rate.num;
3377 self.init_params.frameRateDen = rate.den;
3378 self.init_params.set_splitEncodeMode(split_mode(
3379 self.codec,
3380 new_w,
3381 new_h,
3382 self.engines,
3383 nvenc_cur_ver(),
3384 ) as u32);
3385 if self.reconfigure(true, true) != NVENCSTATUS::NV_ENC_SUCCESS {
3386 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
3387 return Err("NvEncReconfigureEncoder rejected the resolution change".into());
3388 }
3389 self.width = new_w;
3390 self.height = new_h;
3391 self.dpb = dpb;
3392 if let Some(references) = &mut self.references {
3393 references.set_capacity(window_frames(self.codec, dpb, references.anchored()));
3394 references.reset();
3395 }
3396
3397 let mut input_device_ptr: CUdeviceptr = 0;
3398 let mut input_pitch: usize = 0;
3399 let res = (self.cuda.cuMemAllocPitch_v2)(
3400 &mut input_device_ptr,
3401 &mut input_pitch,
3402 (new_w * 4) as usize,
3403 new_h as usize,
3404 16,
3405 );
3406 if res != CUresult::CUDA_SUCCESS {
3407 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
3408 return Err("Failed to allocate ARGB input buffer on GPU".into());
3409 }
3410 let mut reg_res = NV_ENC_REGISTER_RESOURCE {
3411 version: sv(NvStruct::RegisterResource),
3412 resourceType: NV_ENC_INPUT_RESOURCE_TYPE::NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR,
3413 width: new_w,
3414 height: new_h,
3415 resourceToRegister: input_device_ptr as *mut c_void,
3416 pitch: input_pitch as u32,
3417 bufferFormat: self.input_format,
3418 bufferUsage: NV_ENC_BUFFER_USAGE::NV_ENC_INPUT_IMAGE,
3419 ..Default::default()
3420 };
3421 let register_fn = self.nvenc_funcs.nvEncRegisterResource.unwrap();
3422 if register_fn(self.encoder_session, &mut reg_res) != NVENCSTATUS::NV_ENC_SUCCESS {
3423 (self.cuda.cuMemFree_v2)(input_device_ptr);
3424 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
3425 return Err("Failed to register input buffer".into());
3426 }
3427 let mut map_params = NV_ENC_MAP_INPUT_RESOURCE {
3428 version: sv(NvStruct::MapInputResource),
3429 registeredResource: reg_res.registeredResource,
3430 ..Default::default()
3431 };
3432 let map_fn = self.nvenc_funcs.nvEncMapInputResource.unwrap();
3433 if map_fn(self.encoder_session, &mut map_params) != NVENCSTATUS::NV_ENC_SUCCESS {
3434 (self.nvenc_funcs.nvEncUnregisterResource.unwrap())(
3435 self.encoder_session,
3436 reg_res.registeredResource,
3437 );
3438 (self.cuda.cuMemFree_v2)(input_device_ptr);
3439 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
3440 return Err("Failed to map input buffer".into());
3441 }
3442 self.input_device_ptr = input_device_ptr;
3443 self.input_pitch = input_pitch;
3444 self.registered_input_resource = reg_res.registeredResource;
3445 self.mapped_input_buffer = map_params.mappedResource;
3446 if let Some(layout) = ConvertLayout::of(self.fullcolor, self.bit_depth) {
3447 self.csc = ChromaConvert::new(
3448 &self.cuda,
3449 &self.nvenc_funcs,
3450 self.encoder_session,
3451 new_w,
3452 new_h,
3453 layout,
3454 );
3455 if self.bit_depth > 8 && self.csc.is_none() {
3456 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
3457 return Err("the driver took no 10-bit convert kernel".into());
3458 }
3459 }
3460 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
3461 }
3462 self.omit_stripe_headers = settings.omit_stripe_headers;
3463 Ok(true)
3464 }
3465
3466 unsafe fn pin_host_source(&mut self, base: usize, len: usize) {
3471 if let std::collections::hash_map::Entry::Vacant(e) = self.pinned_hosts.entry(base) {
3472 let st = (self.cuda.cuMemHostRegister_v2)(base as *mut c_void, len, 0);
3473 e.insert(if st == CUresult::CUDA_SUCCESS { len } else { 0 });
3474 }
3475 }
3476
3477 pub fn release_pinned_hosts(&mut self) {
3486 if self.pinned_hosts.is_empty() {
3487 return;
3488 }
3489 unsafe {
3490 let _ = (self.cuda.cuCtxPushCurrent_v2)(self.cuda_context);
3491 for (base, len) in self.pinned_hosts.drain() {
3492 if len > 0 {
3493 (self.cuda.cuMemHostUnregister)(base as *mut c_void);
3494 }
3495 }
3496 let _ = (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
3497 }
3498 }
3499
3500 unsafe fn release_dmabuf_import(&self, cache: CachedDmaBuf) {
3505 if cache.tex != 0 {
3506 (self.cuda.cuTexObjectDestroy)(cache.tex);
3507 }
3508 if let DmaBufInput::Direct {
3509 registered, mapped, ..
3510 } = cache.input
3511 {
3512 (self.nvenc_funcs.nvEncUnmapInputResource.unwrap())(self.encoder_session, mapped);
3513 (self.nvenc_funcs.nvEncUnregisterResource.unwrap())(self.encoder_session, registered);
3514 }
3515 (self.cuda.cuGraphicsUnregisterResource)(cache.cuda_resource);
3516 if let Some(egl) = &self.egl {
3517 (egl.eglDestroyImageKHR)(self.egl_display, cache.egl_image);
3518 }
3519 }
3520
3521 unsafe fn set_input_format(&mut self, format: NV_ENC_BUFFER_FORMAT) -> Result<(), String> {
3528 if self.input_format == format && !self.registered_input_resource.is_null() {
3529 return Ok(());
3530 }
3531 if !self.mapped_input_buffer.is_null() {
3532 (self.nvenc_funcs.nvEncUnmapInputResource.unwrap())(
3533 self.encoder_session,
3534 self.mapped_input_buffer,
3535 );
3536 self.mapped_input_buffer = ptr::null_mut();
3537 }
3538 if !self.registered_input_resource.is_null() {
3539 (self.nvenc_funcs.nvEncUnregisterResource.unwrap())(
3540 self.encoder_session,
3541 self.registered_input_resource,
3542 );
3543 self.registered_input_resource = ptr::null_mut();
3544 }
3545 let mut reg_res = NV_ENC_REGISTER_RESOURCE {
3546 version: sv(NvStruct::RegisterResource),
3547 resourceType: NV_ENC_INPUT_RESOURCE_TYPE::NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR,
3548 width: self.width,
3549 height: self.height,
3550 resourceToRegister: self.input_device_ptr as *mut c_void,
3551 pitch: self.input_pitch as u32,
3552 bufferFormat: format,
3553 bufferUsage: NV_ENC_BUFFER_USAGE::NV_ENC_INPUT_IMAGE,
3554 ..Default::default()
3555 };
3556 if (self.nvenc_funcs.nvEncRegisterResource.unwrap())(self.encoder_session, &mut reg_res)
3557 != NVENCSTATUS::NV_ENC_SUCCESS
3558 {
3559 return Err(format!("Failed to register input buffer as {format:?}"));
3560 }
3561 let mut map_params = NV_ENC_MAP_INPUT_RESOURCE {
3562 version: sv(NvStruct::MapInputResource),
3563 registeredResource: reg_res.registeredResource,
3564 ..Default::default()
3565 };
3566 if (self.nvenc_funcs.nvEncMapInputResource.unwrap())(self.encoder_session, &mut map_params)
3567 != NVENCSTATUS::NV_ENC_SUCCESS
3568 {
3569 (self.nvenc_funcs.nvEncUnregisterResource.unwrap())(
3570 self.encoder_session,
3571 reg_res.registeredResource,
3572 );
3573 return Err(format!("Failed to map input buffer as {format:?}"));
3574 }
3575 self.registered_input_resource = reg_res.registeredResource;
3576 self.mapped_input_buffer = map_params.mappedResource;
3577 self.input_format = format;
3578 Ok(())
3579 }
3580
3581 unsafe fn reconfigure(&mut self, reset: bool, force_idr: bool) -> NVENCSTATUS {
3584 self.init_params.encodeConfig = &mut self.encode_config;
3585 let mut params = reconfigure_params(&self.init_params, nvenc_cur_ver(), reset, force_idr);
3586 (self.nvenc_funcs.nvEncReconfigureEncoder.unwrap())(self.encoder_session, &mut params.value)
3587 }
3588
3589 unsafe fn reconfigure_if_needed(&mut self, crf: u32) -> bool {
3600 let binding = self.peak.is_some_and(|p| p.binding);
3601 if self.encode_config.rcParams.rateControlMode
3602 == NV_ENC_PARAMS_RC_MODE::NV_ENC_PARAMS_RC_CBR
3603 && !binding
3604 {
3605 return false;
3606 }
3607 let target_qp = self.codec.hardware_quantizer(Hardware::Nvenc, crf as i32);
3608 if self.current_qp != target_qp {
3609 let rc = &mut self.encode_config.rcParams;
3610 rc.constQP.qpInterP = target_qp;
3611 rc.constQP.qpInterB = target_qp;
3612 rc.constQP.qpIntra = target_qp;
3613 if binding {
3614 rc.minQP.qpInterP = target_qp;
3615 rc.minQP.qpInterB = target_qp;
3616 rc.minQP.qpIntra = target_qp;
3617 }
3618 let status = self.reconfigure(false, false);
3619 if status == NVENCSTATUS::NV_ENC_SUCCESS {
3620 self.current_qp = target_qp;
3621 return true;
3622 }
3623 eprintln!(
3624 "[NVENC] Quantizer reconfigure refused ({status:?}): {}",
3625 last_error(&self.nvenc_funcs, self.encoder_session)
3626 );
3627 }
3628 false
3629 }
3630
3631 pub fn capped(&self) -> bool {
3633 self.peak.is_some()
3634 }
3635
3636 pub fn peak_bound(&self) -> bool {
3638 self.peak.is_some_and(|p| p.binding)
3639 }
3640
3641 pub fn coded_capped(&self) -> bool {
3643 self.last_capped
3644 }
3645
3646 pub fn idle(&mut self) {
3649 let Some(peak) = self.peak.as_mut() else {
3650 return;
3651 };
3652 if let Some(bind) = peak.note(0.0, Some(true)) {
3653 unsafe { self.switch_peak(bind) };
3654 }
3655 }
3656
3657 pub fn defer_blocks(&mut self, plan: Option<&crate::ledger::BlockPlan>) {
3661 self.deferred = plan.filter(|_| self.refines()).cloned();
3662 }
3663
3664 pub fn last_quality(&self) -> Option<u32> {
3667 self.last_quality
3668 }
3669
3670 pub fn last_size(&self) -> Option<usize> {
3672 self.last_bytes
3673 }
3674
3675 pub fn hold_quantizer(&mut self, crf: u32, band: Option<(f64, f64)>) {
3688 self.held_qp = Some(crf);
3689 self.held_band = band.filter(|_| self.band_size().is_some());
3690 }
3691
3692 pub fn refines(&self) -> bool {
3695 matches!(self.codec, Codec::H264 | Codec::H265 | Codec::Av1) && !self.refine_refused
3696 }
3697
3698 pub fn refine_blocks(&mut self, refinement: &crate::ledger::Refinement) {
3705 self.refinement = self.refines().then(|| refinement.clone());
3706 }
3707
3708 unsafe fn block_map(
3714 &mut self,
3715 refinement: Option<&crate::ledger::Refinement>,
3716 deferred: Option<&crate::ledger::BlockPlan>,
3717 ) -> bool {
3718 if !self.delta_map_mode() {
3719 return false;
3720 }
3721 let hw = |index: i32| self.codec.hardware_quantizer(Hardware::Nvenc, index) as i32;
3722 let refine = refinement.map(|r| {
3723 let delta = (hw(r.target) - hw(r.from)).clamp(i32::from(i8::MIN), 0) as i8;
3724 (&r.plan, delta)
3725 });
3726 let defer = (self.codec.quantizer_max() as i32 - self.current_qp as i32)
3727 .clamp(0, i32::from(i8::MAX)) as i8;
3728 let unit = match self.codec {
3729 Codec::H264 => 16,
3730 Codec::H265 => 32,
3731 _ => 64,
3732 };
3733 let (cols, rows) = (self.width.div_ceil(unit), self.height.div_ceil(unit));
3734 self.qp_map.clear();
3735 self.qp_map.extend((0..rows).flat_map(|row| {
3736 (0..cols).map(move |col| {
3737 let (x, y, u) = ((col * unit) as i32, (row * unit) as i32, unit as i32);
3738 match refine {
3739 Some((plan, delta)) if plan.refines_area(x, y, u, u) => delta,
3740 _ if deferred.is_some_and(|d| d.refines_area(x, y, u, u)) => defer,
3741 _ => 0,
3742 }
3743 })
3744 }));
3745 true
3746 }
3747
3748 unsafe fn delta_map_mode(&mut self) -> bool {
3752 if self.delta_mapped {
3753 return true;
3754 }
3755 let saved = self.encode_config.rcParams;
3756 self.encode_config.rcParams.qpMapMode = NV_ENC_QP_MAP_MODE::NV_ENC_QP_MAP_DELTA;
3757 let status = self.reconfigure(false, false);
3758 if status != NVENCSTATUS::NV_ENC_SUCCESS {
3759 self.encode_config.rcParams = saved;
3760 self.refine_refused = true;
3761 eprintln!(
3762 "[NVENC] Refining blocks through a QP delta map refused ({status:?}): {}",
3763 last_error(&self.nvenc_funcs, self.encoder_session)
3764 );
3765 return false;
3766 }
3767 self.delta_mapped = true;
3768 true
3769 }
3770
3771 pub fn cap(&mut self, bytes: usize) {
3777 self.cap = Some(bytes);
3778 }
3779
3780 pub fn refit(&self) -> Option<f64> {
3783 self.refit
3784 }
3785
3786 pub fn take_lost_cleanup(&mut self) -> bool {
3790 std::mem::take(&mut self.lost_cleanup)
3791 }
3792
3793 fn forget_bands(&mut self, pts: u64) {
3797 let (band, lost) = reach_back(
3798 &mut self.band_log,
3799 pts,
3800 self.held_band,
3801 self.held_qp.is_some(),
3802 );
3803 self.held_band = band;
3804 self.lost_cleanup |= lost;
3805 }
3806
3807 fn set_band_layout(&mut self, band: Option<usize>) {
3817 match self.codec {
3818 Codec::H264 => {
3819 self.encode_config
3820 .encodeCodecConfig
3821 .h264Config
3822 .disableDeblockingFilterIDC = u32::from(band.is_some());
3823 }
3824 Codec::H265 => {
3825 let blocks = (self.width.div_ceil(32) * self.height.div_ceil(32)) as usize;
3826 let c = unsafe { &mut self.encode_config.encodeCodecConfig.hevcConfig };
3827 (c.sliceMode, c.sliceModeData) = match band {
3828 Some(first) if first > 0 && blocks.div_ceil(first) <= BAND_SLICES_MAX => {
3829 (0, first as u32)
3830 }
3831 _ => self.slice_layout,
3832 };
3833 }
3834 _ => {}
3835 }
3836 }
3837
3838 pub fn band_size(&self) -> Option<usize> {
3842 matches!(self.codec, Codec::H264 | Codec::H265 | Codec::Av1).then_some(self.held_bytes)
3843 }
3844
3845 unsafe fn hold_rate(&mut self, q: u32, band: Option<usize>) -> Option<NV_ENC_RC_PARAMS> {
3855 let mapped = band.is_some();
3856 let saved = self.encode_config.rcParams;
3857 let constant = saved.rateControlMode == NV_ENC_PARAMS_RC_MODE::NV_ENC_PARAMS_RC_CONSTQP;
3858 if constant && !mapped && saved.constQP.qpIntra == q && saved.constQP.qpInterP == q {
3859 return None;
3860 }
3861 self.set_band_layout(band);
3862 let rc = &mut self.encode_config.rcParams;
3863 if mapped {
3864 rc.qpMapMode = NV_ENC_QP_MAP_MODE::NV_ENC_QP_MAP_DELTA;
3865 }
3866 rc.rateControlMode = NV_ENC_PARAMS_RC_MODE::NV_ENC_PARAMS_RC_CONSTQP;
3867 rc.constQP.qpInterP = q;
3868 rc.constQP.qpInterB = q;
3869 rc.constQP.qpIntra = q;
3870 let status = self.reconfigure(false, false);
3871 if status != NVENCSTATUS::NV_ENC_SUCCESS {
3872 self.encode_config.rcParams = saved;
3873 self.set_band_layout(None);
3874 if !std::mem::replace(&mut self.hold_refused, true) {
3875 eprintln!(
3876 "[NVENC] Holding a frame at quantizer {q} refused ({status:?}): {}",
3877 last_error(&self.nvenc_funcs, self.encoder_session)
3878 );
3879 }
3880 return None;
3881 }
3882 Some(saved)
3883 }
3884
3885 pub fn reconfigure_rate(&mut self, settings: &CaptureSettings) -> bool {
3898 if let Some(peak) = self.peak.as_mut() {
3899 let bps = cbr_bps(settings);
3900 peak.follow(bps, cbr_vbv(settings, bps), settings.target_fps);
3901 if peak.binding {
3902 let rc = &mut self.encode_config.rcParams;
3903 let at = rc.maxBitRate;
3904 rc.maxBitRate = peak.bps;
3905 let past = self.level_for_peak() > self.declared_level();
3906 self.encode_config.rcParams.maxBitRate = at;
3907 if past {
3908 unsafe { self.switch_peak(false) };
3909 }
3910 }
3911 }
3912 unsafe {
3913 let mut changed = false;
3914 let mut level_raised = false;
3915 if self.encode_config.rcParams.rateControlMode
3916 == NV_ENC_PARAMS_RC_MODE::NV_ENC_PARAMS_RC_CBR
3917 {
3918 let bps = cbr_bps(settings);
3919 let vbv = cbr_vbv(settings, bps);
3920 let rc = &mut self.encode_config.rcParams;
3921 if rc.averageBitRate != bps || rc.maxBitRate != bps || rc.vbvBufferSize != vbv {
3922 log_held_rate(settings, bps);
3923 set_cbr_rate(rc, bps, vbv);
3924 changed = true;
3925 let fps = self
3926 .init_params
3927 .frameRateNum
3928 .div_ceil(self.init_params.frameRateDen.max(1));
3929 let (w, h) = (self.init_params.encodeWidth, self.init_params.encodeHeight);
3930 if self.level_for(w, h, fps) > self.declared_level() {
3931 self.set_level(w, h, fps);
3932 level_raised = true;
3933 }
3934 }
3935 }
3936 let rate = FrameRate::of(settings.target_fps);
3937 if (self.init_params.frameRateNum, self.init_params.frameRateDen)
3938 != (rate.num, rate.den)
3939 {
3940 self.init_params.frameRateNum = rate.num;
3941 self.init_params.frameRateDen = rate.den;
3942 let (w, h, fps) = (
3943 self.init_params.encodeWidth,
3944 self.init_params.encodeHeight,
3945 rate.ceil(),
3946 );
3947 if self.level_for(w, h, fps) > self.declared_level() {
3948 self.set_level(w, h, fps);
3949 level_raised = true;
3950 }
3951 changed = true;
3952 }
3953 if !changed {
3954 return true;
3955 }
3956 let mut status = self.reconfigure(false, level_raised);
3957 if status != NVENCSTATUS::NV_ENC_SUCCESS
3958 && self.codec == Codec::Av1
3959 && !NVENC_AV1_WEIGHTED.swap(true, Ordering::Relaxed)
3960 {
3961 let (w, h) = (self.init_params.encodeWidth, self.init_params.encodeHeight);
3962 let fps = self
3963 .init_params
3964 .frameRateNum
3965 .div_ceil(self.init_params.frameRateDen.max(1));
3966 let refused = self.declared_level();
3967 if self.level_for(w, h, fps) > refused {
3968 self.set_level(w, h, fps);
3969 level_raised = true;
3970 status = self.reconfigure(false, true);
3971 }
3972 if status == NVENCSTATUS::NV_ENC_SUCCESS {
3973 eprintln!(
3974 "[NVENC] The driver refused AV1 level {refused} at {} kbps; declaring {}",
3975 self.encode_config.rcParams.maxBitRate / 1000,
3976 self.declared_level()
3977 );
3978 } else {
3979 NVENC_AV1_WEIGHTED.store(false, Ordering::Relaxed);
3980 }
3981 }
3982 if status != NVENCSTATUS::NV_ENC_SUCCESS {
3983 eprintln!(
3984 "[NVENC] Rate reconfigure refused ({status:?}): {}",
3985 last_error(&self.nvenc_funcs, self.encoder_session)
3986 );
3987 return false;
3988 }
3989 if level_raised && let Some(references) = &mut self.references {
3990 references.reset();
3991 }
3992 true
3993 }
3994 }
3995
3996 unsafe fn submit_frame(
4012 &mut self,
4013 mapped_buffer: NV_ENC_INPUT_PTR,
4014 buffer_format: NV_ENC_BUFFER_FORMAT,
4015 frame_number: u64,
4016 force_idr: bool,
4017 ) -> Result<Vec<u8>, String> {
4018 let output_bitstream = self.bitstream_buffers[self.current_buffer_idx];
4019 self.current_buffer_idx = (self.current_buffer_idx + 1) % self.bitstream_buffers.len();
4020
4021 let asked_idr = force_idr;
4022 let mut force_idr =
4023 force_idr || self.references.as_ref().is_some_and(|r| !r.has_reference());
4024 let mut forgot = None;
4025 if !force_idr
4026 && let Some(references) = &mut self.references
4027 && references.plan_anchor(false).is_some()
4028 {
4029 match references.settle() {
4030 Some(Invalidation::Forget(pts)) => {
4031 force_idr = (self.nvenc_funcs.nvEncInvalidateRefFrames.unwrap())(
4032 self.encoder_session,
4033 pts,
4034 ) != NVENCSTATUS::NV_ENC_SUCCESS;
4035 forgot = Some(pts);
4036 }
4037 Some(Invalidation::KeyFrame) => force_idr = true,
4038 _ => {}
4039 }
4040 }
4041 if !force_idr && let Some(references) = &mut self.references {
4042 match references.forget_stale() {
4043 Some(Invalidation::Forget(pts)) => {
4044 force_idr = (self.nvenc_funcs.nvEncInvalidateRefFrames.unwrap())(
4045 self.encoder_session,
4046 pts,
4047 ) != NVENCSTATUS::NV_ENC_SUCCESS;
4048 forgot = Some(forgot.map_or(pts, |f: u64| f.min(pts)));
4049 }
4050 Some(Invalidation::KeyFrame) => force_idr = true,
4051 _ => {}
4052 }
4053 }
4054 if force_idr {
4055 if !asked_idr
4058 && !self.band_log.is_empty()
4059 && (self.held_band.is_some() || self.held_qp.is_none())
4060 {
4061 self.lost_cleanup = true;
4062 }
4063 self.band_log.clear();
4064 } else if let Some(pts) = forgot {
4065 self.forget_bands(pts);
4066 }
4067 let anchor = self
4068 .references
4069 .as_ref()
4070 .and_then(|r| r.plan_anchor(force_idr));
4071 let named = self
4072 .references
4073 .as_ref()
4074 .filter(|_| !force_idr)
4075 .and_then(ReferenceWindow::predicting_anchor);
4076 let mut pic_params = NV_ENC_PIC_PARAMS {
4077 version: sv(NvStruct::PicParams),
4078 inputWidth: self.width,
4079 inputHeight: self.height,
4080 inputBuffer: mapped_buffer,
4081 outputBitstream: output_bitstream,
4082 bufferFmt: buffer_format,
4083 pictureStruct: NV_ENC_PIC_STRUCT::NV_ENC_PIC_STRUCT_FRAME,
4084 encodePicFlags: if force_idr {
4085 NV_ENC_PIC_FLAGS::NV_ENC_PIC_FLAG_FORCEIDR as u32
4086 } else {
4087 0
4088 },
4089 inputTimeStamp: self
4090 .references
4091 .as_ref()
4092 .map_or(0, ReferenceWindow::next_pts),
4093 ..Default::default()
4094 };
4095
4096 macro_rules! long_term {
4097 ($p:expr) => {{
4098 let p = $p;
4099 if let Some(slot) = named {
4100 p.set_ltrUseFrames(1);
4101 p.ltrUseFrameBitmap = 1 << slot;
4102 }
4103 if let Some(slot) = anchor {
4104 p.set_ltrMarkFrame(1);
4105 p.ltrMarkFrameIdx = u32::from(slot);
4106 }
4107 }};
4108 }
4109 match self.codec {
4110 Codec::Av1 => long_term!(&mut pic_params.codecPicParams.av1PicParams),
4111 Codec::H265 => long_term!(&mut pic_params.codecPicParams.hevcPicParams),
4112 _ => long_term!(&mut pic_params.codecPicParams.h264PicParams),
4113 }
4114 let held_qp = self.held_qp.take();
4115 let band = self.held_band.take();
4116 let held = held_qp.and_then(|crf| {
4117 let q = self.codec.hardware_quantizer(Hardware::Nvenc, crf as i32);
4118 let rest = self
4119 .codec
4120 .quantizer_max()
4121 .min(q + u32::from(i8::MIN.unsigned_abs()));
4122 match band {
4123 Some(share) if rest > q => {
4124 let first = fill_band_map(
4125 &mut self.qp_map,
4126 self.codec,
4127 self.width,
4128 self.height,
4129 share,
4130 q as i32 - rest as i32,
4131 );
4132 let held = self.hold_rate(rest, Some(first));
4133 if held.is_some() {
4134 point_at_qp_map(&mut pic_params, &mut self.qp_map, nvenc_cur_ver());
4135 }
4136 held
4137 }
4138 _ => self.hold_rate(q, None),
4139 }
4140 });
4141 let refinement = self
4142 .refinement
4143 .take()
4144 .filter(|_| held_qp.is_none() && !force_idr);
4145 let deferred = self
4146 .deferred
4147 .take()
4148 .filter(|_| held_qp.is_none() && !force_idr);
4149 let mapped = (refinement.is_some() || deferred.is_some())
4150 && self.block_map(refinement.as_ref(), deferred.as_ref());
4151 if mapped {
4152 point_at_qp_map(&mut pic_params, &mut self.qp_map, nvenc_cur_ver());
4153 }
4154 let mut result = self.encode_picture(
4155 &mut pic_params,
4156 output_bitstream,
4157 frame_number,
4158 held_qp.is_some(),
4159 anchor,
4160 );
4161 let retry = match (held_qp, held, &result) {
4162 (Some(crf), Some(rc), Ok(coded))
4163 if rc.rateControlMode == NV_ENC_PARAMS_RC_MODE::NV_ENC_PARAMS_RC_CBR
4164 && (force_idr
4165 || band.is_none()
4166 && coded.len() as f64
4167 > f64::from(rc.averageBitRate) / 8.0
4168 * super::HELD_REFRESH_LIMIT_S) =>
4169 {
4170 let cap = (f64::from(rc.averageBitRate) / 8.0 * super::HELD_KEY_BUDGET_S) as usize;
4171 super::held_key_retry(crf, coded.len(), cap)
4172 }
4173 _ => None,
4174 };
4175 if let Some(coarser) = retry
4176 && self
4177 .hold_rate(
4178 self.codec
4179 .hardware_quantizer(Hardware::Nvenc, coarser as i32),
4180 None,
4181 )
4182 .is_some()
4183 && (force_idr || self.retract_last())
4184 {
4185 pic_params.inputTimeStamp = self
4186 .references
4187 .as_ref()
4188 .map_or(0, ReferenceWindow::next_pts);
4189 result = self.encode_picture(
4190 &mut pic_params,
4191 output_bitstream,
4192 frame_number,
4193 true,
4194 anchor,
4195 );
4196 }
4197 self.refit = None;
4198 let cap = self.cap.take();
4199 let mut held = held;
4200 if let (Some(cap), Ok(coded), None, false) = (cap, &result, retry, force_idr)
4201 && coded.len() > cap
4202 {
4203 let refit = match (held_qp, band, held) {
4204 (Some(crf), Some((from, to)), Some(_)) => {
4205 let q = self.codec.hardware_quantizer(Hardware::Nvenc, crf as i32);
4206 let rest = self
4207 .codec
4208 .quantizer_max()
4209 .min(q + u32::from(i8::MIN.unsigned_abs()));
4210 let end = from + (to - from) * REFIT_SHARE * cap as f64 / coded.len() as f64;
4211 fill_band_map(
4212 &mut self.qp_map,
4213 self.codec,
4214 self.width,
4215 self.height,
4216 (from, end),
4217 q as i32 - rest as i32,
4218 );
4219 point_at_qp_map(&mut pic_params, &mut self.qp_map, nvenc_cur_ver());
4220 Some(end)
4221 }
4222 (None, _, None) => {
4223 held = self.hold_rate(self.codec.quantizer_max(), None);
4224 held.map(|_| 0.0)
4225 }
4226 _ => None,
4227 };
4228 if let Some(end) = refit
4229 && self.retract_last()
4230 {
4231 pic_params.inputTimeStamp = self
4232 .references
4233 .as_ref()
4234 .map_or(0, ReferenceWindow::next_pts);
4235 result = self.encode_picture(
4236 &mut pic_params,
4237 output_bitstream,
4238 frame_number,
4239 true,
4240 anchor,
4241 );
4242 self.refit = Some(end);
4243 }
4244 }
4245 if let (Some(_), Some((from, to)), Ok(_)) = (held, band, &result) {
4246 let to = self.refit.unwrap_or(to);
4247 self.band_log
4248 .push_back((pic_params.inputTimeStamp, from, to));
4249 if self.band_log.len() > BAND_LOG_FRAMES {
4250 self.band_log.pop_front();
4251 }
4252 }
4253 if let Some(rc) = held {
4254 self.encode_config.rcParams = rc;
4255 self.set_band_layout(None);
4256 let status = self.reconfigure(false, false);
4257 if status != NVENCSTATUS::NV_ENC_SUCCESS {
4258 eprintln!(
4259 "[NVENC] Restoring the rate control after a held frame refused ({status:?}): {}",
4260 last_error(&self.nvenc_funcs, self.encoder_session)
4261 );
4262 }
4263 }
4264 self.last_capped = self.peak.is_some_and(|p| p.binding);
4265 if let Ok(coded) = &result {
4266 self.follow_peak(coded.len(), held_qp.is_none());
4267 }
4268 result
4269 }
4270
4271 unsafe fn follow_peak(&mut self, bytes: usize, own: bool) {
4279 let floor = self.current_qp;
4280 let qp = Some(self.last_avg_qp).filter(|q| own && *q > 0);
4281 let Some(peak) = self.peak.as_mut() else {
4282 return;
4283 };
4284 if let Some(bind) = peak.note(bytes as f64 * 8.0, qp.map(|q| q <= floor)) {
4285 self.switch_peak(bind);
4286 }
4287 }
4288
4289 fn bind_refused(&self) -> bool {
4292 self.peak.is_some_and(|p| self.refused_bind == Some(p.bps))
4293 }
4294
4295 unsafe fn switch_peak(&mut self, bind: bool) {
4300 let Some(peak) = self.peak else {
4301 return;
4302 };
4303 if bind && self.bind_refused() {
4304 if let Some(peak) = self.peak.as_mut() {
4305 peak.binding = false;
4306 }
4307 return;
4308 }
4309 let floor = self.current_qp;
4310 let (bps, vbv) = (peak.bps, peak.vbv);
4311 let saved = self.encode_config.rcParams;
4312 let rc = &mut self.encode_config.rcParams;
4313 if bind {
4314 rc.rateControlMode = NV_ENC_PARAMS_RC_MODE::NV_ENC_PARAMS_RC_CBR;
4315 set_cbr_rate(rc, bps, vbv);
4316 rc.set_enableMinQP(1);
4317 rc.minQP.qpInterP = floor;
4318 rc.minQP.qpInterB = floor;
4319 rc.minQP.qpIntra = floor;
4320 } else {
4321 let open = self.open_rc;
4322 rc.rateControlMode = open.rateControlMode;
4323 rc.averageBitRate = open.averageBitRate;
4324 rc.maxBitRate = open.maxBitRate;
4325 rc.vbvBufferSize = open.vbvBufferSize;
4326 rc.vbvInitialDelay = open.vbvInitialDelay;
4327 rc.set_enableMinQP(1);
4330 rc.minQP.qpInterP = 0;
4331 rc.minQP.qpInterB = 0;
4332 rc.minQP.qpIntra = 0;
4333 }
4334 if bind && self.level_for_peak() > self.declared_level() {
4335 self.encode_config.rcParams = saved;
4336 self.refused_bind = Some(bps);
4337 if let Some(peak) = self.peak.as_mut() {
4338 peak.binding = false;
4339 }
4340 return;
4341 }
4342 let status = self.reconfigure(false, false);
4343 if status != NVENCSTATUS::NV_ENC_SUCCESS {
4344 self.encode_config.rcParams = saved;
4345 if bind {
4346 self.refused_bind = Some(bps);
4347 }
4348 if let Some(peak) = self.peak.as_mut() {
4349 peak.binding = !bind;
4350 }
4351 if !std::mem::replace(&mut self.hold_refused, true) {
4352 eprintln!(
4353 "[NVENC] {} the peak of a capped constant quantizer refused ({status:?}): {}",
4354 if bind { "Binding to" } else { "Releasing" },
4355 last_error(&self.nvenc_funcs, self.encoder_session)
4356 );
4357 }
4358 return;
4359 }
4360 if let Some(peak) = self.peak.as_mut() {
4361 peak.binding = bind;
4362 }
4363 }
4364
4365 fn level_for_peak(&self) -> u32 {
4367 let (w, h) = (self.init_params.encodeWidth, self.init_params.encodeHeight);
4368 let fps = self
4369 .init_params
4370 .frameRateNum
4371 .div_ceil(self.init_params.frameRateDen.max(1));
4372 self.level_for(w, h, fps)
4373 }
4374
4375 unsafe fn encode_picture(
4379 &mut self,
4380 pic_params: &mut NV_ENC_PIC_PARAMS,
4381 output_bitstream: NV_ENC_OUTPUT_PTR,
4382 frame_number: u64,
4383 held: bool,
4384 anchor: Option<u8>,
4385 ) -> Result<Vec<u8>, String> {
4386 let encode_fn = self.nvenc_funcs.nvEncEncodePicture.unwrap();
4387 let res = encode_fn(self.encoder_session, pic_params);
4388 if res != NVENCSTATUS::NV_ENC_SUCCESS {
4389 return Err(format!("Encode Picture failed: {:?}", res));
4390 }
4391 if let Some(rows) = self.upload_hash.take() {
4394 let (src, pitch, width, height) = (
4395 self.input_device_ptr,
4396 self.input_pitch,
4397 self.width,
4398 self.height,
4399 );
4400 let cuda = &self.cuda;
4401 self.upload_words = self
4402 .band_hash
4403 .get_or_insert_with(|| BandHash::new(cuda))
4404 .as_mut()
4405 .and_then(|h| h.launch(cuda, src, pitch, width, height, rows));
4406 }
4407
4408 let mut negotiated = Negotiated::new(NV_ENC_LOCK_BITSTREAM {
4409 version: sv(NvStruct::LockBitstream),
4410 outputBitstream: output_bitstream,
4411 ..Default::default()
4412 });
4413 let lock_params = &mut negotiated.value;
4414 lock_params.set_doNotWait(0);
4415 let lock_fn = self.nvenc_funcs.nvEncLockBitstream.unwrap();
4416 let status = lock_fn(self.encoder_session, lock_params);
4417 if status != NVENCSTATUS::NV_ENC_SUCCESS {
4418 return Err(format!("Lock Bitstream failed: {status:?}"));
4419 }
4420
4421 let data_ptr = lock_params.bitstreamBufferPtr as *const u8;
4422 let data_size = lock_params.bitstreamSizeInBytes as usize;
4423 let header_sz = if self.omit_stripe_headers {
4424 0
4425 } else {
4426 VIDEO_HEADER_LEN
4427 };
4428 let mut output = Vec::with_capacity(header_sz + data_size);
4429 if !held {
4430 self.last_avg_qp = lock_params.frameAvgQP;
4431 self.last_quality = match lock_params.frameAvgQP {
4432 0 => None,
4433 q => Some(self.codec.hardware_quality_index(Hardware::Nvenc, q)),
4434 };
4435 self.last_bytes = Some(data_size);
4436 } else {
4437 self.held_bytes = data_size;
4438 }
4439 let frame_type = match lock_params.pictureType {
4440 NV_ENC_PIC_TYPE::NV_ENC_PIC_TYPE_IDR => FRAME_KEY,
4441 NV_ENC_PIC_TYPE::NV_ENC_PIC_TYPE_I => FRAME_INTRA,
4442 _ => FRAME_DELTA,
4443 };
4444 let mut wire_type = frame_type;
4445 if let Some(references) = &mut self.references {
4446 let shared = references.acknowledging() && references.predicts_from_shared();
4447 self.last_reference =
4448 references.record_marked(frame_number as u16, frame_type != FRAME_DELTA, anchor);
4449 if anchor.is_some() && frame_type == FRAME_DELTA && shared {
4450 wire_type |= FRAME_ANCHOR;
4451 }
4452 }
4453
4454 if !self.omit_stripe_headers {
4455 push_video_header(
4456 &mut output,
4457 self.codec,
4458 wire_type,
4459 frame_number as u16,
4460 0,
4461 self.width as u16,
4462 self.height as u16,
4463 self.last_reference,
4464 );
4465 }
4466
4467 if data_size > 0 && !data_ptr.is_null() {
4468 let slice = std::slice::from_raw_parts(data_ptr, data_size);
4469 if self.codec == Codec::H264 && self.references.is_some() {
4470 let mut widened = true;
4471 for nal in crate::encoders::codec::annexb_nals(slice) {
4472 widened &= self.frame_num.push_unit(&[0, 0, 0, 1], nal, &mut output);
4473 }
4474 if !widened && let Some(references) = &mut self.references {
4475 references.reset();
4478 }
4479 } else {
4480 output.extend_from_slice(slice);
4481 }
4482 }
4483 if frame_type == FRAME_KEY
4484 && self.codec == Codec::H264
4485 && let Some(references) = &mut self.references
4486 {
4487 references.set_frame_num_range(h264_frame_num_range(&output[header_sz..]).unwrap_or(0));
4488 }
4489
4490 (self.nvenc_funcs.nvEncUnlockBitstream.unwrap())(self.encoder_session, output_bitstream);
4491 Ok(output)
4492 }
4493
4494 pub fn encode(
4520 &mut self,
4521 dmabuf: &Dmabuf,
4522 frame_number: u64,
4523 crf: u32,
4524 force_idr: bool,
4525 ) -> Result<Vec<u8>, String> {
4526 self.encode_frame(dmabuf, frame_number, crf, force_idr, false)
4527 }
4528
4529 pub fn encode_after_blit(
4535 &mut self,
4536 dmabuf: &Dmabuf,
4537 frame_number: u64,
4538 crf: u32,
4539 force_idr: bool,
4540 ) -> Result<Vec<u8>, String> {
4541 self.encode_frame(dmabuf, frame_number, crf, force_idr, true)
4542 }
4543
4544 pub fn blit_semaphore_fd(&mut self) -> Result<std::os::fd::OwnedFd, String> {
4548 use std::os::fd::IntoRawFd;
4549 unsafe {
4550 let (Some(import), Some(_), Some(destroy)) = (
4551 self.cuda.cuImportExternalSemaphore,
4552 self.cuda.cuWaitExternalSemaphoresAsync,
4553 self.cuda.cuDestroyExternalSemaphore,
4554 ) else {
4555 return Err("the CUDA driver has no external semaphores".into());
4556 };
4557 let mut uuid = CUuuid { bytes: [0; 16] };
4558 if (self.cuda.cuDeviceGetUuid)(&mut uuid, self.cuda_device) != CUresult::CUDA_SUCCESS {
4559 return Err("the CUDA device has no UUID to find its Vulkan device by".into());
4560 }
4561 let (cuda_fd, server_fd) =
4562 super::blit_semaphore::opaque_fd_pair(uuid.bytes.map(|b| b.to_ne_bytes()[0]))?;
4563 let _ = (self.cuda.cuCtxPushCurrent_v2)(self.cuda_context);
4564 if !self.blit_semaphore.is_null() {
4565 destroy(self.blit_semaphore);
4566 self.blit_semaphore = ptr::null_mut();
4567 }
4568 let mut desc = CUDA_EXTERNAL_SEMAPHORE_HANDLE_DESC {
4569 type_: CUexternalSemaphoreHandleType::CU_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD,
4570 ..Default::default()
4571 };
4572 desc.handle.fd = cuda_fd.as_raw_fd();
4573 let mut semaphore: CUexternalSemaphore = ptr::null_mut();
4574 let r = import(&mut semaphore, &desc);
4575 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
4576 if r != CUresult::CUDA_SUCCESS {
4577 return Err(format!(
4578 "CUDA refused the semaphore ({})",
4579 Self::get_error_string(&self.cuda, r)
4580 ));
4581 }
4582 let _ = cuda_fd.into_raw_fd();
4584 self.blit_semaphore = semaphore;
4585 Ok(server_fd)
4586 }
4587 }
4588
4589 fn encode_frame(
4590 &mut self,
4591 dmabuf: &Dmabuf,
4592 frame_number: u64,
4593 crf: u32,
4594 force_idr: bool,
4595 after_blit: bool,
4596 ) -> Result<Vec<u8>, String> {
4597 unsafe {
4598 self.reconfigure_if_needed(crf);
4599 let fd = dmabuf.handles().next().ok_or("No handles")?.as_raw_fd();
4600 let fmt = dmabuf.format();
4601 let modifier: u64 = fmt.modifier.into();
4602 let identity = DmaBufIdentity::probe(fd, modifier, self.width, self.height);
4603 let _ = (self.cuda.cuCtxPushCurrent_v2)(self.cuda_context);
4604
4605 if after_blit {
4608 let wait = self.cuda.cuWaitExternalSemaphoresAsync;
4609 let queued = match wait {
4610 Some(wait) if !self.blit_semaphore.is_null() => {
4611 let params = CUDA_EXTERNAL_SEMAPHORE_WAIT_PARAMS::default();
4612 wait(&self.blit_semaphore, ¶ms, 1, ptr::null_mut())
4613 }
4614 _ => CUresult::CUDA_ERROR_NOT_INITIALIZED,
4615 };
4616 if queued != CUresult::CUDA_SUCCESS {
4617 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
4618 return Err(format!(
4619 "the wait for the server's blit could not be queued ({})",
4620 Self::get_error_string(&self.cuda, queued)
4621 ));
4622 }
4623 }
4624
4625 if self
4629 .dmabuf_cache
4630 .get(&fd)
4631 .is_some_and(|c| c.identity != identity)
4632 && let Some(stale) = self.dmabuf_cache.remove(&fd)
4633 {
4634 self.release_dmabuf_import(stale);
4635 }
4636
4637 if !self.dmabuf_cache.contains_key(&fd) {
4638 let Some(egl) = self.egl.clone() else {
4639 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
4640 return Err("a session opened without an EGL display imports no dmabuf".into());
4641 };
4642 let stride = dmabuf.strides().next().unwrap_or(0) as i32;
4643 let offset = dmabuf.offsets().next().unwrap_or(0) as i32;
4644
4645 let attribs = [
4646 EGL_WIDTH,
4647 self.width as i32,
4648 EGL_HEIGHT,
4649 self.height as i32,
4650 EGL_LINUX_DRM_FOURCC_EXT,
4651 egl_import_fourcc(fmt.code) as i32,
4652 EGL_DMA_BUF_PLANE0_FD_EXT,
4653 fd,
4654 EGL_DMA_BUF_PLANE0_OFFSET_EXT,
4655 offset,
4656 EGL_DMA_BUF_PLANE0_PITCH_EXT,
4657 stride,
4658 EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
4659 (modifier & 0xFFFFFFFF) as i32,
4660 EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT,
4661 (modifier >> 32) as i32,
4662 EGL_NONE,
4663 ];
4664
4665 let egl_image = (egl.eglCreateImageKHR)(
4666 self.egl_display,
4667 ptr::null_mut(),
4668 EGL_LINUX_DMA_BUF_EXT,
4669 ptr::null_mut(),
4670 attribs.as_ptr(),
4671 );
4672 if egl_image == EGL_NO_IMAGE_KHR {
4673 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
4674 return Err("Failed to create EGLImage".into());
4675 }
4676
4677 let mut cuda_resource: CUgraphicsResource = ptr::null_mut();
4678 if (self.cuda.cuGraphicsEGLRegisterImage)(&mut cuda_resource, egl_image, 1)
4679 != CUresult::CUDA_SUCCESS
4680 {
4681 (egl.eglDestroyImageKHR)(self.egl_display, egl_image);
4682 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
4683 return Err("Failed to register EGLImage".into());
4684 }
4685
4686 let mut egl_frame: CUeglFrame = std::mem::zeroed();
4687 if (self.cuda.cuGraphicsResourceGetMappedEglFrame)(
4688 &mut egl_frame,
4689 cuda_resource,
4690 0,
4691 0,
4692 ) != CUresult::CUDA_SUCCESS
4693 {
4694 (self.cuda.cuGraphicsUnregisterResource)(cuda_resource);
4695 (egl.eglDestroyImageKHR)(self.egl_display, egl_image);
4696 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
4697 return Err("Failed to map EGL frame".into());
4698 }
4699
4700 let input = match (
4701 self.direct_dmabuf,
4702 direct_plane(&egl_frame, self.width, self.height),
4703 fourcc_nvenc_format(fmt.code),
4704 ) {
4705 (true, Some(plane), Some(format)) => {
4706 self.register_direct_input(&egl_frame, plane, format)
4707 }
4708 _ => DmaBufInput::Copy,
4709 };
4710 crate::log::debug!(
4711 "[NVENC] dmabuf imported as a {} frame ({} planes, {}x{}, pitch {}, {} channels of element format {}): {}.",
4712 match egl_frame.frame_type {
4713 CU_EGL_FRAME_TYPE_PITCH => "pitch-linear",
4714 CU_EGL_FRAME_TYPE_ARRAY => "CUDA-array",
4715 _ => "unknown-kind",
4716 },
4717 egl_frame.plane_count,
4718 egl_frame.width,
4719 egl_frame.height,
4720 egl_frame.pitch,
4721 egl_frame.num_channels,
4722 egl_frame.cu_format,
4723 match input {
4724 DmaBufInput::Direct { .. } => "encoding in place",
4725 DmaBufInput::Copy => "copying per frame",
4726 }
4727 );
4728
4729 let tex = if self.csc.is_some() && egl_frame.frame_type == CU_EGL_FRAME_TYPE_ARRAY {
4732 ChromaConvert::texture_for(&self.cuda, egl_frame.frame.p_array[0])
4733 } else {
4734 0
4735 };
4736 if self.dmabuf_cache.len() >= DMABUF_IMPORTS
4737 && let Some(&stalest) = self
4738 .dmabuf_cache
4739 .iter()
4740 .min_by_key(|(_, c)| c.last_use)
4741 .map(|(fd, _)| fd)
4742 && let Some(stale) = self.dmabuf_cache.remove(&stalest)
4743 {
4744 self.release_dmabuf_import(stale);
4745 }
4746 self.dmabuf_cache.insert(
4747 fd,
4748 CachedDmaBuf {
4749 identity,
4750 last_use: frame_number,
4751 egl_image,
4752 cuda_resource,
4753 egl_frame,
4754 input,
4755 tex,
4756 },
4757 );
4758 }
4759
4760 let (egl_frame, input, tex) = {
4761 let cached = self.dmabuf_cache.get_mut(&fd).unwrap();
4762 cached.last_use = frame_number;
4763 (cached.egl_frame, cached.input, cached.tex)
4764 };
4765 let (mapped, format) = match input {
4766 DmaBufInput::Direct { mapped, format, .. } => (mapped, format),
4767 DmaBufInput::Copy => {
4768 let mut copy_params = CUDA_MEMCPY2D {
4769 srcMemoryType: CUmemorytype::CU_MEMORYTYPE_DEVICE,
4770 srcHost: ptr::null(),
4771 srcDevice: 0,
4772 srcArray: ptr::null_mut(),
4773 srcPitch: 0,
4774 dstMemoryType: CUmemorytype::CU_MEMORYTYPE_DEVICE,
4775 dstHost: ptr::null_mut(),
4776 dstDevice: self.input_device_ptr,
4777 dstArray: ptr::null_mut(),
4778 dstPitch: self.input_pitch,
4779 WidthInBytes: (self.width * 4) as usize,
4780 Height: self.height as usize,
4781 ..Default::default()
4782 };
4783 if egl_frame.frame_type == CU_EGL_FRAME_TYPE_ARRAY {
4784 copy_params.srcMemoryType = CUmemorytype::CU_MEMORYTYPE_ARRAY;
4785 copy_params.srcArray = egl_frame.frame.p_array[0];
4786 } else {
4787 copy_params.srcMemoryType = CUmemorytype::CU_MEMORYTYPE_DEVICE;
4788 copy_params.srcDevice = egl_frame.frame.p_pitch[0] as CUdeviceptr;
4789 copy_params.srcPitch = egl_frame.pitch as usize;
4790 }
4791 if let Some(format) = fourcc_nvenc_format(fmt.code)
4794 && let Err(e) = self.set_input_format(format)
4795 {
4796 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
4797 return Err(e);
4798 }
4799 if (self.cuda.cuMemcpy2DAsync_v2)(©_params, ptr::null_mut())
4800 != CUresult::CUDA_SUCCESS
4801 {
4802 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
4803 return Err("Sanitization copy failed".into());
4804 }
4805 (self.mapped_input_buffer, self.input_format)
4806 }
4807 };
4808
4809 let swap = format == NV_ENC_BUFFER_FORMAT::NV_ENC_BUFFER_FORMAT_ABGR;
4813 let converted = match input {
4814 DmaBufInput::Copy => {
4815 self.convert_packed(self.input_device_ptr, self.input_pitch, swap)
4816 }
4817 DmaBufInput::Direct { .. } if egl_frame.frame_type == CU_EGL_FRAME_TYPE_PITCH => {
4818 self.convert_packed(
4819 egl_frame.frame.p_pitch[0] as CUdeviceptr,
4820 egl_frame.pitch as usize,
4821 swap,
4822 )
4823 }
4824 DmaBufInput::Direct { .. } => self.convert_texture(tex, swap),
4825 };
4826 let (mapped, format) = match converted {
4827 Ok(Some(converted)) => converted,
4828 Ok(None) => (mapped, format),
4829 Err(e) => {
4830 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
4831 return Err(e);
4832 }
4833 };
4834
4835 let result = self.submit_frame(mapped, format, frame_number, force_idr);
4836 if result.is_err() {
4837 (self.cuda.cuStreamSynchronize)(ptr::null_mut());
4838 }
4839 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
4840 result
4841 }
4842 }
4843
4844 unsafe fn register_direct_input(
4850 &mut self,
4851 frame: &CUeglFrame,
4852 plane: DirectPlane,
4853 format: NV_ENC_BUFFER_FORMAT,
4854 ) -> DmaBufInput {
4855 let (resource_type, resource, pitch) = match plane {
4856 DirectPlane::Pitch(pitch) => (
4857 NV_ENC_INPUT_RESOURCE_TYPE::NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR,
4858 frame.frame.p_pitch[0],
4859 pitch,
4860 ),
4861 DirectPlane::Array(row_bytes) => (
4862 NV_ENC_INPUT_RESOURCE_TYPE::NV_ENC_INPUT_RESOURCE_TYPE_CUDAARRAY,
4863 frame.frame.p_array[0] as *mut c_void,
4864 row_bytes,
4865 ),
4866 };
4867 let mut reg_res = NV_ENC_REGISTER_RESOURCE {
4868 version: sv(NvStruct::RegisterResource),
4869 resourceType: resource_type,
4870 width: self.width,
4871 height: self.height,
4872 resourceToRegister: resource,
4873 pitch,
4874 bufferFormat: format,
4875 bufferUsage: NV_ENC_BUFFER_USAGE::NV_ENC_INPUT_IMAGE,
4876 ..Default::default()
4877 };
4878 let st =
4879 (self.nvenc_funcs.nvEncRegisterResource.unwrap())(self.encoder_session, &mut reg_res);
4880 if st != NVENCSTATUS::NV_ENC_SUCCESS {
4881 eprintln!(
4882 "[NVENC] dmabuf plane registration ({plane:?}) refused ({st:?}); copying per frame."
4883 );
4884 return DmaBufInput::Copy;
4885 }
4886 let mut map_params = NV_ENC_MAP_INPUT_RESOURCE {
4887 version: sv(NvStruct::MapInputResource),
4888 registeredResource: reg_res.registeredResource,
4889 ..Default::default()
4890 };
4891 let st = (self.nvenc_funcs.nvEncMapInputResource.unwrap())(
4892 self.encoder_session,
4893 &mut map_params,
4894 );
4895 if st != NVENCSTATUS::NV_ENC_SUCCESS {
4896 (self.nvenc_funcs.nvEncUnregisterResource.unwrap())(
4897 self.encoder_session,
4898 reg_res.registeredResource,
4899 );
4900 eprintln!("[NVENC] dmabuf plane mapping refused ({st:?}); copying per frame.");
4901 return DmaBufInput::Copy;
4902 }
4903 DmaBufInput::Direct {
4904 registered: reg_res.registeredResource,
4905 mapped: map_params.mappedResource,
4906 format,
4907 }
4908 }
4909
4910 pub fn encode_cpu_argb(
4913 &mut self,
4914 argb: &[u8],
4915 src_stride: usize,
4916 frame_number: u64,
4917 crf: u32,
4918 force_idr: bool,
4919 ) -> Result<Vec<u8>, String> {
4920 self.encode_cpu_packed(argb, src_stride, false, frame_number, crf, force_idr)
4921 }
4922
4923 pub fn encode_cpu_packed(
4948 &mut self,
4949 pixels: &[u8],
4950 src_stride: usize,
4951 rgba_input: bool,
4952 frame_number: u64,
4953 crf: u32,
4954 force_idr: bool,
4955 ) -> Result<Vec<u8>, String> {
4956 unsafe {
4957 self.reconfigure_if_needed(crf);
4958 let _ = (self.cuda.cuCtxPushCurrent_v2)(self.cuda_context);
4959
4960 let width_bytes = (self.width * 4) as usize;
4961 let rows = self.height as usize;
4962 let needed = if rows == 0 {
4963 0
4964 } else {
4965 src_stride * (rows - 1) + width_bytes
4966 };
4967 if src_stride < width_bytes || pixels.len() < needed {
4968 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
4969 return Err(format!(
4970 "packed buffer too small: len={} need>={} (stride={}, {}x{})",
4971 pixels.len(),
4972 needed,
4973 src_stride,
4974 self.width,
4975 self.height
4976 ));
4977 }
4978
4979 let format = if rgba_input {
4980 NV_ENC_BUFFER_FORMAT::NV_ENC_BUFFER_FORMAT_ABGR
4981 } else {
4982 NV_ENC_BUFFER_FORMAT::NV_ENC_BUFFER_FORMAT_ARGB
4983 };
4984 if self.csc.is_none()
4987 && let Err(e) = self.set_input_format(format)
4988 {
4989 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
4990 return Err(e);
4991 }
4992
4993 if self.pin_uploads {
4994 self.pin_host_source(pixels.as_ptr() as usize, pixels.len());
4995 }
4996
4997 let copy = CUDA_MEMCPY2D {
4998 srcMemoryType: CUmemorytype::CU_MEMORYTYPE_HOST,
4999 srcHost: pixels.as_ptr() as *const c_void,
5000 srcPitch: src_stride,
5001 dstMemoryType: CUmemorytype::CU_MEMORYTYPE_DEVICE,
5002 dstDevice: self.input_device_ptr,
5003 dstPitch: self.input_pitch,
5004 WidthInBytes: width_bytes,
5005 Height: rows,
5006 ..Default::default()
5007 };
5008 let copied = (self.cuda.cuMemcpy2DAsync_v2)(©, ptr::null_mut());
5009 if copied != CUresult::CUDA_SUCCESS {
5010 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
5011 return Err(format!(
5012 "packed host->device upload failed ({}; host {:#x}+{} at stride {}, {}x{}, device pitch {}, pinned {})",
5013 Self::get_error_string(&self.cuda, copied),
5014 pixels.as_ptr() as usize,
5015 pixels.len(),
5016 src_stride,
5017 self.width,
5018 self.height,
5019 self.input_pitch,
5020 self.pin_uploads
5021 ));
5022 }
5023
5024 let (mapped, submitted) =
5025 match self.convert_packed(self.input_device_ptr, self.input_pitch, rgba_input) {
5026 Ok(Some(converted)) => converted,
5027 Ok(None) => (self.mapped_input_buffer, self.input_format),
5028 Err(e) => {
5029 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
5030 return Err(e);
5031 }
5032 };
5033 let result = self.submit_frame(mapped, submitted, frame_number, force_idr);
5034 if result.is_err() {
5035 (self.cuda.cuStreamSynchronize)(ptr::null_mut());
5036 }
5037 self.upload_hash = None;
5038 if let Some(words) = self.upload_words.take()
5039 && let Some(Some(hash)) = self.band_hash.as_mut()
5040 {
5041 self.upload_hashes = hash.read(&self.cuda, words);
5042 }
5043 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
5044 result
5045 }
5046 }
5047
5048 pub fn encode_client_packed(
5053 &mut self,
5054 pixels: &[u8],
5055 src_stride: usize,
5056 rgba_input: bool,
5057 frame_number: u64,
5058 crf: u32,
5059 force_idr: bool,
5060 ) -> Result<Vec<u8>, String> {
5061 let pin = std::mem::replace(&mut self.pin_uploads, false);
5062 let out =
5063 self.encode_cpu_packed(pixels, src_stride, rgba_input, frame_number, crf, force_idr);
5064 self.pin_uploads = pin;
5065 out
5066 }
5067
5068 unsafe fn convert_texture(
5073 &self,
5074 tex: CUtexObject,
5075 rgba_input: bool,
5076 ) -> Result<Option<(NV_ENC_INPUT_PTR, NV_ENC_BUFFER_FORMAT)>, String> {
5077 match self.csc.as_ref() {
5078 Some(csc) if tex != 0 => {
5079 csc.run_texture(&self.cuda, tex, self.width, self.height, rgba_input)?;
5080 Ok(Some(csc.input()))
5081 }
5082 _ if self.bit_depth > 8 => {
5083 Err("the driver gave no texture for the import a 10-bit session converts".into())
5084 }
5085 _ => Ok(None),
5086 }
5087 }
5088
5089 unsafe fn convert_packed(
5093 &self,
5094 src: CUdeviceptr,
5095 src_pitch: usize,
5096 rgba_input: bool,
5097 ) -> Result<Option<(NV_ENC_INPUT_PTR, NV_ENC_BUFFER_FORMAT)>, String> {
5098 match self.csc.as_ref() {
5099 Some(csc) => {
5100 csc.run(
5101 &self.cuda,
5102 src,
5103 src_pitch,
5104 self.width,
5105 self.height,
5106 rgba_input,
5107 )?;
5108 Ok(Some(csc.input()))
5109 }
5110 None => Ok(None),
5111 }
5112 }
5113
5114 pub fn width(&self) -> u32 {
5116 self.width
5117 }
5118
5119 pub fn height(&self) -> u32 {
5121 self.height
5122 }
5123
5124 pub(crate) fn push_context(&self) -> bool {
5128 unsafe { (self.cuda.cuCtxPushCurrent_v2)(self.cuda_context) == CUresult::CUDA_SUCCESS }
5129 }
5130
5131 pub(crate) fn pop_context(&self) {
5133 unsafe {
5134 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
5135 }
5136 }
5137
5138 pub fn release_external_input(&mut self) {
5144 if self.external_inputs.is_empty() {
5145 return;
5146 }
5147 unsafe {
5148 let _ = (self.cuda.cuCtxPushCurrent_v2)(self.cuda_context);
5149 self.unmap_external_inputs();
5150 let _ = (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
5151 }
5152 }
5153
5154 unsafe fn unmap_external_inputs(&mut self) {
5156 for ext in std::mem::take(&mut self.external_inputs) {
5157 unsafe { self.unmap_one_input(ext) };
5158 }
5159 }
5160
5161 unsafe fn unmap_one_input(&self, ext: ExternalInput) {
5162 (self.nvenc_funcs.nvEncUnmapInputResource.unwrap())(self.encoder_session, ext.mapped);
5163 (self.nvenc_funcs.nvEncUnregisterResource.unwrap())(self.encoder_session, ext.registered);
5164 }
5165
5166 unsafe fn register_external_input(
5174 &mut self,
5175 device_ptr: CUdeviceptr,
5176 pitch: usize,
5177 format: NV_ENC_BUFFER_FORMAT,
5178 ) -> Result<NV_ENC_INPUT_PTR, String> {
5179 let (width, height) = (self.width, self.height);
5180 let fits = |e: &ExternalInput| {
5181 e.pitch == pitch && e.format == format && e.width == width && e.height == height
5182 };
5183 if let Some(ext) = self
5184 .external_inputs
5185 .iter()
5186 .find(|e| e.device_ptr == device_ptr && fits(e))
5187 {
5188 return Ok(ext.mapped);
5189 }
5190 let (kept, stale): (Vec<_>, Vec<_>) = std::mem::take(&mut self.external_inputs)
5191 .into_iter()
5192 .partition(fits);
5193 self.external_inputs = kept;
5194 for ext in stale {
5195 unsafe { self.unmap_one_input(ext) };
5196 }
5197 if self.external_inputs.len() >= EXTERNAL_INPUTS {
5198 let oldest = self.external_inputs.remove(0);
5199 unsafe { self.unmap_one_input(oldest) };
5200 }
5201 let mut reg_res = NV_ENC_REGISTER_RESOURCE {
5202 version: sv(NvStruct::RegisterResource),
5203 resourceType: NV_ENC_INPUT_RESOURCE_TYPE::NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR,
5204 width: self.width,
5205 height: self.height,
5206 resourceToRegister: device_ptr as *mut c_void,
5207 pitch: pitch as u32,
5208 bufferFormat: format,
5209 bufferUsage: NV_ENC_BUFFER_USAGE::NV_ENC_INPUT_IMAGE,
5210 ..Default::default()
5211 };
5212 let st =
5213 (self.nvenc_funcs.nvEncRegisterResource.unwrap())(self.encoder_session, &mut reg_res);
5214 if st != NVENCSTATUS::NV_ENC_SUCCESS {
5215 return Err(format!(
5216 "failed to register the captured frame as an NVENC input ({st:?})"
5217 ));
5218 }
5219 let mut map_params = NV_ENC_MAP_INPUT_RESOURCE {
5220 version: sv(NvStruct::MapInputResource),
5221 registeredResource: reg_res.registeredResource,
5222 ..Default::default()
5223 };
5224 let st = (self.nvenc_funcs.nvEncMapInputResource.unwrap())(
5225 self.encoder_session,
5226 &mut map_params,
5227 );
5228 if st != NVENCSTATUS::NV_ENC_SUCCESS {
5229 (self.nvenc_funcs.nvEncUnregisterResource.unwrap())(
5230 self.encoder_session,
5231 reg_res.registeredResource,
5232 );
5233 return Err(format!(
5234 "failed to map the captured frame as an NVENC input ({st:?})"
5235 ));
5236 }
5237 self.external_inputs.push(ExternalInput {
5238 device_ptr,
5239 pitch,
5240 width: self.width,
5241 height: self.height,
5242 format,
5243 registered: reg_res.registeredResource,
5244 mapped: map_params.mappedResource,
5245 });
5246 Ok(map_params.mappedResource)
5247 }
5248
5249 pub fn encode_cuda_pitch(
5264 &mut self,
5265 device_ptr: CUdeviceptr,
5266 pitch: usize,
5267 rgba: bool,
5268 frame_number: u64,
5269 crf: u32,
5270 force_idr: bool,
5271 ) -> Result<Vec<u8>, String> {
5272 unsafe {
5273 self.reconfigure_if_needed(crf);
5274 if pitch < self.width as usize * 4 || !pitch.is_multiple_of(4) {
5277 return Err(format!(
5278 "external input pitch {pitch} does not cover {}x{} at 4-byte alignment",
5279 self.width, self.height
5280 ));
5281 }
5282 let _ = (self.cuda.cuCtxPushCurrent_v2)(self.cuda_context);
5283 let format = if rgba {
5284 NV_ENC_BUFFER_FORMAT::NV_ENC_BUFFER_FORMAT_ABGR
5285 } else {
5286 NV_ENC_BUFFER_FORMAT::NV_ENC_BUFFER_FORMAT_ARGB
5287 };
5288 let submitted = match self.convert_packed(device_ptr, pitch, rgba) {
5289 Ok(Some(converted)) => Ok(converted),
5290 Ok(None) => self
5291 .register_external_input(device_ptr, pitch, format)
5292 .map(|m| (m, format)),
5293 Err(e) => Err(e),
5294 };
5295 let (mapped, submitted) = match submitted {
5296 Ok(pair) => pair,
5297 Err(e) => {
5298 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
5299 return Err(e);
5300 }
5301 };
5302 let result = self.submit_frame(mapped, submitted, frame_number, force_idr);
5303 if result.is_err() {
5304 (self.cuda.cuStreamSynchronize)(ptr::null_mut());
5305 }
5306 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
5307 result
5308 }
5309 }
5310
5311 pub fn hash_next_upload(&mut self, rows: u32) -> bool {
5316 self.upload_hashes = None;
5317 self.upload_words = None;
5318 let available = unsafe {
5319 let _ = (self.cuda.cuCtxPushCurrent_v2)(self.cuda_context);
5320 let cuda = &self.cuda;
5321 let loaded = self
5322 .band_hash
5323 .get_or_insert_with(|| BandHash::new(cuda))
5324 .is_some();
5325 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
5326 loaded
5327 };
5328 self.upload_hash = available.then_some(rows);
5329 available
5330 }
5331
5332 pub fn take_upload_hashes(&mut self) -> Option<Vec<u64>> {
5334 self.upload_hashes.take()
5335 }
5336
5337 pub fn band_hashes(
5342 &mut self,
5343 device_ptr: CUdeviceptr,
5344 pitch: usize,
5345 width: u32,
5346 height: u32,
5347 rows: u32,
5348 ) -> Option<Vec<u64>> {
5349 if pitch < width as usize * 4 {
5350 return None;
5351 }
5352 unsafe {
5353 let _ = (self.cuda.cuCtxPushCurrent_v2)(self.cuda_context);
5354 let cuda = &self.cuda;
5355 let hashes = self
5356 .band_hash
5357 .get_or_insert_with(|| BandHash::new(cuda))
5358 .as_mut()
5359 .and_then(|h| h.run(cuda, device_ptr, pitch, width, height, rows));
5360 (self.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
5361 hashes
5362 }
5363 }
5364}
5365
5366#[cfg(test)]
5367mod tests {
5368 use super::*;
5369
5370 #[test]
5374 fn every_codec_declares_the_conversion_matrix() {
5375 for codec in [Codec::H264, Codec::H265, Codec::Av1] {
5376 for fullcolor in [false, true] {
5377 let mut config = NV_ENC_CONFIG {
5378 version: sv(NvStruct::Config),
5379 ..Default::default()
5380 };
5381 NvencEncoder::configure_codec(
5382 &mut config,
5383 codec,
5384 fullcolor,
5385 8,
5386 nvenc_level(codec, 1280, 720, 60, 0, true),
5387 1,
5388 0,
5389 &NvencTuning::default(),
5390 );
5391 let got = unsafe {
5392 match codec {
5393 Codec::H265 => {
5394 config
5395 .encodeCodecConfig
5396 .hevcConfig
5397 .hevcVUIParameters
5398 .colourMatrix
5399 }
5400 Codec::Av1 => config.encodeCodecConfig.av1Config.matrixCoefficients,
5401 _ => {
5402 config
5403 .encodeCodecConfig
5404 .h264Config
5405 .h264VUIParameters
5406 .colourMatrix
5407 }
5408 }
5409 };
5410 assert_eq!(
5411 got as u32,
5412 NV_ENC_VUI_MATRIX_COEFFS::NV_ENC_VUI_MATRIX_COEFFS_BT709 as u32,
5413 "{codec:?} 4:4:4={fullcolor}"
5414 );
5415 }
5416 }
5417 }
5418
5419 #[test]
5420 fn anchors_require_the_negotiated_api_and_device_capacity() {
5421 for acked in [false, true] {
5422 assert_eq!(anchor_count(Codec::Av1, 12, true, Some(6), 4, acked), 0);
5423 assert_eq!(anchor_count(Codec::Av1, 13, false, Some(6), 4, acked), 0);
5424 assert_eq!(anchor_count(Codec::Av1, 13, true, None, 4, acked), 0);
5425 assert_eq!(anchor_count(Codec::Av1, 13, true, Some(0), 4, acked), 0);
5426 assert_eq!(anchor_count(Codec::Av1, 13, true, Some(6), 2, acked), 0);
5427 assert_eq!(anchor_count(Codec::Av1, 13, true, Some(1), 3, acked), 1);
5428 for api in [11, 12, 13] {
5429 assert_eq!(
5430 anchor_count(Codec::H265, api, true, Some(8), 4, acked),
5431 ANCHORS
5432 );
5433 for codec in [Codec::H264, Codec::H265] {
5434 assert_eq!(anchor_count(codec, api, false, Some(8), 4, acked), 0);
5435 assert_eq!(anchor_count(codec, api, true, Some(0), 4, acked), 0);
5436 assert_eq!(anchor_count(codec, api, true, Some(8), 2, acked), 0);
5437 }
5438 }
5439 }
5440 assert_eq!(anchor_count(Codec::Av1, 13, true, Some(6), 4, false), 1);
5441 assert_eq!(
5442 anchor_count(Codec::Av1, 13, true, Some(6), 4, true),
5443 ANCHORS
5444 );
5445 for api in [11, 12, 13] {
5446 for acked in [false, true] {
5447 assert_eq!(anchor_count(Codec::H264, api, true, Some(8), 4, acked), 1);
5448 }
5449 }
5450 assert_eq!(anchor_count(Codec::Av1, 13, true, Some(8), 3, true), 1);
5452 assert_eq!(anchor_count(Codec::Av1, 13, true, Some(1), 4, true), 1);
5453 }
5454
5455 #[test]
5458 fn av1_anchor_configuration_matches_the_reference_window() {
5459 for anchors in [0, 1] {
5460 let mut config = NV_ENC_CONFIG::default();
5461 NvencEncoder::configure_codec(
5462 &mut config,
5463 Codec::Av1,
5464 false,
5465 8,
5466 13,
5467 AV1_REFERENCES,
5468 anchors,
5469 &NvencTuning::default(),
5470 );
5471 let av1 = unsafe { config.encodeCodecConfig.av1Config };
5472 assert_eq!(av1.enableLTR(), anchors as u32);
5473 assert_eq!(av1.ltrNumFrames, anchors as u32);
5474 assert_eq!(av1.maxNumRefFramesInDPB, AV1_REFERENCES);
5475 }
5476 }
5477
5478 #[test]
5482 fn bands_left_out_of_the_predictions_are_coded_again() {
5483 let sweep = || VecDeque::from([(10, 0.0, 0.1), (11, 0.1, 0.2), (12, 0.2, 0.3)]);
5484 let mut log = sweep();
5485 assert_eq!(
5486 reach_back(&mut log, 11, Some((0.3, 0.4)), true),
5487 (Some((0.1, 0.4)), false)
5488 );
5489 assert_eq!(log, VecDeque::from([(10, 0.0, 0.1)]));
5490 assert_eq!(
5491 reach_back(&mut sweep(), 11, None, false),
5492 (None, true),
5493 "a frame holding nothing loses them"
5494 );
5495 assert_eq!(
5496 reach_back(&mut sweep(), 11, None, true),
5497 (None, false),
5498 "a frame held whole codes them"
5499 );
5500 assert_eq!(
5501 reach_back(&mut sweep(), 13, Some((0.3, 0.4)), true),
5502 (Some((0.3, 0.4)), false),
5503 "no band among the frames left out"
5504 );
5505 }
5506
5507 #[test]
5510 fn a_band_covers_its_share_of_the_blocks() {
5511 let mut map = Vec::new();
5512 fill_band_map(&mut map, Codec::H264, 1920, 1080, (0.0, 1.0 / 64.0), -33);
5513 assert_eq!(map.len(), 120 * 68);
5514 assert_eq!(map.iter().filter(|&&d| d == -33).count(), 128);
5515 assert!(map[..128].iter().all(|&d| d == -33) && map[128..].iter().all(|&d| d == 0));
5516 fill_band_map(&mut map, Codec::H265, 1920, 1080, (0.5, 0.75), -80);
5517 assert_eq!(map.len(), 60 * 34);
5518 assert!(map[..1020].iter().all(|&d| d == 0) && map[1530..].iter().all(|&d| d == 0));
5519 assert!(
5520 map[1020..1530].iter().all(|&d| d == -51),
5521 "the delta is bounded to the quantizer range"
5522 );
5523 fill_band_map(&mut map, Codec::Av1, 1920, 1080, (0.0, 1.0), -211);
5524 assert_eq!(map.len(), 30 * 17);
5525 assert!(
5526 map.iter().all(|&d| d == -128),
5527 "an AV1 delta is bounded to what the map holds"
5528 );
5529 let covered = |a: f64, b: f64| {
5530 let mut m = Vec::new();
5531 fill_band_map(&mut m, Codec::H264, 1280, 720, (a, b), -1);
5532 m.iter().map(|&d| d != 0).collect::<Vec<bool>>()
5533 };
5534 let (x, y) = (covered(0.0, 0.3), covered(0.3, 1.0));
5535 assert!(
5536 x.iter().zip(&y).all(|(a, b)| *a || *b),
5537 "adjacent bands leave no block out"
5538 );
5539 }
5540}
5541
5542#[cfg(test)]
5543pub(crate) mod gpu_tests {
5544 use super::*;
5545 use crate::encoders::reference::REFERENCE_FRAMES;
5546
5547 fn settings(w: i32, h: i32, fps: f64) -> CaptureSettings {
5549 CaptureSettings {
5550 width: w,
5551 height: h,
5552 codec: Codec::H264,
5553 target_fps: fps,
5554 video_crf: 25,
5555 ..Default::default()
5556 }
5557 }
5558
5559 fn host_session(s: &CaptureSettings) -> Result<NvencEncoder, String> {
5564 NvencEncoder::new(s, ptr::null()).map(|mut enc| {
5565 enc.pin_uploads = false;
5566 enc
5567 })
5568 }
5569
5570 fn codec_session(s: &CaptureSettings) -> Option<NvencEncoder> {
5573 match host_session(s) {
5574 Ok(enc) => Some(enc),
5575 Err(e) if e.contains("engine") => {
5576 println!("{:?}: {e}", s.codec);
5577 None
5578 }
5579 Err(e) => panic!("{:?} NVENC init: {e}", s.codec),
5580 }
5581 }
5582
5583 fn frame(w: usize, h: usize, seed: u8) -> Vec<u8> {
5586 let mut f = vec![0u8; w * h * 4];
5587 for (i, px) in f.as_chunks_mut::<4>().0.iter_mut().enumerate() {
5588 let v = ((i as u32).wrapping_mul(2654435761) >> 24) as u8;
5589 px[0] = v.wrapping_add(seed);
5590 px[1] = v ^ seed;
5591 px[2] = seed;
5592 px[3] = 255;
5593 }
5594 f
5595 }
5596
5597 fn reconfigure_raw(enc: &mut NvencEncoder) -> (NVENCSTATUS, String) {
5600 unsafe {
5601 let status = enc.reconfigure(false, false);
5602 let detail = if status == NVENCSTATUS::NV_ENC_SUCCESS {
5603 "accepted".to_string()
5604 } else {
5605 last_error(&enc.nvenc_funcs, enc.encoder_session)
5606 };
5607 (status, detail)
5608 }
5609 }
5610
5611 fn apart_without(codec: Codec, frames: &[Vec<u8>], lost: std::ops::Range<usize>) -> f64 {
5614 use crate::webcam::decode::{Decoder as _, VideoDecoder};
5615 let (mut whole, mut lossy) = (
5616 VideoDecoder::new(codec).unwrap(),
5617 VideoDecoder::new(codec).unwrap(),
5618 );
5619 for (i, f) in frames.iter().enumerate() {
5620 assert!(whole.decode(f).expect("decode"), "{codec:?} frame {i}");
5621 if !lost.contains(&i) {
5622 assert!(
5623 lossy.decode(f).expect("decode past the loss"),
5624 "{codec:?} frame {i}"
5625 );
5626 }
5627 }
5628 luma_apart(&whole.frame().unwrap(), &lossy.frame().unwrap())
5629 }
5630
5631 fn luma_apart(
5633 a: &crate::webcam::convert::I420View<'_>,
5634 b: &crate::webcam::convert::I420View<'_>,
5635 ) -> f64 {
5636 a.y.chunks(a.y_stride)
5637 .zip(b.y.chunks(b.y_stride))
5638 .take(a.height)
5639 .flat_map(|(ra, rb)| {
5640 ra[..a.width]
5641 .iter()
5642 .zip(&rb[..a.width])
5643 .map(|(&x, &y)| (f64::from(x) - f64::from(y)).abs())
5644 })
5645 .sum::<f64>()
5646 / (a.width * a.height) as f64
5647 }
5648
5649 fn apart_keeping(codec: Codec, frames: &[Vec<u8>], keep: &[usize]) -> Option<f64> {
5653 use crate::webcam::decode::{Decoder as _, VideoDecoder};
5654 let (mut whole, mut part) = (
5655 VideoDecoder::new(codec).unwrap(),
5656 VideoDecoder::new(codec).unwrap(),
5657 );
5658 let mut got = false;
5659 for (i, f) in frames.iter().enumerate() {
5660 let _ = whole.decode(f);
5661 if keep.contains(&i) {
5662 got = part.decode(f).unwrap_or(false) && i == frames.len() - 1;
5663 }
5664 }
5665 if !got {
5666 return None;
5667 }
5668 Some(luma_apart(&whole.frame()?, &part.frame()?))
5669 }
5670
5671 fn moving_frame(w: usize, h: usize, step: usize) -> Vec<u8> {
5674 let mut f = frame(w, h, 10);
5675 let block = frame(256, 256, 200);
5676 let x0 = (step * 64) % (w - 256);
5677 for row in 0..256.min(h) {
5678 let dst = (row * w + x0) * 4;
5679 f[dst..dst + 256 * 4].copy_from_slice(&block[row * 256 * 4..(row + 1) * 256 * 4]);
5680 }
5681 f
5682 }
5683
5684 fn rbsp(nal: &[u8]) -> Vec<u8> {
5686 let mut out = Vec::with_capacity(nal.len());
5687 let mut zeros = 0usize;
5688 for &b in nal {
5689 if zeros >= 2 && b == 3 {
5690 zeros = 0;
5691 continue;
5692 }
5693 zeros = if b == 0 { zeros + 1 } else { 0 };
5694 out.push(b);
5695 }
5696 out
5697 }
5698
5699 fn luma_psnr(
5702 dec: &mut crate::webcam::decode::VideoDecoder,
5703 pkt: &[u8],
5704 src: &[u8],
5705 w: usize,
5706 h: usize,
5707 ) -> f64 {
5708 use crate::webcam::decode::Decoder;
5709 assert!(
5710 dec.decode(&pkt[VIDEO_HEADER_LEN..]).expect("decode"),
5711 "no picture"
5712 );
5713 let v = dec.frame().expect("decoded frame");
5714 let mut se = 0f64;
5715 for y in 0..h {
5716 for x in 0..w {
5717 let p = &src[(y * w + x) * 4..(y * w + x) * 4 + 3];
5718 let luma = crate::encoders::chroma_siting::ycbcr(
5719 [p[2], p[1], p[0]].map(f64::from),
5720 crate::encoders::chroma_siting::BT709,
5721 )[0];
5722 let d = f64::from(v.y[y * v.y_stride + x]) - luma;
5723 se += d * d;
5724 }
5725 }
5726 let mse = se / (w * h) as f64;
5727 if mse == 0.0 {
5728 99.0
5729 } else {
5730 10.0 * (255.0f64 * 255.0 / mse).log10()
5731 }
5732 }
5733
5734 fn cbr_row(
5738 label: &str,
5739 enc: &mut NvencEncoder,
5740 codec: Codec,
5741 seq: &[&Vec<u8>],
5742 w: usize,
5743 h: usize,
5744 fps: usize,
5745 ) {
5746 use crate::webcam::decode::VideoDecoder;
5747 let n = seq.len() - 1;
5748 let first = enc
5749 .encode_cpu_packed(seq[0], w * 4, false, 0, 25, true)
5750 .expect("warm-up");
5751 let mut pkts: Vec<Vec<u8>> = Vec::with_capacity(n);
5752 per_frame(label, n, |i| {
5753 pkts.push(
5754 enc.encode_cpu_packed(seq[1 + i], w * 4, false, 1 + i as u64, 25, false)
5755 .expect("encode"),
5756 );
5757 });
5758 let sizes: Vec<usize> = pkts
5759 .iter()
5760 .map(|p| p.len().saturating_sub(VIDEO_HEADER_LEN))
5761 .collect();
5762 let bytes: usize = sizes.iter().sum();
5763 let mut dec = VideoDecoder::new(codec).expect("decoder");
5764 luma_psnr(&mut dec, &first, seq[0], w, h);
5765 let psnr: Vec<f64> = pkts
5766 .iter()
5767 .enumerate()
5768 .map(|(i, p)| luma_psnr(&mut dec, p, seq[1 + i], w, h))
5769 .collect();
5770 println!(
5771 " {} kbps, frames {}..{} kbit, luma PSNR {:.1} dB mean, {:.1} dB worst",
5772 bytes * 8 * fps / n / 1000,
5773 sizes.iter().min().unwrap() * 8 / 1000,
5774 sizes.iter().max().unwrap() * 8 / 1000,
5775 psnr.iter().sum::<f64>() / n as f64,
5776 psnr.iter().copied().fold(f64::INFINITY, f64::min),
5777 );
5778 }
5779
5780 fn wire_dims(pkt: &[u8]) -> (u16, u16) {
5782 (
5783 u16::from_be_bytes([pkt[6], pkt[7]]),
5784 u16::from_be_bytes([pkt[8], pkt[9]]),
5785 )
5786 }
5787
5788 #[test]
5795 #[ignore]
5796 fn gpu_resolution_reconfigure_roundtrip() {
5797 let mut s = settings(1280, 720, 60.0);
5798 let t0 = std::time::Instant::now();
5799 let mut enc = host_session(&s).expect("NVENC init");
5800 let init_ms = t0.elapsed().as_secs_f64() * 1000.0;
5801
5802 let mut stream: Vec<u8> = Vec::new();
5803 let f720 = frame(1280, 720, 10);
5804 for i in 0..5u64 {
5805 let pkt = enc
5806 .encode_cpu_argb(&f720, 1280 * 4, i, 25, i == 0)
5807 .expect("encode 720p");
5808 assert_eq!(wire_dims(&pkt), (1280, 720));
5809 stream.extend_from_slice(&pkt[VIDEO_HEADER_LEN..]);
5810 }
5811
5812 assert!(
5813 !enc.reconfigure_resolution(&s)
5814 .expect("same-size reconfigure"),
5815 "unchanged dimensions must not reset the session"
5816 );
5817 let pkt = enc
5818 .encode_cpu_argb(&f720, 1280 * 4, 5, 25, false)
5819 .expect("encode after same-size reconfigure");
5820 assert_eq!(
5821 pkt[1] & 0x0f,
5822 FRAME_DELTA,
5823 "the stream continues without an IDR at unchanged dimensions"
5824 );
5825 assert!(
5826 pkt.len() > VIDEO_HEADER_LEN,
5827 "a locked bitstream carries the encoded picture"
5828 );
5829 stream.extend_from_slice(&pkt[VIDEO_HEADER_LEN..]);
5830
5831 s.width = 1920;
5832 s.height = 1080;
5833 let t1 = std::time::Instant::now();
5834 assert!(enc.reconfigure_resolution(&s).expect("grow reconfigure"));
5835 let grow_ms = t1.elapsed().as_secs_f64() * 1000.0;
5836 let f1080 = frame(1920, 1080, 40);
5837 let pkt = enc
5838 .encode_cpu_argb(&f1080, 1920 * 4, 6, 25, false)
5839 .expect("encode 1080p");
5840 assert_eq!(pkt[0], 0x04);
5841 assert_eq!(
5842 pkt[1] & 0x0f,
5843 FRAME_KEY,
5844 "first frame after a resize must be an IDR"
5845 );
5846 assert_eq!(wire_dims(&pkt), (1920, 1080));
5847 assert!(
5848 pkt.len() > VIDEO_HEADER_LEN,
5849 "a locked bitstream carries the encoded picture"
5850 );
5851 stream.extend_from_slice(&pkt[VIDEO_HEADER_LEN..]);
5852 for i in 7..10u64 {
5853 let pkt = enc
5854 .encode_cpu_argb(&f1080, 1920 * 4, i, 25, false)
5855 .expect("encode 1080p");
5856 assert_eq!(
5857 pkt[1] & 0x0f,
5858 FRAME_DELTA,
5859 "steady frames after the IDR are P frames"
5860 );
5861 stream.extend_from_slice(&pkt[VIDEO_HEADER_LEN..]);
5862 }
5863
5864 s.width = 640;
5865 s.height = 480;
5866 let t2 = std::time::Instant::now();
5867 assert!(enc.reconfigure_resolution(&s).expect("shrink reconfigure"));
5868 let shrink_ms = t2.elapsed().as_secs_f64() * 1000.0;
5869 let f480 = frame(640, 480, 70);
5870 let pkt = enc
5871 .encode_cpu_argb(&f480, 640 * 4, 10, 25, false)
5872 .expect("encode 480p");
5873 assert_eq!(pkt[1] & 0x0f, FRAME_KEY);
5874 assert_eq!(wire_dims(&pkt), (640, 480));
5875 stream.extend_from_slice(&pkt[VIDEO_HEADER_LEN..]);
5876
5877 s.width = 4100;
5878 s.height = 2400;
5879 assert!(enc.reconfigure_resolution(&s).is_err(), "beyond headroom");
5880 s.width = 640;
5881 s.height = 480;
5882 s.video_fullcolor = true;
5883 assert!(enc.reconfigure_resolution(&s).is_err(), "chroma flip");
5884 s.video_fullcolor = false;
5885 s.video_cbr_mode = true;
5886 assert!(enc.reconfigure_resolution(&s).is_err(), "RC mode flip");
5887 s.video_cbr_mode = false;
5888 let pkt = enc
5889 .encode_cpu_argb(&f480, 640 * 4, 11, 25, false)
5890 .expect("session survives rejected reconfigures");
5891 stream.extend_from_slice(&pkt[VIDEO_HEADER_LEN..]);
5892
5893 println!(
5894 "init={init_ms:.1}ms grow(720p->1080p)={grow_ms:.1}ms shrink(1080p->480p)={shrink_ms:.1}ms"
5895 );
5896 if let Ok(path) = std::env::var("NVENC_TEST_DUMP") {
5897 std::fs::write(&path, &stream).unwrap();
5898 println!("wrote {} bytes to {path}", stream.len());
5899 }
5900 }
5901
5902 #[test]
5910 #[ignore]
5911 fn gpu_codec_sessions_encode_and_decode() {
5912 use crate::encoders::codec::{
5913 FRAME_DELTA, FRAME_KEY, av1_is_key, h264_frame_type, h265_frame_type, parse_video_type,
5914 };
5915 use crate::webcam::decode::{Decoder, VideoDecoder};
5916 let (w, h) = (1280usize, 720usize);
5917 for codec in [Codec::H264, Codec::H265, Codec::Av1] {
5918 let mut s = settings(w as i32, h as i32, 60.0);
5919 s.codec = codec;
5920 let mut enc = match host_session(&s) {
5921 Ok(enc) => enc,
5922 Err(e) => {
5923 println!("{codec:?}: {e}");
5924 assert!(
5925 e.contains("engine"),
5926 "a missing codec must be refused as such: {e}"
5927 );
5928 continue;
5929 }
5930 };
5931 assert_eq!(enc.codec(), codec);
5932 assert!(enc.egl.is_none(), "a session on host frames loads no EGL");
5933 let mut dec = VideoDecoder::new(codec).expect("decoder");
5934 for i in 0..6u64 {
5935 let src = frame(w, h, 10 + i as u8);
5936 let pkt = enc
5937 .encode_cpu_argb(&src, w * 4, i, 25, i == 0)
5938 .expect("encode");
5939 let (wire_codec, kind) = parse_video_type(pkt[1]).expect("video type byte");
5940 assert_eq!(wire_codec, codec);
5941 assert_eq!(
5942 kind,
5943 if i == 0 { FRAME_KEY } else { FRAME_DELTA },
5944 "{codec:?} frame {i}"
5945 );
5946 let payload = &pkt[VIDEO_HEADER_LEN..];
5947 if codec != Codec::Av1 {
5948 assert!(
5949 payload.starts_with(&[0, 0, 0, 1]),
5950 "{codec:?} frame {i}: the bitstream starts right after the header"
5951 );
5952 }
5953 let read = match codec {
5954 Codec::H264 => h264_frame_type(payload),
5955 Codec::H265 => h265_frame_type(payload),
5956 _ => {
5957 if av1_is_key(payload) {
5958 FRAME_KEY
5959 } else {
5960 FRAME_DELTA
5961 }
5962 }
5963 };
5964 assert_eq!(
5965 read, kind,
5966 "{codec:?} frame {i}: the bitstream disagrees with pictureType"
5967 );
5968 assert!(
5969 dec.decode(payload).expect("decode"),
5970 "{codec:?} frame {i} decoded nothing"
5971 );
5972 let f = dec.frame().unwrap();
5973 assert_eq!((f.width, f.height), (w, h));
5974 }
5975 let key = enc
5976 .encode_cpu_argb(&frame(w, h, 99), w * 4, 6, 25, true)
5977 .expect("forced key");
5978 assert_eq!(parse_video_type(key[1]), Some((codec, FRAME_KEY)));
5979 let mut fresh = VideoDecoder::new(codec).expect("decoder");
5980 assert!(
5981 fresh.decode(&key[VIDEO_HEADER_LEN..]).expect("decode"),
5982 "{codec:?}: a forced key frame must decode alone"
5983 );
5984
5985 let mut full = s.clone();
5986 full.video_fullcolor = true;
5987 let Some(enc) = codec_session(&full) else {
5988 continue;
5989 };
5990 if codec.fullcolor() {
5991 println!("{codec:?} 4:4:4: {}", enc.is_fullcolor());
5992 } else {
5993 assert!(!enc.is_fullcolor(), "{codec:?} never carries 4:4:4");
5994 }
5995 }
5996 }
5997
5998 #[test]
6003 #[ignore]
6004 fn gpu_ten_bit_follows_the_device() {
6005 use crate::webcam::decode::{Decoder, VideoDecoder};
6006 let (w, h) = (1280usize, 720usize);
6007 for (codec, formats) in probe_codecs(0).expect("probe") {
6008 for fullcolor in [false, codec.fullcolor()] {
6009 let mut s = settings(w as i32, h as i32, 60.0);
6010 s.codec = codec;
6011 s.video_fullcolor = fullcolor;
6012 s.video_bit_depth = 10;
6013 let Some(mut enc) = codec_session(&s) else {
6014 continue;
6015 };
6016 let carried = formats.ten_bit[usize::from(fullcolor && formats.fullcolor)];
6017 println!("{codec:?} 4:4:4 {fullcolor}: {}-bit", enc.bit_depth());
6018 assert_eq!(enc.bit_depth(), if carried { 10 } else { 8 }, "{codec:?}");
6019 assert_eq!(
6020 enc.csc.as_ref().map(|c| c.layout),
6021 ConvertLayout::of(enc.is_fullcolor(), enc.bit_depth()),
6022 "{codec:?}"
6023 );
6024 let key = enc
6025 .encode_cpu_argb(&frame(w, h, 1), w * 4, 0, 25, true)
6026 .expect("key frame");
6027 let decoded = VideoDecoder::new(codec)
6028 .expect("decoder")
6029 .decode(&key[VIDEO_HEADER_LEN..]);
6030 if carried {
6031 let refusal = format!("{:?}", decoded.expect_err("an 8-bit decoder"));
6032 assert!(refusal.contains("bit depth"), "{codec:?}: {refusal}");
6033 } else if !enc.is_fullcolor() {
6034 assert!(decoded.expect("decode"), "{codec:?}");
6035 }
6036 s.width = 1920;
6037 s.height = 1080;
6038 enc.reconfigure_resolution(&s).expect("resize");
6039 assert_eq!(enc.bit_depth(), if carried { 10 } else { 8 });
6040 enc.encode_cpu_argb(&frame(1920, 1080, 2), 1920 * 4, 1, 25, false)
6041 .expect("a frame after the resize");
6042 s.video_bit_depth = 8;
6043 assert_eq!(
6044 enc.reconfigure_resolution(&s).is_err(),
6045 carried,
6046 "{codec:?}: a depth change rebuilds the session"
6047 );
6048 }
6049 }
6050 }
6051
6052 #[test]
6060 #[ignore]
6061 fn gpu_predicts_past_a_lost_frame() {
6062 use crate::encoders::reference::Reference;
6063 use crate::encoders::sps::h264_max_num_ref_frames;
6064 let (w, h) = (1280usize, 720usize);
6065 for codec in [Codec::H264, Codec::H265, Codec::Av1] {
6066 let mut s = settings(w as i32, h as i32, 60.0);
6067 s.codec = codec;
6068 s.omit_stripe_headers = true;
6069 let mut enc = match host_session(&s) {
6070 Ok(enc) => enc,
6071 Err(e) => {
6072 println!("{codec:?}: {e}");
6073 continue;
6074 }
6075 };
6076 let encode = |enc: &mut NvencEncoder, i: usize, w: usize, h: usize| {
6077 let out = enc
6078 .encode_cpu_argb(&moving_frame(w, h, i), w * 4, i as u64, 25, i == 0)
6079 .expect("encode");
6080 (out, enc.last_reference())
6081 };
6082 let (first, reference) = encode(&mut enc, 0, w, h);
6083 if reference == Reference::Untracked {
6084 println!(
6085 "{codec:?}: this device cannot invalidate a reference, so nothing is tracked"
6086 );
6087 assert!(!enc.invalidate_reference(0));
6088 continue;
6089 }
6090 assert_eq!(reference, Reference::None);
6091 if codec == Codec::H264 {
6092 assert_eq!(
6093 h264_max_num_ref_frames(&first),
6094 Some(REFERENCE_FRAMES),
6095 "the SPS declares the DPB"
6096 );
6097 }
6098 let mut frames = vec![first];
6099 for i in 1..8 {
6100 let (out, reference) = encode(&mut enc, i, w, h);
6101 assert_eq!(
6102 reference,
6103 Reference::Frame(i as u16 - 1),
6104 "{codec:?} frame {i}"
6105 );
6106 frames.push(out);
6107 }
6108 assert!(
6109 enc.invalidate_reference(5),
6110 "{codec:?}: the device refused the invalidation"
6111 );
6112 let (out, reference) = encode(&mut enc, 8, w, h);
6113 let before_loss = if codec == Codec::Av1
6116 && enc
6117 .references
6118 .as_ref()
6119 .is_some_and(ReferenceWindow::anchored)
6120 {
6121 0
6122 } else {
6123 4
6124 };
6125 assert_eq!(reference, Reference::Frame(before_loss), "{codec:?}");
6126 frames.push(out);
6127 let (out, reference) = encode(&mut enc, 9, w, h);
6128 assert_eq!(reference, Reference::Frame(8), "{codec:?}");
6129 frames.push(out);
6130 let off = apart_without(codec, &frames, 5..8);
6131 println!("{codec:?}: frame 9 without frames 5-7 is {off:.3} off the complete decode");
6132 assert!(
6133 off < 0.5,
6134 "{codec:?}: the decoder that lost frames 5-7 shows frame 9 {off:.2} off the one that saw them"
6135 );
6136 s.width = 1920;
6139 s.height = 1080;
6140 assert!(
6141 enc.reconfigure_resolution(&s).expect("in-place grow"),
6142 "{codec:?}"
6143 );
6144 let dpb = enc.dpb as usize;
6145 let anchored = enc.references.as_ref().unwrap().anchored();
6146 let (out, reference) = encode(&mut enc, 10, 1920, 1080);
6147 assert_eq!(reference, Reference::None);
6148 if codec == Codec::H264 {
6149 assert_eq!(
6150 h264_max_num_ref_frames(&out),
6151 Some(4),
6152 "1080p at level 4.2 admits four"
6153 );
6154 }
6155 let mut frames = vec![out];
6156 for i in 11..=10 + dpb {
6157 let (out, reference) = encode(&mut enc, i, 1920, 1080);
6158 assert_eq!(
6159 reference,
6160 Reference::Frame(i as u16 - 1),
6161 "{codec:?} frame {i}"
6162 );
6163 frames.push(out);
6164 }
6165 assert!(
6166 enc.invalidate_reference(12),
6167 "{codec:?}: the device refused the invalidation"
6168 );
6169 let (out, reference) = encode(&mut enc, 11 + dpb, 1920, 1080);
6170 assert_eq!(
6171 reference,
6172 Reference::Frame(if anchored { 10 } else { 11 }),
6173 "{codec:?}: use the newest reference still held before the loss"
6174 );
6175 frames.push(out);
6176 let (out, reference) = encode(&mut enc, 12 + dpb, 1920, 1080);
6177 assert_eq!(reference, Reference::Frame(11 + dpb as u16), "{codec:?}");
6178 frames.push(out);
6179 let off = apart_without(codec, &frames, 2..dpb + 1);
6180 println!(
6181 "{codec:?}: at 1080p, frame {} without frames 12-{} is {off:.3} off the complete decode",
6182 12 + dpb,
6183 10 + dpb
6184 );
6185 assert!(
6186 off < 0.5,
6187 "{codec:?}: the decoder that lost frames 12-{} shows {off:.2} off the one that saw them",
6188 10 + dpb
6189 );
6190 assert!(enc.invalidate_reference(11));
6191 assert_eq!(
6192 encode(&mut enc, 13 + dpb, 1920, 1080).1,
6193 if anchored {
6194 Reference::Frame(10)
6195 } else {
6196 Reference::None
6197 },
6198 "{codec:?}: losing frame 11 requires the older anchor or a key frame"
6199 );
6200 assert!(enc.invalidate_reference(10));
6201 assert_eq!(
6202 encode(&mut enc, 14 + dpb, 1920, 1080).1,
6203 if anchored {
6204 Reference::None
6205 } else {
6206 Reference::Frame(13 + dpb as u16)
6207 },
6208 "{codec:?}: losing the anchor requires a key frame; a report before a new key frame is ignored"
6209 );
6210 }
6211 }
6212
6213 #[test]
6219 #[ignore]
6220 fn gpu_a_covered_loss_keeps_the_recovered_chain() {
6221 use crate::encoders::reference::Reference;
6222 let (w, h) = (1920usize, 1080usize);
6223 for codec in [Codec::H264, Codec::H265, Codec::Av1] {
6224 let mut s = settings(w as i32, h as i32, 60.0);
6225 s.codec = codec;
6226 s.omit_stripe_headers = true;
6227 let mut enc = match host_session(&s) {
6228 Ok(enc) => enc,
6229 Err(e) => {
6230 println!("{codec:?}: {e}");
6231 continue;
6232 }
6233 };
6234 let mut frames = Vec::new();
6235 for i in 0..=107usize {
6236 let (lost, expect) = match i {
6237 82 => (Some(80), Some(79)),
6238 99 => (Some(81), Some(98)),
6239 107 => (Some(106), Some(105)),
6240 _ => (None, None),
6241 };
6242 if let Some(lost) = lost {
6243 assert!(enc.invalidate_reference(lost), "{codec:?}: lost {lost}");
6244 }
6245 frames.push(
6246 enc.encode_cpu_argb(&moving_frame(w, h, i), w * 4, i as u64, 25, i == 0)
6247 .expect("encode"),
6248 );
6249 if i == 0 && enc.last_reference() == Reference::Untracked {
6250 break;
6251 }
6252 if let Some(from) = expect {
6253 assert_eq!(
6254 enc.last_reference(),
6255 Reference::Frame(from),
6256 "{codec:?} {i}"
6257 );
6258 }
6259 }
6260 if enc.last_reference() == Reference::Untracked {
6261 println!("{codec:?}: this device cannot invalidate a reference");
6262 continue;
6263 }
6264 let received: Vec<usize> = (0..frames.len())
6265 .filter(|i| !matches!(i, 80 | 81 | 106))
6266 .collect();
6267 let off = apart_keeping(codec, &frames, &received);
6268 assert!(
6269 off.is_some_and(|x| x < 0.5),
6270 "{codec:?}: without frames 80, 81 and 106 the last frame is {off:?} off"
6271 );
6272 println!("{codec:?}: frame 99 predicts from 98 after the covered report, {off:?} off");
6273 }
6274 }
6275
6276 #[test]
6283 #[ignore]
6284 fn gpu_a_held_refresh_past_its_limit_is_coded_again() {
6285 use crate::encoders::reference::Reference;
6286 let (w, h) = (1280usize, 720usize);
6287 let still = frame(w, h, 10);
6288 let encode = |enc: &mut NvencEncoder, i: u64, held: Option<u32>| {
6289 if let Some(crf) = held {
6290 enc.hold_quantizer(crf, None);
6291 }
6292 let out = enc
6293 .encode_cpu_argb(&still, w * 4, i, 25, i == 0)
6294 .expect("encode");
6295 (out, enc.last_reference())
6296 };
6297 for codec in [Codec::H264, Codec::H265, Codec::Av1] {
6298 let mut s = settings(w as i32, h as i32, 60.0);
6299 s.codec = codec;
6300 s.omit_stripe_headers = true;
6301 s.video_cbr_mode = true;
6302 s.video_bitrate_kbps = 50_000;
6303 let mut enc = match host_session(&s) {
6304 Ok(enc) => enc,
6305 Err(e) => {
6306 println!("{codec:?}: {e}");
6307 continue;
6308 }
6309 };
6310 let mut frames: Vec<_> = (0..4).map(|i| encode(&mut enc, i, None).0).collect();
6311 if enc.last_reference() == Reference::Untracked {
6312 println!("{codec:?}: this device cannot invalidate a reference");
6313 continue;
6314 }
6315 let (attempt, _) = encode(&mut enc, 4, Some(20));
6318 assert!(enc.retract_last(), "{codec:?}: the device refused");
6319 frames.push(attempt);
6320 let (out, reference) = encode(&mut enc, 4, Some(40));
6321 assert_eq!(reference, Reference::Frame(3), "{codec:?}");
6322 frames.push(out);
6323 let (out, reference) = encode(&mut enc, 5, None);
6324 assert_eq!(reference, Reference::Frame(4), "{codec:?}");
6325 frames.push(out);
6326 let off = apart_without(codec, &frames, 4..5);
6327 assert!(
6328 off < 0.5,
6329 "{codec:?}: without the attempt the frames after it decode {off:.2} off"
6330 );
6331 assert!(enc.invalidate_reference(4), "{codec:?}");
6332 let (out, reference) = encode(&mut enc, 6, None);
6333 assert!(
6334 matches!(reference, Reference::Frame(id) if id < 4),
6335 "{codec:?}: {reference:?} after losing the frame sent in place of the attempt"
6336 );
6337 frames.push(out);
6338 let lost = apart_without(codec, &frames, 4..7);
6339 assert!(
6340 lost < 0.5,
6341 "{codec:?}: past the loss the frame decodes {lost:.2} off"
6342 );
6343 for (kbps, first) in [(100, 7u64), (1000, 11)] {
6347 s.video_bitrate_kbps = kbps;
6348 assert!(enc.reconfigure_rate(&s), "{codec:?}");
6349 for i in first..first + 3 {
6350 encode(&mut enc, i, None);
6351 }
6352 let pts = |enc: &NvencEncoder| enc.references.as_ref().map_or(0, |r| r.next_pts());
6353 let before = pts(&enc);
6354 let (out, reference) = encode(&mut enc, first + 3, Some(20));
6355 assert_eq!(reference, Reference::Frame(first as u16 + 2), "{codec:?}");
6356 let spent = out.len() as f64 / (f64::from(kbps) * 1000.0 / 8.0);
6357 let coded = pts(&enc) - before;
6358 println!(
6359 "{codec:?}: a held frame at {kbps} kbps goes out at {} bytes, {spent:.2} s of the target, encoded {coded} times",
6360 out.len()
6361 );
6362 assert!(spent <= crate::encoders::HELD_REFRESH_LIMIT_S, "{codec:?}");
6363 if kbps == 1000 {
6364 assert_eq!(coded, 1, "{codec:?}: a frame under the limit stands");
6365 }
6366 }
6367 }
6368 }
6369
6370 #[test]
6376 #[ignore]
6377 fn gpu_a_cleanup_frame_past_its_cap_is_coded_again() {
6378 use crate::encoders::reference::Reference;
6379 use crate::webcam::decode::{Decoder as _, VideoDecoder};
6380 let (w, h) = (1280usize, 720usize);
6381 let still = frame(w, h, 10);
6382 for codec in [Codec::H264, Codec::H265, Codec::Av1] {
6383 let mut s = settings(w as i32, h as i32, 60.0);
6384 s.codec = codec;
6385 s.omit_stripe_headers = true;
6386 s.video_cbr_mode = true;
6387 s.video_bitrate_kbps = 2000;
6388 let mut enc = match host_session(&s) {
6389 Ok(enc) => enc,
6390 Err(e) => {
6391 println!("{codec:?}: {e}");
6392 continue;
6393 }
6394 };
6395 let encode = |enc: &mut NvencEncoder, i: u64| {
6396 enc.encode_cpu_argb(&still, w * 4, i, 25, i == 0)
6397 .expect("encode")
6398 };
6399 let mut frames: Vec<_> = (0..4).map(|i| encode(&mut enc, i)).collect();
6400 if enc.last_reference() == Reference::Untracked {
6401 println!("{codec:?}: this device cannot invalidate a reference");
6402 continue;
6403 }
6404 enc.hold_quantizer(10, Some((0.0, 0.5)));
6405 enc.cap(4000);
6406 frames.push(encode(&mut enc, 4));
6407 let end = enc.refit().expect("the band taken back");
6408 println!(
6409 "{codec:?}: a band to 0.5 coded again to {end:.3}, {} bytes",
6410 frames[4].len()
6411 );
6412 assert!(end > 0.0 && end < 0.5, "{codec:?}: {end}");
6413 assert_eq!(enc.last_reference(), Reference::Frame(3), "{codec:?}");
6414 enc.cap(1);
6415 frames.push(encode(&mut enc, 5));
6416 assert_eq!(
6417 enc.refit(),
6418 Some(0.0),
6419 "{codec:?}: the rate control's frame taken back"
6420 );
6421 assert_eq!(enc.last_reference(), Reference::Frame(4), "{codec:?}");
6422 enc.cap(usize::MAX);
6423 frames.push(encode(&mut enc, 6));
6424 assert_eq!(
6425 enc.refit(),
6426 None,
6427 "{codec:?}: a frame within its cap stands"
6428 );
6429 let mut decoder = VideoDecoder::new(codec).unwrap();
6430 for (i, f) in frames.iter().enumerate() {
6431 assert!(decoder.decode(f).expect("decode"), "{codec:?} frame {i}");
6432 }
6433 }
6434 }
6435
6436 #[test]
6446 #[ignore]
6447 fn gpu_acknowledged_anchors_carry_a_consumer_left_behind() {
6448 use crate::encoders::codec::parse_video_type;
6449 use crate::encoders::reference::Reference;
6450 use crate::webcam::decode::{Decoder as _, VideoDecoder};
6451 let (w, h) = (1280usize, 720usize);
6452 let pinned = [(13..=40, false, 42, 12u16), (61..=100, true, 102, 60)];
6455 let alone = [(49..=90, false, 92, 48u16), (97..=150, false, 152, 96)];
6456 for codec in [Codec::H264, Codec::H265, Codec::Av1] {
6457 let mut s = settings(w as i32, h as i32, 60.0);
6458 s.codec = codec;
6459 s.acknowledge_references = true;
6460 let mut enc = match host_session(&s) {
6461 Ok(enc) => enc,
6462 Err(e) => {
6463 println!("{codec:?}: {e}");
6464 continue;
6465 }
6466 };
6467 let anchors = enc
6468 .references
6469 .as_ref()
6470 .map_or(0, ReferenceWindow::anchor_count);
6471 let (stretches, period, last) = match anchors {
6472 ANCHORS => (&pinned, 12, 120),
6473 1 => (&alone, 48, 160),
6474 _ => {
6475 println!("{codec:?}: this device or API keeps no long-term reference");
6476 continue;
6477 }
6478 };
6479 let (mut whole, mut part) = (
6480 VideoDecoder::new(codec).unwrap(),
6481 VideoDecoder::new(codec).unwrap(),
6482 );
6483 let mut holds = Vec::new();
6484 let mut run = None;
6485 let (mut worst, mut sizes) = (0f64, (0usize, 0usize, 0usize, 0usize));
6486 for i in 0..=last {
6487 let stretch = stretches.iter().find(|t| t.0.contains(&i));
6488 if let Some(&(_, _, _, from)) = stretches.iter().find(|t| t.2 == i) {
6489 let first = run.take().expect("a missing run");
6490 assert!(enc.take_report(ReferenceReport::Lost(first)), "{codec:?}");
6491 assert_eq!(
6492 enc.references
6493 .as_ref()
6494 .and_then(ReferenceWindow::newest_valid)
6495 .map(|f| f.0),
6496 Some(from),
6497 "{codec:?}: {first} reported lost before {i}"
6498 );
6499 }
6500 let out = enc
6501 .encode_cpu_argb(&moving_frame(w, h, i), w * 4, i as u64, 25, i == 0)
6502 .expect("encode");
6503 let (_, kind) = parse_video_type(out[1]).expect("video type byte");
6504 assert_eq!(
6505 kind == FRAME_KEY,
6506 i == 0,
6507 "{codec:?}: frame {i} is kind {kind}"
6508 );
6509 let reference = enc.last_reference();
6510 let can = match reference {
6511 Reference::None => true,
6512 Reference::Frame(r) => holds[r as usize],
6513 Reference::Untracked => false,
6514 };
6515 let anchor = out[1] & FRAME_ANCHOR != 0;
6516 if anchor {
6517 assert!(
6518 can,
6519 "{codec:?}: anchor {i} predicts from {reference:?}, held by one"
6520 );
6521 }
6522 assert_eq!(
6523 anchor,
6524 i > 0 && i % period == 0 && can,
6525 "{codec:?}: frame {i} predicting from {reference:?}"
6526 );
6527 let sent = can && stretch.is_none_or(|t| anchor && !t.1);
6528 let bits = &out[VIDEO_HEADER_LEN..];
6529 assert!(whole.decode(bits).unwrap_or(false), "{codec:?}: frame {i}");
6530 if sent {
6531 assert!(
6532 part.decode(bits).unwrap_or(false),
6533 "{codec:?}: frame {i} sent"
6534 );
6535 let off = luma_apart(&whole.frame().unwrap(), &part.frame().unwrap());
6536 assert!(off < 0.5, "{codec:?}: frame {i} sent is {off:.3} off");
6537 worst = worst.max(off);
6538 run = None;
6539 enc.acknowledge_reference(i as u16, true);
6540 } else if can {
6541 run = Some(i as u16);
6542 }
6543 holds.push(sent);
6544 if i > 0 {
6545 let bucket = if anchor { &mut sizes.2 } else { &mut sizes.0 };
6546 *bucket += out.len();
6547 if anchor { sizes.3 += 1 } else { sizes.1 += 1 }
6548 }
6549 }
6550 println!(
6551 "{codec:?}: worst {worst:.3} apart; {} B a frame, {} B an anchor ({} anchors)",
6552 sizes.0 / sizes.1.max(1),
6553 sizes.2 / sizes.3.max(1),
6554 sizes.3
6555 );
6556 }
6557 }
6558
6559 #[test]
6565 #[ignore]
6566 fn gpu_a_lost_anchor_lends_its_slot_to_the_recent_frames() {
6567 let (w, h) = (1280usize, 720usize);
6568 for codec in [Codec::H264, Codec::H265, Codec::Av1] {
6569 let mut s = settings(w as i32, h as i32, 60.0);
6570 s.codec = codec;
6571 s.acknowledge_references = true;
6572 s.omit_stripe_headers = true;
6573 let mut enc = match host_session(&s) {
6574 Ok(enc) => enc,
6575 Err(e) => {
6576 println!("{codec:?}: {e}");
6577 continue;
6578 }
6579 };
6580 if !enc
6581 .references
6582 .as_ref()
6583 .is_some_and(ReferenceWindow::anchored)
6584 {
6585 println!("{codec:?}: this device or API keeps no long-term reference");
6586 continue;
6587 }
6588 let mut frames = Vec::new();
6589 let mut from = 0;
6590 for i in 0..=22usize {
6591 match i {
6592 16 => assert!(enc.take_report(ReferenceReport::Lost(12)), "{codec:?}"),
6593 21 => assert!(enc.take_report(ReferenceReport::Lost(19)), "{codec:?}"),
6594 _ => {}
6595 }
6596 let out = enc
6597 .encode_cpu_argb(&moving_frame(w, h, i), w * 4, i as u64, 25, i == 0)
6598 .expect("encode");
6599 if i <= 11 || (16..=17).contains(&i) {
6600 enc.acknowledge_reference(i as u16, true);
6601 }
6602 frames.push(out);
6603 if i == 21 {
6604 let Reference::Frame(r) = enc.last_reference() else {
6605 panic!("{codec:?}: {:?} past the loss", enc.last_reference());
6606 };
6607 from = r as usize;
6608 }
6609 }
6610 if codec == Codec::Av1 {
6611 assert_eq!(from, 18, "{codec:?}: past the loss of 19 and 20");
6612 }
6613 let keep: Vec<usize> = (0..=11).chain(16..=from).chain([21, 22]).collect();
6614 let off = apart_keeping(codec, &frames, &keep);
6615 println!("{codec:?}: past the loss from {from}, {off:?} apart");
6616 assert!(
6617 off.is_some_and(|off| off < 0.5),
6618 "{codec:?}: given the frames up to {from}, frame 22 decodes {off:?} apart"
6619 );
6620 }
6621 }
6622
6623 #[test]
6630 #[ignore]
6631 fn gpu_predicts_past_a_loss_deeper_than_the_recent_frames() {
6632 use crate::encoders::reference::Reference;
6633 let cases = [
6637 (Codec::H264, 60u16, 50u16, Some(48u16), Some(0u16), false),
6638 (Codec::H264, 60, 57, Some(48), Some(0), false),
6639 (Codec::H264, 80, 64, Some(48), Some(0), false),
6640 (Codec::H264, 80, 51, Some(48), Some(0), false),
6641 (Codec::H264, 80, 64, Some(48), Some(0), true),
6642 (Codec::H264, 60, 40, None, None, false),
6643 (Codec::Av1, 60, 50, Some(48), Some(0), false),
6644 (Codec::Av1, 60, 57, Some(48), Some(0), false),
6645 (Codec::Av1, 80, 64, Some(48), Some(0), false),
6646 (Codec::Av1, 80, 64, Some(48), Some(0), true),
6647 (Codec::Av1, 60, 40, None, None, false),
6648 (Codec::H265, 30, 28, Some(27), Some(26), false),
6649 (Codec::H265, 30, 27, Some(24), Some(12), false),
6650 (Codec::H265, 40, 37, Some(36), Some(24), false),
6651 (Codec::H265, 40, 30, Some(24), Some(12), false),
6652 (Codec::H265, 58, 40, Some(36), Some(24), false),
6653 (Codec::H265, 58, 37, Some(36), Some(24), false),
6654 (Codec::H265, 40, 20, None, None, false),
6655 ];
6656 let split = [
6657 (Codec::Av1, 60u16, 50u16, Some(48u16), Some(0u16), false),
6658 (Codec::H265, 30, 28, Some(27), Some(26), false),
6659 (Codec::H265, 40, 30, Some(24), Some(12), false),
6660 ];
6661 let fmt = |v: Option<f64>| v.map_or("no picture".to_string(), |x| format!("{x:.3}"));
6662 let sized = (cases.into_iter().map(|c| (1920usize, 1080usize, c)))
6663 .chain(split.into_iter().map(|c| (3840, 2160, c)));
6664 for (w, h, (codec, sent, lost, from, older, retarget)) in sized {
6665 let mut s = settings(w as i32, h as i32, 60.0);
6666 s.codec = codec;
6667 s.omit_stripe_headers = true;
6668 if retarget {
6669 s.video_cbr_mode = true;
6670 s.video_bitrate_kbps = 20_000;
6671 }
6672 let mut enc = match host_session(&s) {
6673 Ok(enc) => enc,
6674 Err(e) => {
6675 println!("{codec:?}: {e}");
6676 continue;
6677 }
6678 };
6679 if codec == Codec::Av1
6680 && !enc
6681 .references
6682 .as_ref()
6683 .is_some_and(ReferenceWindow::anchored)
6684 {
6685 println!("{codec:?}: this device or API keeps no long-term reference");
6686 continue;
6687 }
6688 let mut frames = Vec::new();
6689 for i in 0..=sent as usize {
6690 if retarget && i == lost as usize {
6691 s.video_bitrate_kbps = 8000;
6692 assert!(
6693 enc.reconfigure_rate(&s),
6694 "{codec:?}: the rate change was refused"
6695 );
6696 }
6697 frames.push(
6698 enc.encode_cpu_argb(&moving_frame(w, h, i), w * 4, i as u64, 25, i == 0)
6699 .expect("encode"),
6700 );
6701 if i == 0 && enc.last_reference() == Reference::Untracked {
6702 break;
6703 }
6704 }
6705 if enc.last_reference() == Reference::Untracked {
6706 println!("{codec:?}: this device cannot invalidate a reference");
6707 continue;
6708 }
6709 let normal = frames[sent as usize].len();
6710 assert!(
6711 enc.invalidate_reference(lost),
6712 "{codec:?}: the device refused the invalidation"
6713 );
6714 let next = sent as usize + 1;
6715 frames.push(
6716 enc.encode_cpu_argb(&moving_frame(w, h, next), w * 4, next as u64, 25, false)
6717 .expect("encode"),
6718 );
6719 let reference = enc.last_reference();
6720 let keeping = |upto: usize| (0..=upto).chain([next]).collect::<Vec<_>>();
6721 let without_lost: Vec<usize> = (0..=next)
6722 .filter(|&i| i < lost as usize || i == next)
6723 .collect();
6724 println!(
6725 "{codec:?} {w}x{h}: frame {lost} of {sent} lost, frame {next} predicts from {reference:?}, {} B against {normal} B",
6726 frames[next].len()
6727 );
6728 if let Some(anchor) = from.filter(|&a| a > 0 && a < sent).map(usize::from) {
6730 let skipped: Vec<usize> = (0..=anchor + 1).filter(|&i| i != anchor).collect();
6731 let off = apart_keeping(codec, &frames[..=anchor + 1], &skipped);
6732 assert!(
6733 off.is_none_or(|x| x >= 0.5),
6734 "{codec:?}: frame {} decodes without {anchor} ({})",
6735 anchor + 1,
6736 fmt(off)
6737 );
6738 }
6739 match from {
6740 Some(anchor) => {
6741 assert_eq!(
6742 reference,
6743 Reference::Frame(anchor),
6744 "{codec:?}: lost {lost}"
6745 );
6746 let off = apart_keeping(codec, &frames, &without_lost);
6747 assert!(
6748 off.is_some_and(|x| x < 0.5),
6749 "{codec:?}: without {lost}-{sent} the frame is {} off",
6750 fmt(off)
6751 );
6752 let off = apart_keeping(codec, &frames, &keeping(anchor as usize));
6753 assert!(
6754 off.is_some_and(|x| x < 0.5),
6755 "{codec:?}: up to the anchor {anchor} the frame is {} off",
6756 fmt(off)
6757 );
6758 if let Some(older) = older {
6759 let off = apart_keeping(codec, &frames, &keeping(older as usize));
6760 let past_buffer = codec == Codec::H265
6761 && anchor == sent - (enc.dpb - ANCHORS as u32) as u16;
6762 if past_buffer && off.is_some_and(|x| x < 0.5) {
6763 println!(
6764 "{codec:?}: lost {lost}, the device predicts from before {anchor}, the frame past its buffer"
6765 );
6766 } else {
6767 assert!(
6768 off.is_none_or(|x| x >= 0.5),
6769 "{codec:?}: the frame decodes from anchor {older} alone ({})",
6770 fmt(off)
6771 );
6772 }
6773 }
6774 }
6775 None => assert_eq!(
6776 reference,
6777 Reference::None,
6778 "{codec:?}: lost {lost}, before every anchor"
6779 ),
6780 }
6781 }
6782 }
6783
6784 #[test]
6789 #[ignore]
6790 fn gpu_a_shrink_predicts_past_a_loss_deeper_than_its_buffer() {
6791 use crate::encoders::reference::Reference;
6792 use crate::encoders::sps::h264_max_num_ref_frames;
6793 for codec in [Codec::H264, Codec::H265] {
6794 let mut s = settings(1920, 1080, 60.0);
6795 s.codec = codec;
6796 s.omit_stripe_headers = true;
6797 let mut enc = match host_session(&s) {
6798 Ok(enc) => enc,
6799 Err(e) if codec == Codec::H265 => {
6800 println!("{codec:?}: {e}");
6801 continue;
6802 }
6803 Err(e) => panic!("{codec:?}: {e}"),
6804 };
6805 let encode = |enc: &mut NvencEncoder, i: usize, w: usize, h: usize| {
6806 let out = enc
6807 .encode_cpu_argb(&moving_frame(w, h, i), w * 4, i as u64, 25, i == 0)
6808 .expect("encode");
6809 (out, enc.last_reference())
6810 };
6811 let (first, reference) = encode(&mut enc, 0, 1920, 1080);
6812 if reference == Reference::Untracked {
6813 println!("{codec:?}: this device cannot invalidate a reference");
6814 continue;
6815 }
6816 let declared = enc.dpb;
6817 if codec == Codec::H264 {
6818 assert_eq!(
6819 h264_max_num_ref_frames(&first),
6820 Some(4),
6821 "1080p at level 4.2 admits four"
6822 );
6823 }
6824 (s.width, s.height) = (1280, 720);
6825 assert!(
6826 enc.reconfigure_resolution(&s).expect("in-place shrink"),
6827 "{codec:?}"
6828 );
6829 assert_eq!(
6830 enc.dpb, declared,
6831 "{codec:?}: the driver never raises the buffer"
6832 );
6833 let mut frames = Vec::new();
6834 for i in 1..=8 {
6835 let (out, reference) = encode(&mut enc, i, 1280, 720);
6836 let want = if i == 1 {
6837 Reference::None
6838 } else {
6839 Reference::Frame(i as u16 - 1)
6840 };
6841 assert_eq!(reference, want, "{codec:?} frame {i}");
6842 frames.push(out);
6843 }
6844 assert!(
6845 enc.invalidate_reference(2),
6846 "{codec:?}: the device refused the invalidation"
6847 );
6848 let (out, reference) = encode(&mut enc, 9, 1280, 720);
6849 assert_eq!(
6850 reference,
6851 Reference::Frame(1),
6852 "{codec:?}: a loss six deep, from the resize's key frame"
6853 );
6854 frames.push(out);
6855 let off = apart_without(codec, &frames, 1..8);
6856 println!(
6857 "{codec:?}: at 720p on a {declared}-frame buffer, frame 9 without frames 2-8 is {off:.3} off the complete decode"
6858 );
6859 assert!(
6860 off < 0.5,
6861 "{codec:?}: the decoder that lost frames 2-8 shows {off:.2} off the one that saw them"
6862 );
6863 }
6864 }
6865
6866 #[test]
6873 #[ignore]
6874 fn gpu_bench_loss_recovery() {
6875 use crate::encoders::reference::Reference;
6876 use crate::webcam::decode::{Decoder as _, VideoDecoder};
6877 let (w, h) = (1920usize, 1080usize);
6878 for codec in [Codec::H264, Codec::H265] {
6879 let mut s = settings(w as i32, h as i32, 60.0);
6880 s.codec = codec;
6881 s.video_cbr_mode = true;
6882 s.video_bitrate_kbps = 8000;
6883 for depth in [0usize, 1, 2, 3, 5, 8, 12] {
6884 let mut enc = host_session(&s).expect("init");
6885 let mut dec = VideoDecoder::new(codec).unwrap();
6886 let mut pending: std::collections::VecDeque<Vec<u8>> = Default::default();
6887 let mut rng = 0x2545_f491_4f6c_dd1du64 ^ depth as u64;
6888 let (mut sizes, mut first, mut after, mut keys) =
6889 (Vec::new(), Vec::new(), Vec::new(), 0);
6890 let mut i = 0usize;
6891 for event in 0..10 {
6892 rng ^= rng << 13;
6893 rng ^= rng >> 7;
6894 rng ^= rng << 17;
6895 let gap = if event == 0 {
6896 60
6897 } else {
6898 20 + (rng % 37) as usize
6899 };
6900 for _ in 0..gap {
6901 let out = enc
6902 .encode_cpu_argb(&moving_frame(w, h, i), w * 4, i as u64, 25, i == 0)
6903 .expect("encode");
6904 pending.push_back(out);
6905 i += 1;
6906 while pending.len() > depth + 1 {
6907 let out = pending.pop_front().unwrap();
6908 dec.decode(&out[VIDEO_HEADER_LEN..]).expect("decode");
6909 }
6910 }
6911 if depth == 0 {
6913 for out in pending.drain(..) {
6914 dec.decode(&out[VIDEO_HEADER_LEN..]).expect("decode");
6915 }
6916 } else {
6917 pending.clear();
6918 enc.invalidate_reference((i - 1 - depth) as u16);
6919 }
6920 let mut row = Vec::new();
6921 for k in 0..15 {
6922 let src = moving_frame(w, h, i);
6923 let out = enc
6924 .encode_cpu_argb(&src, w * 4, i as u64, 25, false)
6925 .expect("encode");
6926 if k == 0 {
6927 sizes.push(out.len() - VIDEO_HEADER_LEN);
6928 keys += usize::from(enc.last_reference() == Reference::None);
6929 }
6930 row.push(luma_psnr(&mut dec, &out, &src, w, h));
6931 i += 1;
6932 }
6933 first.push(row[0]);
6934 after.push(row.iter().sum::<f64>() / row.len() as f64);
6935 }
6936 sizes.sort();
6937 let mean = |v: &[f64]| v.iter().sum::<f64>() / v.len() as f64;
6938 println!(
6939 "RECOVERY {codec:?} depth {depth}: {keys}/10 key frames, answer {} B median, \
6940 answer PSNR {:.2} dB mean ({:.2} worst), with the 14 after {:.2} dB mean",
6941 sizes[sizes.len() / 2],
6942 mean(&first),
6943 first.iter().copied().fold(f64::INFINITY, f64::min),
6944 mean(&after),
6945 );
6946 }
6947 }
6948 }
6949
6950 #[test]
6955 #[ignore]
6956 fn gpu_predicts_past_a_loss_at_the_devices_frame_num_wrap() {
6957 use crate::encoders::reference::Reference;
6958 use crate::encoders::sps::h264_frame_num_range;
6959 let (w, h) = (1280usize, 720usize);
6960 let mut s = settings(w as i32, h as i32, 60.0);
6961 s.omit_stripe_headers = true;
6962 let mut enc = host_session(&s).expect("H.264 session");
6963 let encode = |enc: &mut NvencEncoder, i: usize| {
6964 let out = enc
6965 .encode_cpu_argb(&moving_frame(w, h, i), w * 4, i as u64, 25, i == 0)
6966 .expect("encode");
6967 (out, enc.last_reference())
6968 };
6969 let (first, reference) = encode(&mut enc, 0);
6970 if reference == Reference::Untracked {
6971 println!("this device cannot invalidate a reference, so nothing is tracked");
6972 return;
6973 }
6974 let range = h264_frame_num_range(&first).expect("the key frame carries the SPS") as usize;
6975 assert_eq!(range, 65536, "NVENC's 256 values, a byte wider");
6976 let mut frames = vec![first];
6977 for i in 1..=258 {
6978 frames.push(encode(&mut enc, i).0);
6979 }
6980 assert!(
6981 enc.invalidate_reference(256),
6982 "the device's wrap frame is reported lost"
6983 );
6984 let (out, reference) = encode(&mut enc, 259);
6985 assert!(
6986 matches!(reference, Reference::Frame(r) if r < 256),
6987 "{reference:?} past the loss"
6988 );
6989 frames.push(out);
6990 let off = apart_without(Codec::H264, &frames, 256..259);
6991 assert!(
6992 off < 0.5,
6993 "past the loss at the wrap the frame decodes {off:.2} off"
6994 );
6995 }
6996
6997 #[test]
7001 #[ignore]
7002 fn gpu_resolution_reconfigure_cbr() {
7003 let mut s = settings(1280, 720, 60.0);
7004 s.video_cbr_mode = true;
7005 s.video_bitrate_kbps = 4000;
7006 let mut enc = host_session(&s).expect("NVENC init");
7007 let f720 = frame(1280, 720, 10);
7008 for i in 0..3u64 {
7009 enc.encode_cpu_argb(&f720, 1280 * 4, i, 25, i == 0)
7010 .expect("encode 720p");
7011 }
7012 s.width = 1920;
7013 s.height = 1080;
7014 s.video_bitrate_kbps = 8000;
7015 enc.reconfigure_resolution(&s).expect("cbr resize+rate");
7016 assert_eq!(enc.encode_config.rcParams.averageBitRate, 8_000_000);
7017 let f1080 = frame(1920, 1080, 40);
7018 let pkt = enc
7019 .encode_cpu_argb(&f1080, 1920 * 4, 3, 25, false)
7020 .expect("encode 1080p");
7021 assert_eq!(pkt[1] & 0x0f, FRAME_KEY);
7022 assert_eq!(wire_dims(&pkt), (1920, 1080));
7023 }
7024
7025 #[test]
7029 #[ignore]
7030 fn gpu_sessions_keep_the_reference_frames_they_are_given() {
7031 use crate::encoders::sps::h264_max_num_ref_frames;
7032 for codec in [Codec::H264, Codec::H265, Codec::Av1] {
7033 let mut s = settings(1280, 720, 60.0);
7034 s.codec = codec;
7035 s.omit_stripe_headers = true;
7036 s.acknowledge_references = true;
7037 s.video_reference_frames = 1;
7038 let mut enc = match host_session(&s) {
7039 Ok(enc) => enc,
7040 Err(e) => {
7041 println!("{codec:?}: {e}");
7042 continue;
7043 }
7044 };
7045 let key = enc
7046 .encode_cpu_argb(&frame(1280, 720, 10), 1280 * 4, 0, 25, true)
7047 .expect("encode");
7048 let anchors = enc
7049 .references
7050 .as_ref()
7051 .map_or(0, ReferenceWindow::anchor_count);
7052 match codec {
7053 Codec::Av1 => assert_eq!(enc.dpb, AV1_REFERENCES, "AV1"),
7054 _ => {
7055 assert_eq!(enc.dpb, 1, "{codec:?}");
7056 assert_eq!(anchors, 0, "{codec:?}");
7057 }
7058 }
7059 if codec == Codec::H264 {
7060 assert_eq!(h264_max_num_ref_frames(&key), Some(1));
7061 }
7062 }
7063 }
7064
7065 #[test]
7068 #[ignore]
7069 fn gpu_h264_bounds_reordering_at_zero() {
7070 use crate::encoders::sps::fixtures::assert_no_reorder;
7071 for fullcolor in [false, true] {
7072 let mut s = settings(1920, 1080, 60.0);
7073 s.video_fullcolor = fullcolor;
7074 s.omit_stripe_headers = true;
7075 let mut enc = match host_session(&s) {
7076 Ok(enc) => enc,
7077 Err(e) => {
7078 println!("fullcolor {fullcolor}: {e}");
7079 continue;
7080 }
7081 };
7082 let key = enc
7083 .encode_cpu_argb(&frame(1920, 1080, 10), 1920 * 4, 0, 25, true)
7084 .expect("encode 1080p");
7085 assert_no_reorder(&key, &format!("NVENC fullcolor {fullcolor} 1080p"));
7086 s.width = 1280;
7087 s.height = 720;
7088 if enc.reconfigure_resolution(&s).expect("in-place resize") {
7089 let key = enc
7090 .encode_cpu_argb(&frame(1280, 720, 20), 1280 * 4, 1, 25, false)
7091 .expect("encode 720p");
7092 assert_no_reorder(
7093 &key,
7094 &format!("NVENC fullcolor {fullcolor} 720p after the resize"),
7095 );
7096 }
7097 }
7098 }
7099
7100 #[test]
7105 #[ignore]
7106 fn gpu_a_refused_resize_is_rebuilt() {
7107 use crate::encoders::sps::h264_max_num_ref_frames;
7108 use crate::encoders::{FrameEncoder, FrameSource, select_frame_encoder};
7109 let mut s = settings(1920, 1080, 60.0);
7110 let mut enc = host_session(&s).expect("NVENC init");
7111 let key = enc
7112 .encode_cpu_argb(&frame(1920, 1080, 10), 1920 * 4, 0, 25, true)
7113 .expect("encode 1080p");
7114 assert_eq!(
7115 h264_max_num_ref_frames(&key[VIDEO_HEADER_LEN..]),
7116 Some(4),
7117 "1080p at level 4.2 admits four"
7118 );
7119 enc.dpb = REFERENCE_FRAMES;
7120 s.width = 1280;
7121 s.height = 720;
7122 let prior = Some(FrameEncoder::Nvenc(enc));
7123 let Some(FrameEncoder::Nvenc(mut enc)) =
7124 select_frame_encoder(&mut s, FrameSource::Host { rgba: false }, prior, "test")
7125 else {
7126 panic!("the ladder rebuilt no NVENC session");
7127 };
7128 assert_eq!(
7129 enc.references.as_ref().map_or(0, ReferenceWindow::next_pts),
7130 0,
7131 "a new session, not the refused one"
7132 );
7133 let key = enc
7134 .encode_cpu_argb(&frame(1280, 720, 20), 1280 * 4, 1, 25, false)
7135 .expect("encode 720p");
7136 assert_eq!(key[1] & 0x0f, FRAME_KEY);
7137 assert_eq!(wire_dims(&key), (1280, 720));
7138 assert_eq!(
7139 h264_max_num_ref_frames(&key[VIDEO_HEADER_LEN..]),
7140 Some(REFERENCE_FRAMES),
7141 "720p declares eight"
7142 );
7143 }
7144
7145 fn guarded<T>(value: T) -> &'static mut T {
7148 unsafe {
7149 let page = libc::sysconf(libc::_SC_PAGESIZE) as usize;
7150 let map = libc::mmap(
7151 ptr::null_mut(),
7152 2 * page,
7153 libc::PROT_READ | libc::PROT_WRITE,
7154 libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
7155 -1,
7156 0,
7157 );
7158 assert_ne!(map, libc::MAP_FAILED);
7159 assert_eq!(
7160 libc::mprotect(map.cast::<u8>().add(page).cast(), page, libc::PROT_NONE),
7161 0
7162 );
7163 let at = map
7164 .cast::<u8>()
7165 .add(page - std::mem::size_of::<T>())
7166 .cast::<T>();
7167 at.write(value);
7168 &mut *at
7169 }
7170 }
7171
7172 #[test]
7177 #[ignore]
7178 fn gpu_the_driver_stays_inside_the_structs_it_is_handed() {
7179 const PINNED: &str = "PIXELFLUX_TEST_NVENC_PINNED";
7180 if std::env::var_os(PINNED).is_none() {
7181 let name = format!(
7182 "{}::gpu_the_driver_stays_inside_the_structs_it_is_handed",
7183 module_path!().split_once("::").unwrap().1
7184 );
7185 for api in ["10.0", "11.0", "11.1", "12.0", "12.1", "13.0"] {
7186 let out = std::process::Command::new(std::env::current_exe().unwrap())
7187 .args([
7188 name.as_str(),
7189 "--exact",
7190 "--ignored",
7191 "--nocapture",
7192 "--test-threads=1",
7193 ])
7194 .env("PIXELFLUX_NVENC_MAX_API", api)
7195 .env(PINNED, "1")
7196 .output()
7197 .expect("the test binary runs");
7198 let stdout = String::from_utf8_lossy(&out.stdout);
7199 let report = stdout
7200 .lines()
7201 .find(|l| l.contains("stayed inside"))
7202 .unwrap_or_default();
7203 println!("{report}");
7204 assert!(
7205 out.status.success() && !report.is_empty(),
7206 "API {api}: {}\n{stdout}",
7207 out.status
7208 );
7209 }
7210 return;
7211 }
7212 let mut enc = host_session(&settings(1280, 720, 60.0)).expect("NVENC init");
7213 unsafe {
7214 let funcs = enc.nvenc_funcs;
7215 let _ = (enc.cuda.cuCtxPushCurrent_v2)(enc.cuda_context);
7216 let mut open = NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS {
7217 version: sv(NvStruct::OpenSessionExParams),
7218 deviceType: NV_ENC_DEVICE_TYPE::NV_ENC_DEVICE_TYPE_CUDA,
7219 device: enc.cuda_context as *mut c_void,
7220 apiVersion: neg_api(),
7221 ..Default::default()
7222 };
7223 let mut session = ptr::null_mut();
7224 assert_eq!(
7225 (funcs.nvEncOpenEncodeSessionEx.unwrap())(&mut open, &mut session),
7226 NVENCSTATUS::NV_ENC_SUCCESS
7227 );
7228 enc.init_params.encodeConfig = &mut enc.encode_config;
7229 let init = guarded(Negotiated::new(enc.init_params));
7230 let status = (funcs.nvEncInitializeEncoder.unwrap())(session, &mut init.value);
7231 (funcs.nvEncDestroyEncoder.unwrap())(session);
7232 assert_eq!(status, NVENCSTATUS::NV_ENC_SUCCESS, "initialize");
7233 let reconfigure = guarded(reconfigure_params(
7234 &enc.init_params,
7235 nvenc_cur_ver(),
7236 true,
7237 true,
7238 ));
7239 let status = (funcs.nvEncReconfigureEncoder.unwrap())(
7240 enc.encoder_session,
7241 &mut reconfigure.value,
7242 );
7243 assert_eq!(status, NVENCSTATUS::NV_ENC_SUCCESS, "reconfigure");
7244 let output = enc.bitstream_buffers[0];
7245 let mut pic = NV_ENC_PIC_PARAMS {
7246 version: sv(NvStruct::PicParams),
7247 inputWidth: enc.width,
7248 inputHeight: enc.height,
7249 inputBuffer: enc.mapped_input_buffer,
7250 outputBitstream: output,
7251 bufferFmt: enc.input_format,
7252 pictureStruct: NV_ENC_PIC_STRUCT::NV_ENC_PIC_STRUCT_FRAME,
7253 ..Default::default()
7254 };
7255 assert_eq!(
7256 (funcs.nvEncEncodePicture.unwrap())(enc.encoder_session, &mut pic),
7257 NVENCSTATUS::NV_ENC_SUCCESS
7258 );
7259 let lock = guarded(Negotiated::new(NV_ENC_LOCK_BITSTREAM {
7260 version: sv(NvStruct::LockBitstream),
7261 outputBitstream: output,
7262 ..Default::default()
7263 }));
7264 let status = (funcs.nvEncLockBitstream.unwrap())(enc.encoder_session, &mut lock.value);
7265 assert_eq!(status, NVENCSTATUS::NV_ENC_SUCCESS, "lock");
7266 assert!(
7267 lock.value.bitstreamSizeInBytes > 0,
7268 "the locked bitstream carries the picture"
7269 );
7270 (funcs.nvEncUnlockBitstream.unwrap())(enc.encoder_session, output);
7271 (enc.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
7272 }
7273 let (maj, min) = nvenc_cur_ver();
7274 println!("API {maj}.{min}: the driver stayed inside every struct");
7275 }
7276
7277 #[test]
7282 #[ignore]
7283 fn gpu_pinned_uploads_read_the_source_as_it_is_now() {
7284 use crate::webcam::decode::VideoDecoder;
7285 let (w, h) = (1280usize, 720usize);
7286 let mut enc = NvencEncoder::new(&settings(w as i32, h as i32, 60.0), ptr::null())
7287 .expect("NVENC init");
7288 assert!(enc.pin_uploads, "the production default pins");
7289 let mut dec = VideoDecoder::new(Codec::H264).expect("decoder");
7290 for (source, frames) in [(0, 0..4usize), (1, 4..8usize)] {
7291 if source == 1 {
7292 enc.release_pinned_hosts();
7293 }
7294 let mut shm = vec![0u8; w * h * 4];
7295 for i in frames {
7296 shm.copy_from_slice(&moving_frame(w, h, i));
7297 let pkt = enc
7298 .encode_cpu_packed(&shm, w * 4, false, i as u64, 25, i == 0)
7299 .expect("encode");
7300 let psnr = luma_psnr(&mut dec, &pkt, &moving_frame(w, h, i), w, h);
7301 assert!(
7302 psnr > 30.0,
7303 "source {source}, frame {i}: {psnr:.1} dB against the picture just painted"
7304 );
7305 }
7306 }
7307 }
7308
7309 #[test]
7314 #[ignore]
7315 fn gpu_a_client_pool_mapped_where_the_last_one_was_uploads_its_own_picture() {
7316 use crate::webcam::decode::VideoDecoder;
7317 let (w, h) = (1280usize, 720usize);
7318 let len = w * h * 4;
7319 let mut enc = NvencEncoder::new(&settings(w as i32, h as i32, 60.0), ptr::null())
7320 .expect("NVENC init");
7321 assert!(enc.pin_uploads, "the production default pins");
7322 let mut dec = VideoDecoder::new(Codec::H264).expect("decoder");
7323 let mut at: *mut c_void = ptr::null_mut();
7324 for (pool, frames) in [(0, 0..4usize), (1, 4..8usize)] {
7325 let fd = crate::memfd_create(c"client-pool", libc::MFD_CLOEXEC);
7326 assert!(fd >= 0, "memfd_create");
7327 let flags = if at.is_null() {
7328 libc::MAP_SHARED
7329 } else {
7330 libc::MAP_SHARED | libc::MAP_FIXED
7331 };
7332 let rows = unsafe {
7335 assert_eq!(libc::ftruncate(fd, len as libc::off_t), 0);
7336 let p = libc::mmap(at, len, libc::PROT_READ | libc::PROT_WRITE, flags, fd, 0);
7337 libc::close(fd);
7338 assert_ne!(p, libc::MAP_FAILED, "mmap");
7339 std::slice::from_raw_parts_mut(p as *mut u8, len)
7340 };
7341 at = rows.as_mut_ptr().cast();
7342 for i in frames {
7343 rows.copy_from_slice(&moving_frame(w, h, i));
7344 let pkt = enc
7345 .encode_client_packed(rows, w * 4, false, i as u64, 25, i == 0)
7346 .expect("encode");
7347 let psnr = luma_psnr(&mut dec, &pkt, &moving_frame(w, h, i), w, h);
7348 assert!(
7349 psnr > 30.0,
7350 "pool {pool}, frame {i}: {psnr:.1} dB against the picture just painted"
7351 );
7352 }
7353 assert!(
7354 enc.pin_uploads,
7355 "the client path restores the production default"
7356 );
7357 unsafe { libc::munmap(at, len) };
7359 }
7360 }
7361
7362 #[test]
7372 #[ignore]
7373 fn gpu_a_session_reopened_under_a_full_gpu_keeps_its_context() {
7374 type Ctx = *mut c_void;
7375 let first = host_session(&settings(256, 128, 60.0)).expect("NVENC init");
7376 let device = first.cuda_device;
7377 drop(first);
7378 unsafe {
7379 let lib = Library::new("libcuda.so.1").expect("libcuda");
7380 let state: Symbol<unsafe extern "C" fn(CUdevice, *mut u32, *mut i32) -> CUresult> =
7381 lib.get(b"cuDevicePrimaryCtxGetState\0").unwrap();
7382 let create: Symbol<unsafe extern "C" fn(*mut Ctx, u32, CUdevice) -> CUresult> =
7383 lib.get(b"cuCtxCreate_v2\0").unwrap();
7384 let destroy: Symbol<unsafe extern "C" fn(Ctx) -> CUresult> =
7385 lib.get(b"cuCtxDestroy_v2\0").unwrap();
7386 let info: Symbol<unsafe extern "C" fn(*mut usize, *mut usize) -> CUresult> =
7387 lib.get(b"cuMemGetInfo_v2\0").unwrap();
7388 let alloc: Symbol<unsafe extern "C" fn(*mut u64, usize) -> CUresult> =
7389 lib.get(b"cuMemAlloc_v2\0").unwrap();
7390 let (mut flags, mut active) = (0u32, 0i32);
7391 assert_eq!(
7392 state(device, &mut flags, &mut active),
7393 CUresult::CUDA_SUCCESS
7394 );
7395
7396 let mut game: Ctx = ptr::null_mut();
7397 assert_eq!(create(&mut game, 0, device), CUresult::CUDA_SUCCESS);
7398 let (mut free, mut total) = (0usize, 0usize);
7399 let mut chunk = 256usize << 20;
7400 let mut taken = 0usize;
7401 while chunk >= 1 << 20 {
7402 let mut ptr = 0u64;
7403 info(&mut free, &mut total);
7404 if free < (48 << 20) + chunk || alloc(&mut ptr, chunk) != CUresult::CUDA_SUCCESS {
7405 chunk /= 2;
7406 } else {
7407 taken += chunk;
7408 }
7409 }
7410 info(&mut free, &mut total);
7411 println!(
7412 "took {} MiB; {} MiB of {} left",
7413 taken >> 20,
7414 free >> 20,
7415 total >> 20
7416 );
7417 let reopened = host_session(&settings(256, 128, 60.0)).map(|mut enc| {
7418 enc.encode_cpu_argb(&vec![0x40u8; 256 * 128 * 4], 256 * 4, 0, 25, true)
7419 .map(|out| out.len())
7420 });
7421 destroy(game);
7422 println!(
7423 "primary context active after the first session: {active}; reopened: {reopened:?}"
7424 );
7425 assert_eq!(
7426 active, 1,
7427 "the primary context outlived the session that opened it"
7428 );
7429 let bytes = reopened.expect("the session reopens").expect("and encodes");
7430 assert!(bytes > 0);
7431 }
7432 }
7433
7434 #[test]
7435 #[ignore]
7436 fn gpu_a_device_out_of_sessions_says_so() {
7437 let enc = host_session(&settings(256, 128, 60.0)).expect("NVENC init");
7438 let mut sessions = Vec::new();
7439 let refused = unsafe {
7440 loop {
7441 let mut open = NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS {
7442 version: sv(NvStruct::OpenSessionExParams),
7443 deviceType: NV_ENC_DEVICE_TYPE::NV_ENC_DEVICE_TYPE_CUDA,
7444 device: enc.cuda_context as *mut c_void,
7445 apiVersion: neg_api(),
7446 ..Default::default()
7447 };
7448 let mut session = ptr::null_mut();
7449 match (enc.nvenc_funcs.nvEncOpenEncodeSessionEx.unwrap())(&mut open, &mut session) {
7450 NVENCSTATUS::NV_ENC_SUCCESS => sessions.push(session),
7451 status => break Some(status),
7452 }
7453 if sessions.len() == 64 {
7454 break None;
7455 }
7456 }
7457 };
7458 let taken = probe_codecs(0);
7459 let opened = sessions.len();
7460 for session in sessions.drain(..) {
7461 unsafe { (enc.nvenc_funcs.nvEncDestroyEncoder.unwrap())(session) };
7462 }
7463 let Some(status) = refused else {
7464 println!("this device took 65 sessions at once: no cap to reach");
7465 return;
7466 };
7467 assert_eq!(
7468 session_refusal(status),
7469 SESSIONS_TAKEN,
7470 "session {} refused: {status:?}",
7471 opened + 2
7472 );
7473 assert_eq!(
7474 taken,
7475 Err(SESSIONS_TAKEN.to_string()),
7476 "every session taken"
7477 );
7478 assert!(
7479 probe_codecs(0).is_ok_and(|codecs| !codecs.is_empty()),
7480 "a freed session is listed again"
7481 );
7482 }
7483
7484 #[test]
7489 #[ignore]
7490 fn gpu_frame_rate_drop_keeps_the_level_the_buffer_needs() {
7491 for codec in [Codec::H264, Codec::H265] {
7492 let mut s = settings(1920, 1080, 120.0);
7493 s.codec = codec;
7494 s.video_cbr_mode = true;
7495 s.video_bitrate_kbps = 8000;
7496 let Some(mut enc) = codec_session(&s) else {
7497 continue;
7498 };
7499 let level = enc.declared_level();
7500 let frames: Vec<Vec<u8>> = (0..16).map(|i| moving_frame(1920, 1080, i)).collect();
7501 let kbit = |enc: &mut NvencEncoder, range: std::ops::Range<u64>| {
7502 let n = (range.end - range.start) as f64;
7503 let bytes: usize = range
7504 .map(|i| {
7505 enc.encode_cpu_argb(&frames[i as usize % 16], 1920 * 4, i, 25, i == 0)
7506 .expect("encode")
7507 .len()
7508 - VIDEO_HEADER_LEN
7509 })
7510 .sum();
7511 bytes as f64 * 8.0 / n / 1000.0
7512 };
7513 kbit(&mut enc, 0..20);
7514 let at120 = kbit(&mut enc, 20..80);
7515 s.target_fps = 60.0;
7516 assert!(
7517 enc.reconfigure_rate(&s),
7518 "{codec:?}: the driver refused 60 fps"
7519 );
7520 assert_eq!(
7521 enc.declared_level(),
7522 level,
7523 "{codec:?}: the level the buffer needs stays"
7524 );
7525 let at60 = kbit(&mut enc, 80..200);
7526 println!("{codec:?}: {at120:.1} kbit a frame at 120 fps, {at60:.1} at 60");
7527 assert!(
7528 at60 > 1.4 * at120,
7529 "{codec:?}: halving the frame rate left {at60:.1} kbit a frame against {at120:.1}"
7530 );
7531 }
7532 }
7533
7534 #[test]
7538 #[ignore]
7539 fn gpu_frame_rate_reaches_the_driver_as_its_fraction() {
7540 let f = frame(640, 360, 30);
7541 for codec in [Codec::H264, Codec::H265, Codec::Av1] {
7542 for (num, den) in [(60000u32, 1001u32), (120000, 1001), (144000, 1001), (60, 1)] {
7543 let fps = f64::from(num) / f64::from(den);
7544 let mut s = settings(640, 360, fps);
7545 s.codec = codec;
7546 s.video_cbr_mode = true;
7547 s.video_bitrate_kbps = 8000;
7548 let mut enc = match host_session(&s) {
7549 Ok(enc) => enc,
7550 Err(e) if e.contains("engine") => {
7551 println!("{codec:?}: {e}");
7552 break;
7553 }
7554 Err(e) => panic!("{codec:?} at {num}/{den}: {e}"),
7555 };
7556 assert_eq!(
7557 (enc.init_params.frameRateNum, enc.init_params.frameRateDen),
7558 (num, den),
7559 "{codec:?}"
7560 );
7561 let pkt = enc
7562 .encode_cpu_argb(&f, 640 * 4, 0, 25, true)
7563 .expect("encode");
7564 if codec == Codec::H264 {
7565 let (tick, scale) = crate::encoders::sps::h264_timing(&pkt[VIDEO_HEADER_LEN..])
7566 .expect("SPS timing");
7567 assert_eq!(
7568 u64::from(scale) * u64::from(den),
7569 2 * u64::from(num) * u64::from(tick),
7570 "the SPS declares {scale}/2x{tick}, not {num}/{den}"
7571 );
7572 }
7573 s.target_fps = 30.0;
7574 assert!(
7575 enc.reconfigure_rate(&s),
7576 "{codec:?}: the driver refused 30 fps"
7577 );
7578 assert_eq!(
7579 (enc.init_params.frameRateNum, enc.init_params.frameRateDen),
7580 (30, 1)
7581 );
7582 s.target_fps = fps;
7583 assert!(
7584 enc.reconfigure_rate(&s),
7585 "{codec:?}: the driver refused {num}/{den}"
7586 );
7587 assert_eq!(
7588 (enc.init_params.frameRateNum, enc.init_params.frameRateDen),
7589 (num, den),
7590 "{codec:?}"
7591 );
7592 enc.encode_cpu_argb(&f, 640 * 4, 1, 25, false)
7593 .expect("encode after the change");
7594 }
7595 }
7596 }
7597
7598 #[test]
7602 #[ignore]
7603 fn gpu_frame_rate_rise_raises_the_level_at_a_key_frame() {
7604 let mut s = settings(1920, 1080, 60.0);
7605 let mut enc = host_session(&s).expect("NVENC init");
7606 let f = frame(1920, 1080, 30);
7607 for i in 0..3u64 {
7608 enc.encode_cpu_argb(&f, 1920 * 4, i, 25, i == 0)
7609 .expect("encode");
7610 }
7611 assert_eq!(enc.declared_level(), 42);
7612 s.target_fps = 120.0;
7613 assert!(enc.reconfigure_rate(&s), "the driver refused 120 fps");
7614 let pkt = enc
7615 .encode_cpu_argb(&f, 1920 * 4, 3, 25, false)
7616 .expect("encode");
7617 assert_eq!(
7618 pkt[1] & 0x0f,
7619 FRAME_KEY,
7620 "the raised level reaches the stream at a key frame"
7621 );
7622 let sps = crate::encoders::codec::annexb_nals(&pkt[VIDEO_HEADER_LEN..])
7623 .find(|n| n[0] & 0x1f == 7)
7624 .expect("an SPS");
7625 assert_eq!(sps[3], 51, "level_idc");
7626 }
7627
7628 #[test]
7636 #[ignore]
7637 fn gpu_a_capped_constant_quantizer_codes_as_uncapped_under_a_peak_it_does_not_reach() {
7638 let (w, h) = (640usize, 360usize);
7639 let plain = settings(w as i32, h as i32, 60.0);
7640 let capped = CaptureSettings {
7641 video_bitrate_kbps: 200_000,
7642 video_vbv_multiplier: crate::encoders::CAPPED_VBV_FRAMES,
7643 ..plain.clone()
7644 };
7645 let mut a = host_session(&plain).expect("NVENC init");
7646 let mut b = host_session(&capped).expect("NVENC init");
7647 assert!(a.peak.is_none() && b.peak.is_some());
7648 for i in 0..60u64 {
7649 let f = moving_frame(w, h, i as usize);
7650 let one = a.encode_cpu_argb(&f, w * 4, i, 25, i == 0).expect("encode");
7651 let two = b.encode_cpu_argb(&f, w * 4, i, 25, i == 0).expect("encode");
7652 assert!(
7653 one == two,
7654 "frame {i}: {} bytes uncapped, {} capped",
7655 one.len(),
7656 two.len()
7657 );
7658 }
7659 assert_eq!(
7660 b.encode_config.rcParams.rateControlMode,
7661 NV_ENC_PARAMS_RC_MODE::NV_ENC_PARAMS_RC_CONSTQP
7662 );
7663 }
7664
7665 #[test]
7670 #[ignore]
7671 fn gpu_a_capped_constant_quantizer_binds_and_releases_without_a_key_frame() {
7672 use crate::encoders::codec::{FRAME_KEY, parse_video_type};
7673 let (w, h) = (640usize, 360usize);
7674 let mut s = CaptureSettings {
7675 video_bitrate_kbps: 200_000,
7676 video_vbv_multiplier: crate::encoders::CAPPED_VBV_FRAMES,
7677 ..settings(w as i32, h as i32, 60.0)
7678 };
7679 let mut enc = host_session(&s).expect("NVENC init");
7680 let floor = enc.current_qp;
7681 let mut sizes = Vec::new();
7682 let code = |enc: &mut NvencEncoder, i: u64, crf: u32| {
7683 let out = enc
7684 .encode_cpu_argb(
7685 &crate::encoders::scrolled_text(w, h, i as usize),
7686 w * 4,
7687 i,
7688 crf,
7689 i == 0,
7690 )
7691 .expect("encode");
7692 assert!(
7693 i == 0 || parse_video_type(out[1]).map(|(_, k)| k) != Some(FRAME_KEY),
7694 "frame {i} is a key frame"
7695 );
7696 (out.len() - VIDEO_HEADER_LEN) * 8
7697 };
7698 for i in 0..30 {
7699 sizes.push(code(&mut enc, i, 25));
7700 }
7701 let kbps = |f: &[usize]| f.iter().sum::<usize>() as f64 * 60.0 / f.len() as f64 / 1000.0;
7702 let free = kbps(&sizes[1..]);
7703 s.video_bitrate_kbps = (free / 3.0) as i32;
7704 enc.reconfigure_rate(&s);
7705 let mut bound = Vec::new();
7706 for i in 30..150 {
7707 bound.push(code(&mut enc, i, 25));
7708 if i >= 40 {
7709 assert!(
7710 enc.last_avg_qp >= floor,
7711 "frame {i} coded finer than the quantizer"
7712 );
7713 }
7714 }
7715 assert_eq!(
7716 enc.encode_config.rcParams.rateControlMode,
7717 NV_ENC_PARAMS_RC_MODE::NV_ENC_PARAMS_RC_CBR,
7718 "content over the peak binds the session"
7719 );
7720 let held = kbps(&bound[30..]);
7721 println!(
7722 "free {free:.0} kbps, held {held:.0} kbps under a {} kbps peak",
7723 s.video_bitrate_kbps
7724 );
7725 assert!(
7726 held <= 1.1 * f64::from(s.video_bitrate_kbps),
7727 "the session ran at {held:.0} kbps under a {} kbps peak",
7728 s.video_bitrate_kbps
7729 );
7730 s.video_bitrate_kbps = 200_000;
7731 enc.reconfigure_rate(&s);
7732 for i in 150..150 + u64::from(PeakBucket::RELEASE_FRAMES) + 10 {
7733 code(&mut enc, i, 25);
7734 }
7735 assert_eq!(
7736 enc.encode_config.rcParams.rateControlMode,
7737 NV_ENC_PARAMS_RC_MODE::NV_ENC_PARAMS_RC_CONSTQP,
7738 "a peak above what the content takes releases it"
7739 );
7740 assert_eq!(enc.last_avg_qp, floor);
7741 let paint = enc.codec.hardware_quantizer(Hardware::Nvenc, 18);
7743 for i in 190..200 {
7744 code(&mut enc, i, 18);
7745 }
7746 assert_eq!(
7747 enc.last_avg_qp, paint,
7748 "a released session codes at its paint-over quantizer"
7749 );
7750 for i in 200..210 {
7751 code(&mut enc, i, 25);
7752 }
7753 assert_eq!(enc.last_avg_qp, floor);
7754 }
7755
7756 #[test]
7760 #[ignore]
7761 fn gpu_a_bound_session_refining_blocks_is_not_released_by_their_deltas() {
7762 let (w, h) = (640usize, 360usize);
7763 let mut s = CaptureSettings {
7764 video_bitrate_kbps: 200_000,
7765 video_vbv_multiplier: crate::encoders::CAPPED_VBV_FRAMES,
7766 ..settings(w as i32, h as i32, 60.0)
7767 };
7768 let mut enc = host_session(&s).expect("NVENC init");
7769 let mut ledger = crate::ledger::BlockLedger::new(w as i32, h as i32);
7770 ledger.frame([]);
7771 let plan = ledger.plan(0, usize::MAX, usize::MAX).expect("every block");
7772 let mut sizes = Vec::new();
7773 let code = |enc: &mut NvencEncoder, i: u64, refine: bool| {
7774 if refine {
7775 enc.refine_blocks(&crate::ledger::Refinement {
7776 plan: plan.clone(),
7777 target: 0,
7778 from: 25,
7779 reach: u32::MAX,
7780 });
7781 }
7782 let out = enc
7783 .encode_cpu_argb(
7784 &crate::encoders::scrolled_text(w, h, i as usize),
7785 w * 4,
7786 i,
7787 25,
7788 i == 0,
7789 )
7790 .expect("encode");
7791 out.len() * 8
7792 };
7793 for i in 0..30 {
7794 sizes.push(code(&mut enc, i, false));
7795 }
7796 let free = sizes[1..].iter().sum::<usize>() as f64 * 60.0 / 29.0 / 1000.0;
7797 s.video_bitrate_kbps = (free / 3.0) as i32;
7798 enc.reconfigure_rate(&s);
7799 let (mut released, mut counted) = (0, 0);
7800 for i in 30..200 {
7801 code(&mut enc, i, i >= 60);
7802 if i >= 60 {
7803 released += usize::from(!enc.peak_bound());
7804 counted = counted.max(enc.peak.map_or(0, |p| p.floor_run()));
7805 }
7806 }
7807 println!(
7808 "{released} of 140 refining frames coded released, {counted} counted at the floor in a \
7809 row, free {free:.0} kbps"
7810 );
7811 assert_eq!(
7812 released, 0,
7813 "the session let go of a peak its content overflows"
7814 );
7815 assert_eq!(counted, 0, "a refining frame counted toward the release");
7816 }
7817
7818 #[test]
7819 #[ignore]
7820 fn gpu_rate_reconfigure_moves_the_vbv_initial_delay_with_the_buffer() {
7821 let (w, h) = (1920usize, 1080usize);
7822 let mut s = settings(w as i32, h as i32, 60.0);
7823 s.video_cbr_mode = true;
7824 s.video_bitrate_kbps = 8000;
7825 let mut enc = host_session(&s).expect("NVENC init");
7826 let rate = |enc: &NvencEncoder| {
7827 let rc = enc.encode_config.rcParams;
7828 (rc.averageBitRate, rc.vbvBufferSize, rc.vbvInitialDelay)
7829 };
7830 let vbv8 = cbr_vbv(&s, 8_000_000);
7831 assert_eq!(rate(&enc), (8_000_000, vbv8, vbv8));
7832 let frames: Vec<Vec<u8>> = (0..16).map(|i| moving_frame(w, h, i)).collect();
7833 enc.encode_cpu_argb(&frames[0], w * 4, 0, 25, true)
7834 .expect("encode");
7835
7836 s.video_bitrate_kbps = 2000;
7837 assert!(enc.reconfigure_rate(&s), "the driver takes the lower rate");
7838 let vbv2 = cbr_vbv(&s, 2_000_000);
7839 assert_eq!(rate(&enc), (2_000_000, vbv2, vbv2));
7840 let n = 120u64;
7841 let mut bytes = 0usize;
7842 for i in 1..=n {
7843 let pkt = enc
7844 .encode_cpu_argb(&frames[i as usize % 16], w * 4, i, 25, false)
7845 .expect("encode");
7846 bytes += pkt.len().saturating_sub(VIDEO_HEADER_LEN);
7847 }
7848 let kbps = bytes as f64 * 8.0 * 60.0 / n as f64 / 1000.0;
7849 println!("CBR 2000 kbps after the rate change: {kbps:.0} kbps over {n} steady frames");
7850 assert!(
7851 (1500.0..=2500.0).contains(&kbps),
7852 "the session encodes at the new rate: {kbps:.0} kbps"
7853 );
7854
7855 enc.encode_config.rcParams.vbvInitialDelay = vbv2 * 4;
7856 let (status, detail) = reconfigure_raw(&mut enc);
7857 println!("a VBV initial delay of four buffers: {status:?} ({detail})");
7858 enc.encode_config.rcParams.vbvInitialDelay = vbv2;
7859 assert_eq!(reconfigure_raw(&mut enc).0, NVENCSTATUS::NV_ENC_SUCCESS);
7860 enc.encode_cpu_argb(&frames[7], w * 4, n + 1, 25, false)
7861 .expect("the session still encodes");
7862 }
7863
7864 #[test]
7869 #[ignore]
7870 fn gpu_frames_carry_their_slices_and_hevc_declares_high_tier() {
7871 use crate::encoders::codec::annexb_nals;
7872 for (w, h) in [(1280usize, 720usize), (640, 480)] {
7873 for codec in [Codec::H264, Codec::H265] {
7874 let mut s = settings(w as i32, h as i32, 60.0);
7875 s.codec = codec;
7876 let Some(mut enc) = codec_session(&s) else {
7877 continue;
7878 };
7879 for i in 0..4u64 {
7880 let pkt = enc
7881 .encode_cpu_argb(&frame(w, h, 20 + i as u8), w * 4, i, 25, i == 0)
7882 .expect("encode");
7883 let au = &pkt[VIDEO_HEADER_LEN..];
7884 let vcl = annexb_nals(au)
7885 .filter(|nal| match codec {
7886 Codec::H265 => (nal[0] >> 1) & 0x3f < 32,
7887 _ => matches!(nal[0] & 0x1f, 1 | 5),
7888 })
7889 .count();
7890 let slices = if codec == Codec::H265 {
7891 1
7892 } else {
7893 SLICES_PER_FRAME as usize
7894 };
7895 assert_eq!(vcl, slices, "{codec:?} {w}x{h} frame {i} slices");
7896 if codec == Codec::H264 && i == 0 {
7897 let sps = annexb_nals(au)
7902 .find(|nal| nal[0] & 0x1f == 7)
7903 .map(rbsp)
7904 .expect("an SPS on the key frame");
7905 assert_eq!(
7906 u32::from(sps[3]),
7907 nvenc_level(Codec::H264, w as u32, h as u32, 60, 0, true),
7908 "level_idc at {w}x{h}"
7909 );
7910 }
7911 if codec == Codec::H265 && i == 0 {
7912 let sps = annexb_nals(au)
7913 .find(|nal| (nal[0] >> 1) & 0x3f == 33)
7914 .map(rbsp)
7915 .expect("an SPS on the key frame");
7916 assert_eq!((sps[3] >> 5) & 1, 1, "general_tier_flag at {w}x{h}");
7922 assert_eq!(
7923 u32::from(sps[14]),
7924 nvenc_level(Codec::H265, w as u32, h as u32, 60, 0, true),
7925 "general_level_idc at {w}x{h}"
7926 );
7927 }
7928 }
7929 }
7930 }
7931 }
7932
7933 fn hevc_slice_starts(au: &[u8], ctbs: usize) -> (Vec<u32>, Option<bool>) {
7937 use crate::encoders::codec::annexb_nals;
7938 struct Bits<'a>(&'a [u8], usize);
7939 impl Bits<'_> {
7940 fn u(&mut self, n: u32) -> u32 {
7941 (0..n).fold(0, |acc, _| {
7942 let bit = (self.0[self.1 / 8] >> (7 - self.1 % 8)) & 1;
7943 self.1 += 1;
7944 (acc << 1) | u32::from(bit)
7945 })
7946 }
7947 fn ue(&mut self) -> u32 {
7948 let mut zeros = 0;
7949 while self.u(1) == 0 {
7950 zeros += 1;
7951 }
7952 (1 << zeros) - 1 + self.u(zeros)
7953 }
7954 }
7955 let address_bits = usize::BITS - (ctbs - 1).leading_zeros();
7956 let (mut starts, mut across) = (Vec::new(), None);
7957 for nal in annexb_nals(au) {
7958 let kind = (nal[0] >> 1) & 0x3f;
7959 let body = rbsp(nal);
7960 let mut b = Bits(&body[2..], 0);
7961 if kind == 34 {
7962 b.ue();
7963 b.ue();
7964 b.u(7);
7965 b.ue();
7966 b.ue();
7967 let _qp = b.ue();
7968 b.u(2);
7969 if b.u(1) == 1 {
7970 b.ue();
7971 }
7972 b.ue();
7973 b.ue();
7974 b.u(4);
7975 let tiles = b.u(1) == 1;
7976 b.u(1);
7977 assert!(!tiles, "NVENC's HEVC codes no tiles");
7978 across = Some(b.u(1) == 1);
7979 } else if kind < 32 {
7980 let first = b.u(1) == 1;
7981 if (16..=23).contains(&kind) {
7982 b.u(1);
7983 }
7984 b.ue();
7985 starts.push(if first { 0 } else { b.u(address_bits) });
7986 }
7987 }
7988 (starts, across)
7989 }
7990
7991 #[test]
7996 #[ignore]
7997 fn gpu_hevc_band_frames_start_a_slice_at_the_band() {
7998 use crate::webcam::decode::{Decoder as _, VideoDecoder};
7999 let (w, h) = (1920usize, 1080usize);
8000 let ctbs = w.div_ceil(32) * h.div_ceil(32);
8001 let mut s = settings(w as i32, h as i32, 60.0);
8002 s.codec = Codec::H265;
8003 s.omit_stripe_headers = true;
8004 let Some(mut enc) = codec_session(&s) else {
8005 return;
8006 };
8007 let still = frame(w, h, 40);
8008 let mut frames = Vec::new();
8009 let mut encode = |enc: &mut NvencEncoder, i: u64| {
8010 let pkt = enc
8011 .encode_cpu_argb(&still, w * 4, i, 25, i == 0)
8012 .expect("encode");
8013 frames.push(pkt.clone());
8014 hevc_slice_starts(&pkt, ctbs)
8015 };
8016 let (starts, across) = encode(&mut enc, 0);
8017 assert_eq!(starts, [0], "a key frame is one slice");
8018 assert_eq!(
8019 across,
8020 Some(false),
8021 "the PPS filters across no slice boundary"
8022 );
8023 let mut i = 1u64;
8024 for share in [0.5, 0.25, 0.75, 0.13, 0.05] {
8025 assert_eq!(
8026 encode(&mut enc, i).0,
8027 [0],
8028 "frame {i} before a band is one slice"
8029 );
8030 i += 1;
8031 enc.hold_quantizer(10, Some((share, share + 0.02)));
8032 let (starts, _) = encode(&mut enc, i);
8033 i += 1;
8034 let first = (share * ctbs as f64).floor() as usize;
8035 if ctbs.div_ceil(first) <= BAND_SLICES_MAX {
8036 assert!(
8037 starts.contains(&(first as u32)) && starts.len() <= BAND_SLICES_MAX,
8038 "a band from {first} starts a slice: {starts:?}"
8039 );
8040 } else {
8041 assert_eq!(starts, [0], "a band from {first} keeps the frame's slice");
8042 }
8043 }
8044 assert_eq!(
8045 encode(&mut enc, i).0,
8046 [0],
8047 "the frame after a band is one slice"
8048 );
8049 let mut decoder = VideoDecoder::new(Codec::H265).unwrap();
8050 for (k, f) in frames.iter().enumerate() {
8051 assert!(decoder.decode(f).expect("decode"), "frame {k}");
8052 }
8053 }
8054
8055 #[test]
8059 #[ignore]
8060 fn gpu_bench_slices() {
8061 let (w, h) = (1920usize, 1080usize);
8062 let n: usize = std::env::var("NVENC_BENCH_FRAMES")
8063 .ok()
8064 .and_then(|v| v.parse().ok())
8065 .unwrap_or(240);
8066 let frames: Vec<Vec<u8>> = (0..8u8).map(|k| frame(w, h, 10 + 30 * k)).collect();
8067 for codec in [Codec::H264, Codec::H265] {
8068 let mut s = settings(w as i32, h as i32, 60.0);
8069 s.codec = codec;
8070 let mut sizes = Vec::new();
8071 for slices in [1u32, SLICES_PER_FRAME] {
8072 let tuning = NvencTuning {
8073 slices: Some(slices),
8074 ..NvencTuning::default()
8075 };
8076 let mut enc = match NvencEncoder::new_tuned(&s, ptr::null(), tuning) {
8077 Ok(enc) => enc,
8078 Err(e) => {
8079 println!("{codec:?}: {e}");
8080 continue;
8081 }
8082 };
8083 enc.encode_cpu_packed(&frames[0], w * 4, false, 0, 25, true)
8084 .expect("warm-up");
8085 let mut bytes = 0usize;
8086 per_frame(&format!("{codec:?} {slices} slice(s)"), n, |i| {
8087 bytes += enc
8088 .encode_cpu_packed(&frames[i % 8], w * 4, false, 1 + i as u64, 25, false)
8089 .expect("encode")
8090 .len();
8091 });
8092 println!(" {} kbps", bytes * 8 * 60 / n / 1000);
8093 sizes.push(bytes);
8094 }
8095 if let [one, many] = sizes[..] {
8096 println!(
8097 "{codec:?}: {SLICES_PER_FRAME} slices cost {:+.2}% bitrate at a fixed quantizer",
8098 (many as f64 / one as f64 - 1.0) * 100.0
8099 );
8100 }
8101 }
8102 }
8103
8104 #[test]
8114 #[ignore]
8115 fn gpu_bench_cbr_policy() {
8116 let (w, h) = (1920usize, 1080usize);
8117 let env = |name: &str, default: usize| {
8118 std::env::var(name)
8119 .ok()
8120 .and_then(|v| v.parse().ok())
8121 .unwrap_or(default)
8122 };
8123 let n = env("NVENC_BENCH_FRAMES", 120);
8124 let kbps = env("NVENC_BENCH_KBPS", 8000);
8125 let fps = env("NVENC_BENCH_FPS", 60);
8126 let cuts: Vec<Vec<u8>> = (0..8u8).map(|k| frame(w, h, 10 + 30 * k)).collect();
8127 let steady: Vec<Vec<u8>> = (0..16).map(|i| moving_frame(w, h, i)).collect();
8128 let other = frame(w, h, 200);
8129 let contents: Vec<(&str, Vec<&Vec<u8>>)> = vec![
8130 (
8131 "scene cuts",
8132 std::iter::once(&cuts[0])
8133 .chain((0..n).map(|i| &cuts[1 + i % 7]))
8134 .collect(),
8135 ),
8136 (
8137 "steady",
8138 std::iter::once(&steady[0])
8139 .chain((0..n).map(|i| &steady[1 + i % 15]))
8140 .collect(),
8141 ),
8142 (
8143 "one cut",
8144 std::iter::once(&other)
8145 .chain((0..n).map(|i| &steady[i % 16]))
8146 .collect(),
8147 ),
8148 ];
8149 println!("CBR {kbps} kbit/s at {fps} fps, {n} frames per row");
8150 for codec in [Codec::H264, Codec::H265] {
8151 for (content, seq) in &contents {
8152 for vbv_frames in [1.0f64, 1.5] {
8153 for full_delay in [true, false] {
8154 for slices in [SLICES_PER_FRAME, 1] {
8155 let mut s = settings(w as i32, h as i32, fps as f64);
8156 s.codec = codec;
8157 s.video_cbr_mode = true;
8158 s.video_bitrate_kbps = kbps as i32;
8159 s.video_vbv_multiplier = vbv_frames;
8160 let tuning = NvencTuning {
8161 slices: Some(slices),
8162 ..NvencTuning::default()
8163 };
8164 let mut enc = match NvencEncoder::new_tuned(&s, ptr::null(), tuning) {
8165 Ok(enc) => enc,
8166 Err(e) => {
8167 println!("{codec:?}: {e}");
8168 continue;
8169 }
8170 };
8171 if !full_delay {
8172 enc.encode_config.rcParams.vbvInitialDelay = 0;
8173 assert_eq!(
8174 reconfigure_raw(&mut enc).0,
8175 NVENCSTATUS::NV_ENC_SUCCESS
8176 );
8177 }
8178 let label = format!(
8179 "{codec:?} {content}: VBV {vbv_frames} frame(s), initial delay {}, {slices} slice(s)",
8180 if full_delay { "full" } else { "default" }
8181 );
8182 cbr_row(&label, &mut enc, codec, seq, w, h, fps);
8183 }
8184 }
8185 }
8186 }
8187 }
8188 }
8189
8190 #[test]
8197 #[ignore]
8198 fn gpu_bench_cbr_rate_control() {
8199 let (w, h) = (1920usize, 1080usize);
8200 let env = |name: &str, default: usize| {
8201 std::env::var(name)
8202 .ok()
8203 .and_then(|v| v.parse().ok())
8204 .unwrap_or(default)
8205 };
8206 let n = env("NVENC_BENCH_FRAMES", 120);
8207 let kbps = env("NVENC_BENCH_KBPS", 8000);
8208 let fps = env("NVENC_BENCH_FPS", 60);
8209 let cuts: Vec<Vec<u8>> = (0..8u8).map(|k| frame(w, h, 10 + 30 * k)).collect();
8210 let steady: Vec<Vec<u8>> = (0..16).map(|i| moving_frame(w, h, i)).collect();
8211 let other = frame(w, h, 200);
8212 let contents: Vec<(&str, Vec<&Vec<u8>>)> = vec![
8213 (
8214 "scene cuts",
8215 std::iter::once(&cuts[0])
8216 .chain((0..n).map(|i| &cuts[1 + i % 7]))
8217 .collect(),
8218 ),
8219 (
8220 "one cut",
8221 std::iter::once(&other)
8222 .chain((0..n).map(|i| &steady[i % 16]))
8223 .collect(),
8224 ),
8225 ];
8226 let variants = [
8227 (
8228 "production",
8229 NV_ENC_MULTI_PASS::NV_ENC_TWO_PASS_QUARTER_RESOLUTION,
8230 true,
8231 0,
8232 ),
8233 (
8234 "single pass",
8235 NV_ENC_MULTI_PASS::NV_ENC_MULTI_PASS_DISABLED,
8236 true,
8237 0,
8238 ),
8239 (
8240 "two-pass full",
8241 NV_ENC_MULTI_PASS::NV_ENC_TWO_PASS_FULL_RESOLUTION,
8242 true,
8243 0,
8244 ),
8245 (
8246 "strictGOPTarget off",
8247 NV_ENC_MULTI_PASS::NV_ENC_TWO_PASS_QUARTER_RESOLUTION,
8248 false,
8249 0,
8250 ),
8251 (
8252 "max QP 51",
8253 NV_ENC_MULTI_PASS::NV_ENC_TWO_PASS_QUARTER_RESOLUTION,
8254 true,
8255 51,
8256 ),
8257 ];
8258 println!("H264 CBR {kbps} kbit/s at {fps} fps, {n} frames per row");
8259 for (content, seq) in &contents {
8260 for vbv_frames in [1.0f64, 1.5] {
8261 for (name, multipass, strict, max_qp) in variants {
8262 let mut s = settings(w as i32, h as i32, fps as f64);
8263 s.video_cbr_mode = true;
8264 s.video_bitrate_kbps = kbps as i32;
8265 s.video_vbv_multiplier = vbv_frames;
8266 s.video_max_qp = max_qp;
8267 let tuning = NvencTuning {
8268 multipass,
8269 ..NvencTuning::default()
8270 };
8271 let mut enc = match NvencEncoder::new_tuned(&s, ptr::null(), tuning) {
8272 Ok(enc) => enc,
8273 Err(e) => {
8274 println!("{name}: {e}");
8275 continue;
8276 }
8277 };
8278 if !strict {
8279 enc.encode_config.rcParams.set_strictGOPTarget(0);
8280 assert_eq!(reconfigure_raw(&mut enc).0, NVENCSTATUS::NV_ENC_SUCCESS);
8281 }
8282 cbr_row(
8283 &format!("{content}: VBV {vbv_frames} frame(s), {name}"),
8284 &mut enc,
8285 Codec::H264,
8286 seq,
8287 w,
8288 h,
8289 fps,
8290 );
8291 }
8292 }
8293 }
8294 }
8295
8296 #[test]
8306 #[ignore]
8307 fn gpu_hevc_high_tier_opens_above_the_main_tier_ceiling() {
8308 let mut s = settings(3840, 2160, 60.0);
8309 s.codec = Codec::H265;
8310 s.video_cbr_mode = true;
8311 s.video_bitrate_kbps = 100_000;
8313
8314 let high = host_session(&s);
8315 match &high {
8316 Ok(enc) => unsafe {
8317 let c = enc.encode_config.encodeCodecConfig.hevcConfig;
8318 assert_eq!(c.tier, 1);
8319 println!(
8320 "HEVC High tier at {} kbps: opened, level {}",
8321 s.video_bitrate_kbps, c.level,
8322 );
8323 },
8324 Err(e) if e.contains("engine") => {
8325 println!("H265: {e}");
8326 return;
8327 }
8328 Err(e) => panic!("High tier must open above the Main-tier ceiling: {e}"),
8329 }
8330 drop(high);
8331
8332 let main = NvencEncoder::new_tuned(
8334 &s,
8335 ptr::null(),
8336 NvencTuning {
8337 hevc_high_tier: false,
8338 ..NvencTuning::default()
8339 },
8340 );
8341 match main {
8342 Ok(enc) => unsafe {
8343 println!(
8344 "HEVC Main tier at {} kbps: opened on level {}",
8345 s.video_bitrate_kbps, enc.encode_config.encodeCodecConfig.hevcConfig.level,
8346 )
8347 },
8348 Err(e) => println!(
8349 "HEVC Main tier at {} kbps: refused, the Main-tier ceiling: {e}",
8350 s.video_bitrate_kbps,
8351 ),
8352 }
8353 }
8354
8355 #[test]
8360 #[ignore]
8361 fn gpu_split_frame_follows_the_codec_and_picture() {
8362 use crate::webcam::decode::VideoDecoder;
8363 use NV_ENC_SPLIT_ENCODE_MODE as Split;
8364 for codec in [Codec::H265, Codec::Av1] {
8365 let mut s = settings(1920, 1080, 60.0);
8366 s.codec = codec;
8367 let mut enc = match host_session(&s) {
8368 Ok(enc) => enc,
8369 Err(e) if e.contains("engine") => {
8370 println!("{codec:?}: {e}");
8371 continue;
8372 }
8373 Err(e) => panic!("{codec:?}: {e}"),
8374 };
8375 if !enc.engines.is_some_and(|n| n > 1) || nvenc_cur_ver() < (12, 1) {
8376 println!(
8377 "{codec:?}: {}, API {:?}",
8378 enc.split_summary(),
8379 nvenc_cur_ver()
8380 );
8381 assert_eq!(
8382 enc.init_params.splitEncodeMode(),
8383 Split::NV_ENC_SPLIT_AUTO_MODE as u32
8384 );
8385 continue;
8386 }
8387 for (n, (w, h)) in [(1920usize, 1080usize), (3840, 2160), (1920, 1080)]
8388 .into_iter()
8389 .enumerate()
8390 {
8391 if n > 0 {
8392 s.width = w as i32;
8393 s.height = h as i32;
8394 assert!(enc.reconfigure_resolution(&s).expect("resize"));
8395 }
8396 let want = match codec {
8397 Codec::Av1 if enc.engines == Some(3) => Split::NV_ENC_SPLIT_THREE_FORCED_MODE,
8398 Codec::Av1 => Split::NV_ENC_SPLIT_AUTO_FORCED_MODE,
8399 _ if w * h >= 3840 * 2160 => Split::NV_ENC_SPLIT_AUTO_FORCED_MODE,
8400 _ => Split::NV_ENC_SPLIT_AUTO_MODE,
8401 };
8402 assert_eq!(
8403 enc.init_params.splitEncodeMode(),
8404 want as u32,
8405 "{codec:?} at {w}x{h}: {}",
8406 enc.split_summary()
8407 );
8408 let mut dec = VideoDecoder::new(codec).expect("decoder");
8409 for i in 0..6usize {
8410 let f = moving_frame(w, h, i);
8411 let pkt = enc
8412 .encode_cpu_packed(&f, w * 4, false, (n * 10 + i) as u64, 25, i == 0)
8413 .expect("encode");
8414 let psnr = luma_psnr(&mut dec, &pkt, &f, w, h);
8415 assert!(psnr > 30.0, "{codec:?} {w}x{h} frame {i}: {psnr:.1} dB");
8416 }
8417 println!("{codec:?} {w}x{h}: {}", enc.split_summary());
8418 }
8419 }
8420 }
8421
8422 #[test]
8428 #[ignore]
8429 fn gpu_cbr_targets_past_the_picture_level_open() {
8430 for (codec, kbps, level) in [
8431 (Codec::H264, 100_000, 50),
8432 (Codec::H265, 60_000, 150),
8433 (Codec::Av1, 30_000, 14),
8434 ] {
8435 let mut s = settings(1920, 1080, 60.0);
8436 s.codec = codec;
8437 s.video_cbr_mode = true;
8438 s.video_bitrate_kbps = kbps;
8439 let mut enc = match host_session(&s) {
8440 Ok(enc) => enc,
8441 Err(e) if e.contains("engine") => {
8442 println!("{codec:?}: {e}");
8443 continue;
8444 }
8445 Err(e) => panic!("{codec:?} at {kbps} kbps must open on level {level}: {e}"),
8446 };
8447 let level = if codec == Codec::Av1 && !NVENC_AV1_WEIGHTED.load(Ordering::Relaxed) {
8448 13
8449 } else {
8450 level
8451 };
8452 assert_eq!(
8453 enc.declared_level(),
8454 level,
8455 "{codec:?} level at {kbps} kbps"
8456 );
8457 s.video_bitrate_kbps = 200_000;
8458 assert!(
8459 enc.reconfigure_rate(&s),
8460 "{codec:?} live raise to 200 Mbit/s refused"
8461 );
8462 assert!(
8463 enc.declared_level() > level,
8464 "{codec:?} level did not rise with the target"
8465 );
8466 println!(
8467 "{codec:?}: {kbps} kbps opened on level {level}; 200 Mbit/s live took level {}",
8468 enc.declared_level()
8469 );
8470 }
8471 }
8472
8473 #[test]
8478 #[ignore]
8479 fn gpu_av1_declares_the_lowest_level_the_driver_admits() {
8480 let f = frame(1920, 1080, 30);
8481 for raised in [false, true] {
8482 NVENC_AV1_WEIGHTED.store(false, Ordering::Relaxed);
8483 let mut s = settings(1920, 1080, 60.0);
8484 s.codec = Codec::Av1;
8485 s.video_cbr_mode = true;
8486 s.video_bitrate_kbps = if raised { 8_000 } else { 30_000 };
8487 let mut enc = match host_session(&s) {
8488 Ok(enc) => enc,
8489 Err(e) if e.contains("engine") => {
8490 println!("Av1: {e}");
8491 return;
8492 }
8493 Err(e) => panic!("Av1 at {} kbps: {e}", s.video_bitrate_kbps),
8494 };
8495 enc.encode_cpu_argb(&f, 1920 * 4, 0, 25, true)
8496 .expect("encode");
8497 if raised {
8498 assert_eq!(enc.declared_level(), 13);
8499 s.video_bitrate_kbps = 30_000;
8500 assert!(
8501 enc.reconfigure_rate(&s),
8502 "the raise to 30 Mbit/s was refused"
8503 );
8504 }
8505 let weighted = NVENC_AV1_WEIGHTED.load(Ordering::Relaxed);
8506 assert_eq!(enc.declared_level(), if weighted { 14 } else { 13 });
8507 let pkt = enc
8508 .encode_cpu_argb(&f, 1920 * 4, 1, 25, false)
8509 .expect("encode at the level");
8510 if raised && weighted {
8511 assert!(
8512 crate::encoders::codec::av1_is_key(&pkt[VIDEO_HEADER_LEN..]),
8513 "a raised level reaches the decoder at a key frame"
8514 );
8515 }
8516 println!(
8517 "Av1 30 Mbit/s {}: level {} ({})",
8518 if raised { "raised from 8" } else { "opened" },
8519 enc.declared_level(),
8520 if weighted {
8521 "the driver holds two thirds of Annex A"
8522 } else {
8523 "Annex A"
8524 }
8525 );
8526 }
8527 }
8528
8529 #[test]
8535 #[ignore]
8536 fn gpu_reconfigures_cost_a_key_frame_only_where_asked() {
8537 let f = frame(1920, 1080, 30);
8538 let kind = |enc: &mut NvencEncoder, n: u64, crf: u32| {
8539 enc.encode_cpu_argb(&f, 1920 * 4, n, crf, n == 0)
8540 .expect("encode")[1]
8541 & 0x0f
8542 };
8543 let mut s = settings(1920, 1080, 60.0);
8544 s.video_cbr_mode = true;
8545 s.video_bitrate_kbps = 8000;
8546 let mut enc = host_session(&s).expect("NVENC init");
8547 kind(&mut enc, 0, 25);
8548 for (n, kbps) in [(1, 6000), (2, 8000), (3, 6000)] {
8549 s.video_bitrate_kbps = kbps;
8550 assert!(enc.reconfigure_rate(&s));
8551 assert_eq!(
8552 kind(&mut enc, n, 25),
8553 FRAME_DELTA,
8554 "{:?}: after the change to {kbps} kbit/s",
8555 nvenc_cur_ver()
8556 );
8557 }
8558 s.video_bitrate_kbps = 200_000;
8559 assert!(enc.reconfigure_rate(&s));
8560 assert_eq!(
8561 kind(&mut enc, 4, 25),
8562 FRAME_KEY,
8563 "{:?}: the raised level reaches the stream at a key frame",
8564 nvenc_cur_ver()
8565 );
8566 let mut enc = host_session(&settings(1920, 1080, 60.0)).expect("NVENC init");
8567 for n in 0..6u64 {
8568 let want = if n == 0 { FRAME_KEY } else { FRAME_DELTA };
8569 assert_eq!(
8570 kind(&mut enc, n, if n % 2 == 0 { 25 } else { 18 }),
8571 want,
8572 "{:?}: paint-over frame {n}",
8573 nvenc_cur_ver()
8574 );
8575 }
8576 }
8577
8578 #[test]
8581 #[ignore]
8582 fn gpu_vram_probe() {
8583 fn used_mb() -> Option<i64> {
8588 let out = std::process::Command::new("nvidia-smi")
8589 .args(["--query-gpu=memory.used", "--format=csv,noheader,nounits"])
8590 .output()
8591 .ok()?;
8592 String::from_utf8_lossy(&out.stdout)
8593 .lines()
8594 .map(|l| l.trim().parse::<i64>().ok())
8595 .sum()
8596 }
8597 let s = settings(1920, 1080, 60.0);
8598 let Some(before) = used_mb() else {
8599 println!("VRAM probe skipped: nvidia-smi answered nothing");
8600 return;
8601 };
8602 let mut enc = host_session(&s).expect("init");
8603 let f = frame(1920, 1080, 5);
8604 for i in 0..3u64 {
8605 enc.encode_cpu_argb(&f, 1920 * 4, i, 25, i == 0)
8606 .expect("encode");
8607 }
8608 println!(
8609 "VRAM delta for one 1080p session: {} MiB",
8610 used_mb().unwrap_or(before) - before
8611 );
8612 }
8613
8614 #[test]
8624 #[ignore]
8625 fn gpu_vui_describes_the_conversion() {
8626 for fullcolor in [false, true] {
8627 let mut s = settings(1280, 720, 60.0);
8628 s.video_fullcolor = fullcolor;
8629 let Some(enc) = codec_session(&s) else {
8630 continue;
8631 };
8632 let h264 = unsafe { &enc.encode_config.encodeCodecConfig.h264Config };
8634 let vui = &h264.h264VUIParameters;
8635 assert_eq!(
8636 vui.colourMatrix as u32,
8637 NV_ENC_VUI_MATRIX_COEFFS::NV_ENC_VUI_MATRIX_COEFFS_BT709 as u32,
8638 "fullcolor={fullcolor}"
8639 );
8640 assert_eq!(
8641 vui.colourPrimaries as u32,
8642 NV_ENC_VUI_COLOR_PRIMARIES::NV_ENC_VUI_COLOR_PRIMARIES_BT709 as u32
8643 );
8644 assert_eq!(
8645 vui.transferCharacteristics as u32,
8646 NV_ENC_VUI_TRANSFER_CHARACTERISTIC::NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT709 as u32
8647 );
8648 assert_eq!(vui.videoFullRangeFlag, 0, "fullcolor={fullcolor}");
8649 assert_eq!(h264.chromaFormatIDC, if enc.is_fullcolor() { 3 } else { 1 });
8650 }
8651 }
8652
8653 #[test]
8659 #[ignore]
8660 fn gpu_dmabuf_chroma_is_sited_at_the_block_center() {
8661 use crate::webcam::decode::{Codec, Decoder as _, VideoDecoder};
8662 let (w, h) = (256u32, 256u32);
8663 let s = settings(w as i32, h as i32, 60.0);
8664 let Some((gbm, mut renderer)) = gpu_render() else {
8665 return;
8666 };
8667 let egl_display = renderer.egl_context().display().get_display_handle().handle;
8668 let (_bo, dmabuf) = column_dmabuf(&gbm, &mut renderer, w, h);
8669 let mut enc = NvencEncoder::new(&s, egl_display).expect("NVENC init");
8670 assert!(enc.csc.is_some(), "this GPU took no chroma convert");
8671 let pkt = enc.encode(&dmabuf, 0, 20, true).expect("dmabuf encode");
8672 let mut dec = VideoDecoder::new(Codec::H264).expect("H.264 decoder");
8673 assert!(
8674 dec.decode(&pkt[VIDEO_HEADER_LEN..]).expect("decode"),
8675 "no picture"
8676 );
8677 let v = dec.frame().expect("decoded frame");
8678 let (mut su, mut sv) = (0.0f64, 0.0f64);
8679 let n = (v.chroma_height() * v.chroma_width()) as f64;
8680 for r in 0..v.chroma_height() {
8681 for c in 0..v.chroma_width() {
8682 let i = r * v.uv_stride + c;
8683 su += f64::from(v.u[i]);
8684 sv += f64::from(v.v[i]);
8685 }
8686 }
8687 let (u, cr) = (su / n, sv / n);
8688 println!(
8689 "[chroma-siting] dmabuf mapped as {}: ({u:.1}, {cr:.1})",
8690 mapped_kind(&enc)
8691 );
8692 let off = (u - 128.0).hypot(cr - 128.0);
8693 assert!(
8694 off <= 2.0,
8695 "the zero-copy path leaves chroma {off:.1} off neutral"
8696 );
8697 }
8698
8699 fn column_dmabuf(
8702 gbm: &gbm::Device<std::fs::File>,
8703 renderer: &mut smithay::backend::renderer::gles::GlesRenderer,
8704 w: u32,
8705 h: u32,
8706 ) -> (gbm::BufferObject<()>, Dmabuf) {
8707 use gbm::{BufferObjectFlags, Format as GbmFormat};
8708 use smithay::backend::renderer::{Bind, Color32F, Frame, Renderer};
8709 use smithay::utils::{Physical, Rectangle, Size, Transform};
8710 let bo = gbm
8711 .create_buffer_object::<()>(w, h, GbmFormat::Argb8888, BufferObjectFlags::RENDERING)
8712 .expect("GBM buffer");
8713 let mut dmabuf = crate::create_dmabuf_from_bo(&bo);
8714 {
8715 let mut fb = renderer.bind(&mut dmabuf).expect("bind dmabuf");
8716 let size: Size<i32, Physical> = (w as i32, h as i32).into();
8717 let mut frame = renderer
8718 .render(&mut fb, size, Transform::Normal)
8719 .expect("render");
8720 let full: Rectangle<i32, Physical> = Rectangle::from_size(size);
8721 frame
8722 .clear(Color32F::new(0.0, 0.0, 1.0, 1.0), &[full])
8723 .expect("clear");
8724 for x in (1..w as i32).step_by(2) {
8725 let column: Rectangle<i32, Physical> =
8726 Rectangle::new((x, 0).into(), (1, h as i32).into());
8727 frame
8728 .draw_solid(
8729 column,
8730 &[Rectangle::from_size(column.size)],
8731 Color32F::new(1.0, 1.0, 0.0, 1.0),
8732 )
8733 .expect("draw column");
8734 }
8735 let sync = frame.finish().expect("finish");
8736 let _ = sync.wait();
8737 }
8738 (bo, dmabuf)
8739 }
8740
8741 #[test]
8752 #[ignore]
8753 fn gpu_chroma_is_sited_at_the_block_center() {
8754 use crate::encoders::chroma_siting::{BT709, chroma};
8755 let (w, h) = (256usize, 256usize);
8756 let (blue, yellow) = ([0.0, 0.0, 255.0], [255.0, 255.0, 0.0]);
8757 let st = settings(w as i32, h as i32, 60.0);
8758 let mut enc = host_session(&st).expect("NVENC init");
8759 assert!(enc.csc.is_some(), "this GPU took no chroma convert");
8760 let rows = encode_and_measure(&mut enc, &color_pair(w, h, blue, yellow, false));
8761 let cols = encode_and_measure(&mut enc, &color_pair(w, h, blue, yellow, true));
8762 println!("[chroma-siting] convert: rows {rows:?} columns {cols:?}");
8763 for (label, (u, v)) in [("rows", rows), ("columns", cols)] {
8764 let off = (u - 128.0).hypot(v - 128.0);
8765 assert!(off <= 2.0, "{label} come back {off:.1} off neutral chroma");
8766 }
8767 unsafe {
8768 let cu = enc.cuda.clone();
8769 (cu.cuCtxPushCurrent_v2)(enc.cuda_context);
8770 if let Some(csc) = enc.csc.take() {
8771 csc.release(&cu, &enc.nvenc_funcs, enc.encoder_session);
8772 }
8773 (cu.cuCtxPopCurrent_v2)(ptr::null_mut());
8774 }
8775 let hardware = encode_and_measure(&mut enc, &color_pair(w, h, blue, yellow, true));
8776 let weighted = chroma([0, 1, 2].map(|i| 0.75 * blue[i] + 0.25 * yellow[i]), BT709);
8777 println!("[chroma-siting] hardware: columns {hardware:?} against 3:1 {weighted:?}");
8778 assert!(
8779 (hardware.0 - weighted.0).hypot(hardware.1 - weighted.1) <= 2.0,
8780 "NVENC's own conversion weights the columns 3:1: {hardware:?} against {weighted:?}"
8781 );
8782
8783 let full = CaptureSettings {
8784 video_fullcolor: true,
8785 ..st
8786 };
8787 if let Some(enc444) = codec_session(&full)
8788 && enc444.is_fullcolor()
8789 {
8790 assert!(
8791 enc444.csc.is_none(),
8792 "a 4:4:4 session has no chroma to site and takes no convert"
8793 );
8794 }
8795 }
8796
8797 #[test]
8806 #[ignore]
8807 fn gpu_chart_decodes_to_the_color_that_was_painted() {
8808 use crate::encoders::chroma_siting::{BT709, chart_bgra, chart_error};
8809 use crate::encoders::sps::{h264_chroma_format_idc, read_color};
8810 use crate::webcam::decode::{Decoder as _, VideoDecoder};
8811 let (w, h) = (256usize, 128usize);
8812 for (codec, fullcolor) in [
8813 (Codec::H264, false),
8814 (Codec::H264, true),
8815 (Codec::H265, true),
8816 ] {
8817 let st = CaptureSettings {
8818 codec,
8819 video_fullcolor: fullcolor,
8820 ..settings(w as i32, h as i32, 60.0)
8821 };
8822 let Some(mut enc) = codec_session(&st) else {
8823 continue;
8824 };
8825 if fullcolor && !enc.is_fullcolor() {
8826 println!("[chart] this GPU carries no 4:4:4 {codec:?}");
8827 continue;
8828 }
8829 assert_eq!(
8830 enc.csc.is_some(),
8831 !fullcolor,
8832 "the convert follows the chroma format"
8833 );
8834 let check = |enc: &mut NvencEncoder, bgra: &[u8], w: usize, frame: u64, what: &str| {
8835 let pkt = enc
8836 .encode_cpu_packed(bgra, w * 4, false, frame, 20, true)
8837 .expect("encode");
8838 let stream = &pkt[VIDEO_HEADER_LEN..];
8839 if codec == Codec::H264 && fullcolor {
8840 assert_eq!(
8841 h264_chroma_format_idc(stream),
8842 Some(3),
8843 "the SPS declares 4:4:4"
8844 );
8845 let sps = crate::encoders::codec::annexb_nals(stream)
8846 .find(|n| n[0] & 0x1f == 7)
8847 .expect("an SPS");
8848 assert_eq!(
8849 read_color(sps).map(|s| (s.matrix, s.full_range)),
8850 Some((1, false)),
8851 "the SPS declares BT.709 limited"
8852 );
8853 println!("[chart] NVENC H264 4:4:4{what}: the SPS declares 4:4:4 BT.709");
8854 return;
8855 }
8856 let mut dec = VideoDecoder::new(codec).expect("decoder");
8857 assert!(dec.decode(stream).expect("decode"), "no picture");
8858 let worst = chart_error(&dec.frame().expect("decoded frame"), BT709);
8859 println!(
8860 "[chart] NVENC {codec:?} 4:4:4 {fullcolor}{what}: worst |dRGB| {worst:.1}"
8861 );
8862 assert!(
8863 worst <= 8.0,
8864 "the session paints {worst:.1} off the chart{what}"
8865 );
8866 };
8867 check(&mut enc, &chart_bgra(w, h), w, 0, "");
8868
8869 let (w2, h2) = (w + 64, h + 32);
8872 let grown = CaptureSettings {
8873 width: w2 as i32,
8874 height: h2 as i32,
8875 ..st
8876 };
8877 assert!(
8878 enc.reconfigure_resolution(&grown).expect("resize"),
8879 "the resize was taken"
8880 );
8881 assert_eq!(
8882 enc.csc.is_some(),
8883 !fullcolor,
8884 "the convert followed the resize"
8885 );
8886 check(&mut enc, &chart_bgra(w2, h2), w2, 1, " after a resize");
8887 }
8888 }
8889
8890 #[test]
8897 #[ignore]
8898 fn gpu_hardware_conversion_matches_the_declared_matrix() {
8899 use crate::encoders::chroma_siting::{BT601, BT709, chart_bgra, chart_error, chroma};
8900 use crate::webcam::decode::{Decoder as _, VideoDecoder};
8901 let (w, h) = (256usize, 256usize);
8902 let (blue, yellow) = ([0.0, 0.0, 255.0], [255.0, 255.0, 0.0]);
8903 let st = settings(w as i32, h as i32, 60.0);
8904 let tuning = NvencTuning {
8905 hardware_csc: true,
8906 ..Default::default()
8907 };
8908 let mut enc = NvencEncoder::new_tuned(&st, ptr::null(), tuning).expect("NVENC init");
8909 assert!(
8910 enc.csc.is_none(),
8911 "the session was asked for NVENC's own conversion"
8912 );
8913
8914 let pkt = enc
8915 .encode_cpu_packed(&chart_bgra(w, h), w * 4, false, 0, 20, true)
8916 .expect("encode");
8917 let mut dec = VideoDecoder::new(Codec::H264).expect("H.264 decoder");
8918 assert!(
8919 dec.decode(&pkt[VIDEO_HEADER_LEN..]).expect("decode"),
8920 "no picture"
8921 );
8922 let worst = chart_error(&dec.frame().expect("decoded frame"), BT709);
8923 println!("[csc] NVENC's own conversion: chart worst |dRGB| {worst:.1}");
8924 assert!(
8925 worst <= 8.0,
8926 "the hardware conversion paints {worst:.1} off the declared matrix"
8927 );
8928
8929 let hardware = encode_and_measure(&mut enc, &color_pair(w, h, blue, yellow, true));
8930 let mix = [0, 1, 2].map(|i| 0.75 * blue[i] + 0.25 * yellow[i]);
8931 let (weighted_601, weighted_709) = (chroma(mix, BT601), chroma(mix, BT709));
8932 let off = |c: (f64, f64)| (hardware.0 - c.0).hypot(hardware.1 - c.1);
8933 println!(
8934 "[csc] NVENC's own conversion: columns {hardware:?} against the 3:1 mix under \
8935 BT.709 {weighted_709:?} and BT.601 {weighted_601:?}"
8936 );
8937 assert!(
8938 off(weighted_709) <= 2.0 && off(weighted_709) < off(weighted_601),
8939 "the hardware conversion did not follow the declared matrix: {hardware:?}"
8940 );
8941 }
8942
8943 #[test]
8949 #[ignore]
8950 fn gpu_external_pointer_is_encoded_where_it_lies() {
8951 use crate::encoders::chroma_siting::{BT709, ycbcr};
8952 use crate::webcam::decode::VideoDecoder;
8953 let (w, h) = (256usize, 128usize);
8954 let st = settings(w as i32, h as i32, 60.0);
8955 let mut enc = host_session(&st).expect("NVENC init");
8956 let paint = [32u8, 192, 64];
8957 let poison = [240u8, 16, 200];
8958 let (external, external_pitch) = unsafe {
8959 let cu = enc.cuda.clone();
8960 (cu.cuCtxPushCurrent_v2)(enc.cuda_context);
8961 let (mut ptr_, mut pitch) = (0 as CUdeviceptr, 0usize);
8962 assert_eq!(
8963 (cu.cuMemAllocPitch_v2)(&mut ptr_, &mut pitch, w * 4, h, 16),
8964 CUresult::CUDA_SUCCESS
8965 );
8966 paint_surface(&cu, ptr_, pitch, w, h, paint);
8967 paint_surface(&cu, enc.input_device_ptr, enc.input_pitch, w, h, poison);
8968 (cu.cuCtxPopCurrent_v2)(ptr::null_mut());
8969 (ptr_, pitch)
8970 };
8971 let pkt = enc
8972 .encode_cuda_pitch(external, external_pitch, false, 0, 20, true)
8973 .expect("external encode");
8974 let mut dec = VideoDecoder::new(Codec::H264).expect("H.264 decoder");
8975 let (mean, _) = decoded_means(&mut dec, &pkt, (0, 0, w as i32, h as i32));
8976 let want = ycbcr(paint.map(f64::from), BT709);
8977 println!("[external] decoded {mean:?} against the painted {want:?}");
8978 for i in 0..3 {
8979 assert!(
8980 (mean[i] - want[i]).abs() <= 8.0,
8981 "plane {i} came back {:.1}, not the painted {:.1}: the frame was staged, not read in place",
8982 mean[i],
8983 want[i]
8984 );
8985 }
8986 assert!(
8987 enc.encode_cuda_pitch(external, w * 4 - 4, false, 1, 20, false)
8988 .is_err(),
8989 "a pitch too short for the session's rows must be refused"
8990 );
8991 enc.release_external_input();
8992 unsafe {
8993 let cu = enc.cuda.clone();
8994 (cu.cuCtxPushCurrent_v2)(enc.cuda_context);
8995 (cu.cuMemFree_v2)(external);
8996 (cu.cuCtxPopCurrent_v2)(ptr::null_mut());
8997 }
8998 }
8999
9000 #[test]
9005 #[ignore]
9006 fn gpu_an_uploaded_frame_is_hashed_beside_its_encode() {
9007 let (w, h, rows) = (1920usize, 1080usize, 32u32);
9008 let mut enc = host_session(&settings(w as i32, h as i32, 60.0)).expect("NVENC init");
9009 let mut picture = frame(w, h, 3);
9010 let encode = |enc: &mut NvencEncoder, picture: &[u8], n: u64, hash: bool| {
9011 if hash {
9012 assert!(enc.hash_next_upload(rows), "the driver takes the band hash");
9013 }
9014 let out = enc.encode_cpu_packed(picture, w * 4, false, n, 23, n == 0);
9015 assert!(out.is_ok_and(|o| !o.is_empty()), "frame {n} encodes");
9016 enc.take_upload_hashes()
9017 };
9018 let first = encode(&mut enc, &picture, 0, true).expect("hashes come with the frame");
9019 assert_eq!(first.len(), h.div_ceil(rows as usize));
9020 assert_eq!(
9021 encode(&mut enc, &picture, 1, true),
9022 Some(first.clone()),
9023 "one picture, one hash"
9024 );
9025 assert_eq!(encode(&mut enc, &picture, 2, false), None, "none unasked");
9026 for y in 200..220 {
9027 for x in 300..302 {
9028 picture[(y * w + x) * 4..][..3].copy_from_slice(&[0, 0, 0]);
9029 }
9030 }
9031 let caret = encode(&mut enc, &picture, 3, true).unwrap();
9032 let changed: Vec<usize> = (0..first.len()).filter(|&i| first[i] != caret[i]).collect();
9033 assert_eq!(changed, vec![200 / rows as usize], "a 2x20 caret");
9034
9035 let mut timed = |hash: bool, from: u64| {
9036 let t = std::time::Instant::now();
9037 for n in from..from + 120 {
9038 picture[(n as usize % h) * w * 4] ^= 0xff;
9039 encode(&mut enc, &picture, n, hash);
9040 }
9041 t.elapsed().as_secs_f64() * 1e3 / 120.0
9042 };
9043 let (plain, hashed) = (timed(false, 4), timed(true, 124));
9044 let (plain2, hashed2) = (timed(false, 244), timed(true, 364));
9045 eprintln!(
9046 "1080p host encode: {:.3} / {:.3} ms plain, {:.3} / {:.3} ms with the device hash",
9047 plain, plain2, hashed, hashed2
9048 );
9049 }
9050
9051 #[test]
9055 #[ignore]
9056 fn gpu_band_hash_reads_the_bands_that_changed() {
9057 let (w, h, rows) = (1920usize, 1080usize, 32usize);
9058 let pitch = w * 4 + 64;
9059 let mut enc = host_session(&settings(w as i32, h as i32, 60.0)).expect("NVENC init");
9060 let (cu, ctx) = (enc.cuda.clone(), enc.cuda_context);
9061 let picture = frame(w, h, 3);
9062 let mut host = vec![0u8; pitch * h];
9063 for y in 0..h {
9064 host[y * pitch..][..w * 4].copy_from_slice(&picture[y * w * 4..][..w * 4]);
9065 }
9066 let mut dev: CUdeviceptr = 0;
9067 unsafe {
9068 (cu.cuCtxPushCurrent_v2)(ctx);
9069 assert_eq!(
9070 (cu.cuMemAlloc_v2)(&mut dev, pitch * h),
9071 CUresult::CUDA_SUCCESS
9072 );
9073 (cu.cuCtxPopCurrent_v2)(ptr::null_mut());
9074 }
9075 let upload = |host: &[u8]| unsafe {
9076 (cu.cuCtxPushCurrent_v2)(ctx);
9077 assert_eq!(
9078 (cu.cuMemcpyHtoD_v2)(dev, host.as_ptr() as *const c_void, host.len()),
9079 CUresult::CUDA_SUCCESS
9080 );
9081 (cu.cuCtxPopCurrent_v2)(ptr::null_mut());
9082 };
9083 let changed = |a: &[u64], b: &[u64]| -> Vec<usize> {
9084 (0..a.len()).filter(|&i| a[i] != b[i]).collect()
9085 };
9086 upload(&host);
9087 let hash =
9088 |enc: &mut NvencEncoder| enc.band_hashes(dev, pitch, w as u32, h as u32, rows as u32);
9089 let jit = std::time::Instant::now();
9090 let first = hash(&mut enc).expect("the driver takes the band hash");
9091 let jit = jit.elapsed().as_secs_f64() * 1e3;
9092 assert_eq!(first.len(), h.div_ceil(rows));
9093 assert_eq!(hash(&mut enc).unwrap(), first, "one frame, one hash");
9094
9095 for y in 200..220 {
9096 for x in 300..302 {
9097 host[y * pitch + x * 4..][..3].copy_from_slice(&[0, 0, 0]);
9098 }
9099 }
9100 upload(&host);
9101 let caret = hash(&mut enc).unwrap();
9102 assert_eq!(changed(&first, &caret), vec![200 / rows], "a 2x20 caret");
9103
9104 for y in 0..h {
9105 for x in 0..w {
9106 host[y * pitch + x * 4 + 3] ^= 0x5a;
9107 }
9108 host[y * pitch + w * 4..][..64].fill(0xa5);
9109 }
9110 upload(&host);
9111 assert_eq!(
9112 hash(&mut enc).unwrap(),
9113 caret,
9114 "the alpha byte and the padding are not the picture"
9115 );
9116
9117 let (a, b) = (650 * pitch + 100 * 4, 650 * pitch + 101 * 4);
9118 assert_ne!(host[a..a + 3], host[b..b + 3]);
9119 for i in 0..4 {
9120 host.swap(a + i, b + i);
9121 }
9122 upload(&host);
9123 assert_eq!(
9124 changed(&caret, &hash(&mut enc).unwrap()),
9125 vec![650 / rows],
9126 "two pixels swapped"
9127 );
9128
9129 let mut time = |w: u32, h: u32| -> Vec<f64> {
9130 let mut ms: Vec<f64> = (0..300)
9131 .map(|_| {
9132 let t = std::time::Instant::now();
9133 enc.band_hashes(dev, pitch, w, h, rows as u32).unwrap();
9134 t.elapsed().as_secs_f64() * 1e3
9135 })
9136 .collect();
9137 ms.sort_by(f64::total_cmp);
9138 ms
9139 };
9140 let (hashing, fixed) = (time(w as u32, h as u32), time(64, rows as u32));
9141 let mut encoding: Vec<f64> = (0..120u64)
9142 .map(|i| {
9143 let t = std::time::Instant::now();
9144 enc.encode_cuda_pitch(dev, pitch, false, i, 25, i == 0)
9145 .expect("encode");
9146 t.elapsed().as_secs_f64() * 1e3
9147 })
9148 .collect();
9149 encoding.sort_by(f64::total_cmp);
9150 println!(
9151 "[band hash] 1080p: {:.3} ms median, {:.3} ms p99 a frame ({:.3} ms of it for a 64x{rows} \
9152 one), the first {jit:.1} ms with the module's load; the encode {:.3} ms median",
9153 hashing[150], hashing[297], fixed[150], encoding[60]
9154 );
9155 enc.release_external_input();
9156 unsafe {
9157 (cu.cuCtxPushCurrent_v2)(ctx);
9158 (cu.cuMemFree_v2)(dev);
9159 (cu.cuCtxPopCurrent_v2)(ptr::null_mut());
9160 }
9161 }
9162
9163 unsafe fn paint_surface(
9165 cuda: &CudaFunctions,
9166 dst: CUdeviceptr,
9167 dst_pitch: usize,
9168 w: usize,
9169 h: usize,
9170 rgb: [u8; 3],
9171 ) {
9172 let mut host = vec![255u8; w * h * 4];
9173 for px in host.as_chunks_mut::<4>().0 {
9174 px[..3].copy_from_slice(&[rgb[2], rgb[1], rgb[0]]);
9175 }
9176 let copy = CUDA_MEMCPY2D {
9177 srcMemoryType: CUmemorytype::CU_MEMORYTYPE_HOST,
9178 srcHost: host.as_ptr() as *const c_void,
9179 srcPitch: w * 4,
9180 dstMemoryType: CUmemorytype::CU_MEMORYTYPE_DEVICE,
9181 dstDevice: dst,
9182 dstPitch: dst_pitch,
9183 WidthInBytes: w * 4,
9184 Height: h,
9185 ..Default::default()
9186 };
9187 assert_eq!(
9188 (cuda.cuMemcpy2D_v2)(©),
9189 CUresult::CUDA_SUCCESS,
9190 "paint the surface"
9191 );
9192 }
9193
9194 fn color_pair(w: usize, h: usize, a: [f64; 3], b: [f64; 3], by_column: bool) -> Vec<u8> {
9197 let mut f = vec![255u8; w * h * 4];
9198 for y in 0..h {
9199 for x in 0..w {
9200 let p = if (if by_column { x } else { y }) % 2 == 0 {
9201 a
9202 } else {
9203 b
9204 };
9205 f[(y * w + x) * 4..][..3].copy_from_slice(&[p[2] as u8, p[1] as u8, p[0] as u8]);
9206 }
9207 }
9208 f
9209 }
9210
9211 fn encode_and_measure(enc: &mut NvencEncoder, bgra: &[u8]) -> (f64, f64) {
9213 use crate::webcam::decode::{Decoder as _, VideoDecoder};
9214 let w = enc.width() as usize;
9215 let pkt = enc
9216 .encode_cpu_packed(bgra, w * 4, false, 0, 20, true)
9217 .expect("packed encode");
9218 let mut dec = VideoDecoder::new(Codec::H264).expect("H.264 decoder");
9219 assert!(
9220 dec.decode(&pkt[VIDEO_HEADER_LEN..]).expect("decode"),
9221 "no picture from this access unit"
9222 );
9223 let v = dec.frame().expect("decoded frame");
9224 let (mut su, mut sv) = (0.0f64, 0.0f64);
9225 let n = (v.chroma_height() * v.chroma_width()) as f64;
9226 for r in 0..v.chroma_height() {
9227 for c in 0..v.chroma_width() {
9228 let i = r * v.uv_stride + c;
9229 su += f64::from(v.u[i]);
9230 sv += f64::from(v.v[i]);
9231 }
9232 }
9233 (su / n, sv / n)
9234 }
9235
9236 #[test]
9239 #[ignore]
9240 fn gpu_bench_chroma_convert() {
9241 use crate::encoders::software::convert_to_yuv_mt;
9242 for (w, h) in [(1920usize, 1080usize), (3840, 2160)] {
9243 let st = settings(w as i32, h as i32, 60.0);
9244 let bgra = crate::encoders::chroma_siting::bgra(w, h);
9245 let mut kernel = host_session(&st).expect("NVENC init");
9246 assert!(kernel.csc.is_some());
9247 let with = bench_frames(&mut kernel, &bgra);
9248 let mut hardware = host_session(&st).expect("NVENC init");
9249 unsafe {
9250 let cu = hardware.cuda.clone();
9251 (cu.cuCtxPushCurrent_v2)(hardware.cuda_context);
9252 if let Some(csc) = hardware.csc.take() {
9253 csc.release(&cu, &hardware.nvenc_funcs, hardware.encoder_session);
9254 }
9255 (cu.cuCtxPopCurrent_v2)(ptr::null_mut());
9256 }
9257 let without = bench_frames(&mut hardware, &bgra);
9258 let (cw, ch) = (w / 2, h / 2);
9259 let (mut y, mut u, mut v) = (vec![0u8; w * h], vec![0u8; cw * ch], vec![0u8; cw * ch]);
9260 let mut host = f64::MAX;
9261 for _ in 0..20 {
9262 let t = std::time::Instant::now();
9263 convert_to_yuv_mt(
9264 &bgra,
9265 (w * 4) as u32,
9266 w,
9267 h,
9268 false,
9269 false,
9270 false,
9271 false,
9272 &mut y,
9273 &mut u,
9274 &mut v,
9275 (w, cw),
9276 8,
9277 )
9278 .expect("host convert");
9279 host = host.min(t.elapsed().as_secs_f64() * 1000.0);
9280 }
9281 println!(
9282 "[bench] {w}x{h}: encode {with:.2} ms with the convert, {without:.2} ms on NVENC's own; the host convert costs {host:.2} ms on 8 threads"
9283 );
9284 }
9285 }
9286
9287 fn bench_frames(enc: &mut NvencEncoder, bgra: &[u8]) -> f64 {
9289 let w = enc.width() as usize;
9290 for i in 0..10 {
9291 enc.encode_cpu_packed(bgra, w * 4, false, i, 25, i == 0)
9292 .expect("warm-up");
9293 }
9294 let t = std::time::Instant::now();
9295 for i in 0..60u64 {
9296 enc.encode_cpu_packed(bgra, w * 4, false, 10 + i, 25, false)
9297 .expect("encode");
9298 }
9299 t.elapsed().as_secs_f64() * 1000.0 / 60.0
9300 }
9301
9302 #[test]
9303 #[ignore]
9304 fn gpu_init_above_default_headroom() {
9305 let s = settings(2160, 4096, 30.0);
9306 let mut enc = host_session(&s).expect("NVENC init portrait 4K");
9307 assert_eq!(enc.init_params.maxEncodeWidth, 4096);
9308 assert_eq!(enc.init_params.maxEncodeHeight, 4096);
9309 let f = frame(2160, 4096, 20);
9310 let pkt = enc
9311 .encode_cpu_argb(&f, 2160 * 4, 0, 25, true)
9312 .expect("encode portrait 4K");
9313 assert_eq!(wire_dims(&pkt), (2160, 4096));
9314 }
9315
9316 fn thread_cpu() -> std::time::Duration {
9319 let mut ts = libc::timespec {
9320 tv_sec: 0,
9321 tv_nsec: 0,
9322 };
9323 unsafe { libc::clock_gettime(libc::CLOCK_THREAD_CPUTIME_ID, &mut ts) };
9324 std::time::Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32)
9325 }
9326
9327 fn per_frame(label: &str, n: usize, mut f: impl FnMut(usize)) -> (f64, f64) {
9329 let t0 = std::time::Instant::now();
9330 let c0 = thread_cpu();
9331 for i in 0..n {
9332 f(i);
9333 }
9334 let wall = t0.elapsed().as_secs_f64() * 1e6 / n as f64;
9335 let cpu = (thread_cpu() - c0).as_secs_f64() * 1e6 / n as f64;
9336 println!("{label}: {wall:.0} us wall/frame, {cpu:.0} us cpu/frame ({n} frames)");
9337 (wall, cpu)
9338 }
9339
9340 pub(crate) fn gpu_render() -> Option<(
9345 gbm::Device<std::fs::File>,
9346 smithay::backend::renderer::gles::GlesRenderer,
9347 )> {
9348 let node = std::env::var("PIXELFLUX_TEST_RENDER_NODE")
9349 .ok()
9350 .or_else(|| crate::auto_select_render_node(Some("nvidia")))
9351 .expect("no NVIDIA render node");
9352 let (gbm, renderer) =
9353 crate::gpu_render_init(std::path::Path::new(&node)).expect("GPU render init");
9354 let bo = gbm.create_buffer_object::<()>(
9355 64,
9356 64,
9357 gbm::Format::Argb8888,
9358 gbm::BufferObjectFlags::RENDERING,
9359 );
9360 if let Err(e) = bo {
9361 println!("{node}: GBM allocates nothing ({e})");
9362 return None;
9363 }
9364 Some((gbm, renderer))
9365 }
9366
9367 pub(crate) const BG: [f32; 3] = [0.1, 0.2, 0.8];
9369 pub(crate) const FG: [f32; 3] = [0.9, 0.3, 0.1];
9370
9371 pub(crate) fn painted_ycbcr(rgb: [f32; 3]) -> [f64; 3] {
9373 use crate::encoders::chroma_siting::{BT709, ycbcr};
9374 ycbcr(rgb.map(|c| f64::from(c) * 255.0), BT709)
9375 }
9376
9377 pub(crate) fn block_rect(w: u32, h: u32, seed: u32) -> (i32, i32, i32, i32) {
9380 let x = ((seed * 37) % (w / 2)) as i32 & !1;
9381 let y = ((seed * 53) % (h / 2)) as i32 & !1;
9382 (x, y, (w / 4) as i32 & !1, (h / 4) as i32 & !1)
9383 }
9384
9385 pub(crate) fn painted_dmabuf(
9389 gbm: &gbm::Device<std::fs::File>,
9390 renderer: &mut smithay::backend::renderer::gles::GlesRenderer,
9391 w: u32,
9392 h: u32,
9393 seed: u32,
9394 ) -> (gbm::BufferObject<()>, Dmabuf) {
9395 use gbm::{BufferObjectFlags, Format as GbmFormat};
9396 let bo = gbm
9397 .create_buffer_object::<()>(w, h, GbmFormat::Argb8888, BufferObjectFlags::RENDERING)
9398 .expect("GBM buffer");
9399 let mut dmabuf = crate::create_dmabuf_from_bo(&bo);
9400 paint_dmabuf(renderer, &mut dmabuf, w, h, seed);
9401 (bo, dmabuf)
9402 }
9403
9404 pub(crate) fn paint_dmabuf(
9406 renderer: &mut smithay::backend::renderer::gles::GlesRenderer,
9407 dmabuf: &mut Dmabuf,
9408 w: u32,
9409 h: u32,
9410 seed: u32,
9411 ) {
9412 use smithay::backend::renderer::{Bind, Color32F, Frame, Renderer};
9413 use smithay::utils::{Physical, Rectangle, Size, Transform};
9414 {
9415 let mut fb = renderer.bind(dmabuf).expect("bind dmabuf");
9416 let size: Size<i32, Physical> = (w as i32, h as i32).into();
9417 let mut frame = renderer
9418 .render(&mut fb, size, Transform::Normal)
9419 .expect("render");
9420 let full: Rectangle<i32, Physical> = Rectangle::from_size(size);
9421 frame
9422 .clear(Color32F::new(BG[0], BG[1], BG[2], 1.0), &[full])
9423 .expect("clear");
9424 let (x, y, bw, bh) = block_rect(w, h, seed);
9425 let block: Rectangle<i32, Physical> = Rectangle::new((x, y).into(), (bw, bh).into());
9426 frame
9427 .draw_solid(
9428 block,
9429 &[Rectangle::from_size(block.size)],
9430 Color32F::new(FG[0], FG[1], FG[2], 1.0),
9431 )
9432 .expect("draw block");
9433 let sync = frame.finish().expect("finish");
9434 let _ = sync.wait();
9435 }
9436 }
9437
9438 pub(crate) fn decoded_means(
9441 dec: &mut crate::webcam::decode::VideoDecoder,
9442 pkt: &[u8],
9443 rect: (i32, i32, i32, i32),
9444 ) -> ([f64; 3], [f64; 3]) {
9445 use crate::webcam::decode::Decoder;
9446 assert!(
9447 dec.decode(&pkt[VIDEO_HEADER_LEN..]).expect("decode"),
9448 "no picture from this access unit"
9449 );
9450 let v = dec.frame().expect("decoded frame");
9451 let (rx, ry, rw, rh) = rect;
9452 let inside = |x: usize, y: usize| {
9453 x as i32 >= rx && (x as i32) < rx + rw && y as i32 >= ry && (y as i32) < ry + rh
9454 };
9455 let mut acc = [[0f64; 3]; 2];
9456 let mut cnt = [0f64; 2];
9457 for y in 0..v.height {
9458 for x in 0..v.width {
9459 let k = if inside(x, y) { 0 } else { 1 };
9460 acc[k][0] += f64::from(v.y[y * v.y_stride + x]);
9461 acc[k][1] += f64::from(v.u[(y / 2) * v.uv_stride + x / 2]);
9462 acc[k][2] += f64::from(v.v[(y / 2) * v.uv_stride + x / 2]);
9463 cnt[k] += 1.0;
9464 }
9465 }
9466 let mean = |k: usize| [acc[k][0] / cnt[k], acc[k][1] / cnt[k], acc[k][2] / cnt[k]];
9467 (mean(0), mean(1))
9468 }
9469
9470 pub(crate) fn assert_painted(label: &str, block: [f64; 3], bg: [f64; 3], tol: f64) {
9473 let (eb, eg) = (painted_ycbcr(FG), painted_ycbcr(BG));
9474 for i in 0..3 {
9475 assert!(
9476 (block[i] - eb[i]).abs() <= tol,
9477 "{label}: block plane {i} = {:.1}, expected {:.1}",
9478 block[i],
9479 eb[i]
9480 );
9481 assert!(
9482 (bg[i] - eg[i]).abs() <= tol,
9483 "{label}: background plane {i} = {:.1}, expected {:.1}",
9484 bg[i],
9485 eg[i]
9486 );
9487 }
9488 }
9489
9490 fn all_direct(enc: &NvencEncoder) -> bool {
9492 !enc.dmabuf_cache.is_empty()
9493 && enc
9494 .dmabuf_cache
9495 .values()
9496 .all(|c| matches!(c.input, DmaBufInput::Direct { .. }))
9497 }
9498
9499 fn mapped_kind(enc: &NvencEncoder) -> &'static str {
9501 match enc
9502 .dmabuf_cache
9503 .values()
9504 .next()
9505 .map(|c| c.egl_frame.frame_type)
9506 {
9507 Some(CU_EGL_FRAME_TYPE_PITCH) => "pitch-linear",
9508 Some(CU_EGL_FRAME_TYPE_ARRAY) => "a CUDA array",
9509 _ => "an unknown frame kind",
9510 }
9511 }
9512
9513 #[test]
9520 #[ignore]
9521 fn gpu_dmabuf_direct_and_copy_paths_decode_to_the_paint() {
9522 use crate::webcam::decode::{Codec, VideoDecoder};
9523 let (w, h) = (1920u32, 1080u32);
9524 let s = settings(w as i32, h as i32, 60.0);
9525 let Some((gbm, mut renderer)) = gpu_render() else {
9526 return;
9527 };
9528 let egl_display = renderer.egl_context().display().get_display_handle().handle;
9529 let bufs: Vec<_> = (1..=2u32)
9530 .map(|seed| (seed, painted_dmabuf(&gbm, &mut renderer, w, h, seed)))
9531 .collect();
9532 let mut enc = NvencEncoder::new(&s, egl_display).expect("NVENC init");
9533
9534 let run = |enc: &mut NvencEncoder, label: &str| -> Vec<Vec<u8>> {
9535 let mut dec = VideoDecoder::new(Codec::H264).expect("H.264 decoder");
9536 let mut out = Vec::new();
9537 for i in 0..6u64 {
9538 let (seed, (_, dmabuf)) = &bufs[(i % 2) as usize];
9539 let pkt = enc.encode(dmabuf, i, 25, i == 0).expect("dmabuf encode");
9540 assert_eq!(wire_dims(&pkt), (w as u16, h as u16));
9541 let (block, bg) = decoded_means(&mut dec, &pkt, block_rect(w, h, *seed));
9542 assert_painted(&format!("{label} frame {i}"), block, bg, 6.0);
9543 out.push(pkt[VIDEO_HEADER_LEN..].to_vec());
9544 }
9545 out
9546 };
9547
9548 if all_direct(&enc) {
9551 unsafe {
9552 let cu = enc.cuda.clone();
9553 (cu.cuCtxPushCurrent_v2)(enc.cuda_context);
9554 paint_surface(
9555 &cu,
9556 enc.input_device_ptr,
9557 enc.input_pitch,
9558 w as usize,
9559 h as usize,
9560 [240, 16, 200],
9561 );
9562 (cu.cuCtxPopCurrent_v2)(ptr::null_mut());
9563 }
9564 }
9565 let direct = run(&mut enc, "direct");
9566 println!(
9567 "driver mapped the dmabuf {}: {}",
9568 mapped_kind(&enc),
9569 if all_direct(&enc) {
9570 "registered in place"
9571 } else {
9572 "direct registration unavailable, copy arm used"
9573 }
9574 );
9575
9576 let mut copy_only = NvencEncoder::new(&s, egl_display).expect("NVENC init");
9580 copy_only.direct_dmabuf = false;
9581 let copied = run(&mut copy_only, "copy");
9582 assert!(!all_direct(©_only));
9583 let identical = direct.iter().zip(&copied).all(|(a, b)| a == b);
9584 println!(
9585 "direct vs copy streams: {} ({} vs {} bytes)",
9586 if identical {
9587 "byte-identical"
9588 } else {
9589 "differ"
9590 },
9591 direct.iter().map(Vec::len).sum::<usize>(),
9592 copied.iter().map(Vec::len).sum::<usize>()
9593 );
9594 if let Ok(dir) = std::env::var("NVENC_TEST_DUMP_DIR") {
9595 std::fs::write(format!("{dir}/dmabuf-direct.h264"), direct.concat()).unwrap();
9596 std::fs::write(format!("{dir}/dmabuf-copy.h264"), copied.concat()).unwrap();
9597 }
9598 }
9599
9600 #[test]
9605 #[ignore]
9606 fn gpu_dmabuf_ten_bit_arms_agree() {
9607 let (w, h) = (1920u32, 1080u32);
9608 let Some((gbm, mut renderer)) = gpu_render() else {
9609 return;
9610 };
9611 let egl_display = renderer.egl_context().display().get_display_handle().handle;
9612 let bufs: Vec<_> = (1..=2u32)
9613 .map(|seed| painted_dmabuf(&gbm, &mut renderer, w, h, seed))
9614 .collect();
9615 for fullcolor in [false, true] {
9616 let mut s = settings(w as i32, h as i32, 60.0);
9617 s.codec = Codec::H265;
9618 s.video_fullcolor = fullcolor;
9619 s.video_bit_depth = 10;
9620 let mut streams = Vec::new();
9621 for direct in [true, false] {
9622 let mut enc = match NvencEncoder::new(&s, egl_display) {
9623 Ok(enc) => enc,
9624 Err(e) if e.contains("engine") => {
9625 println!("{e}");
9626 return;
9627 }
9628 Err(e) => panic!("NVENC init: {e}"),
9629 };
9630 if enc.bit_depth() != 10 {
9631 println!("this device codes no 10-bit HEVC");
9632 return;
9633 }
9634 enc.direct_dmabuf = direct;
9635 let stream: Vec<u8> = (0..6u64)
9636 .flat_map(|i| {
9637 let (_, dmabuf) = &bufs[(i % 2) as usize];
9638 let pkt = enc.encode(dmabuf, i, 25, i == 0).expect("dmabuf encode");
9639 pkt[VIDEO_HEADER_LEN..].to_vec()
9640 })
9641 .collect();
9642 println!(
9643 "4:4:4 {fullcolor} direct {direct}: mapped {}, {} bytes",
9644 mapped_kind(&enc),
9645 stream.len()
9646 );
9647 if let Ok(dir) = std::env::var("NVENC_TEST_DUMP_DIR") {
9648 std::fs::write(
9649 format!("{dir}/dmabuf-10bit-{fullcolor}-{direct}.h265"),
9650 &stream,
9651 )
9652 .unwrap();
9653 }
9654 streams.push(stream);
9655 }
9656 assert_eq!(streams[0], streams[1], "4:4:4 {fullcolor}: the arms differ");
9657 }
9658 }
9659
9660 #[test]
9665 #[ignore]
9666 fn gpu_packed_bgra_and_rgba_inputs_agree() {
9667 use crate::webcam::decode::{Codec, VideoDecoder};
9668 let (w, h) = (1280u32, 720u32);
9669 let s = settings(w as i32, h as i32, 60.0);
9670 let rect = block_rect(w, h, 3);
9671 let paint = |rgba: bool| -> Vec<u8> {
9672 let to_u8 = |c: f32| (c * 255.0).round() as u8;
9673 let mut f = vec![0u8; (w * h * 4) as usize];
9674 for y in 0..h as i32 {
9675 for x in 0..w as i32 {
9676 let inside =
9677 x >= rect.0 && x < rect.0 + rect.2 && y >= rect.1 && y < rect.1 + rect.3;
9678 let c = if inside { FG } else { BG };
9679 let px = &mut f[((y as u32 * w + x as u32) * 4) as usize..][..4];
9680 let (r, g, b) = (to_u8(c[0]), to_u8(c[1]), to_u8(c[2]));
9681 if rgba {
9682 px.copy_from_slice(&[r, g, b, 255]);
9683 } else {
9684 px.copy_from_slice(&[b, g, r, 255]);
9685 }
9686 }
9687 }
9688 f
9689 };
9690 let bgra = paint(false);
9691 let rgba = paint(true);
9692 let mut enc = host_session(&s).expect("NVENC init");
9693 let mut dec = VideoDecoder::new(Codec::H264).expect("H.264 decoder");
9694 let stride = (w * 4) as usize;
9695 let check_stage = |enc: &mut NvencEncoder, dec: &mut VideoDecoder, tag: &str| {
9699 for (i, (buf, is_rgba)) in
9700 [(&bgra, false), (&rgba, true), (&bgra, false), (&rgba, true)]
9701 .into_iter()
9702 .enumerate()
9703 {
9704 let pkt = enc
9705 .encode_cpu_packed(buf, stride, is_rgba, i as u64, 25, i == 0)
9706 .expect("packed encode");
9707 if enc.csc.is_none() {
9708 assert_eq!(
9709 enc.input_format,
9710 if is_rgba {
9711 NV_ENC_BUFFER_FORMAT::NV_ENC_BUFFER_FORMAT_ABGR
9712 } else {
9713 NV_ENC_BUFFER_FORMAT::NV_ENC_BUFFER_FORMAT_ARGB
9714 }
9715 );
9716 }
9717 let (block, bg) = decoded_means(dec, &pkt, rect);
9718 assert_painted(&format!("{tag} frame {i} rgba={is_rgba}"), block, bg, 6.0);
9719 }
9720 };
9721 check_stage(&mut enc, &mut dec, "convert");
9722 unsafe {
9723 let cu = enc.cuda.clone();
9724 (cu.cuCtxPushCurrent_v2)(enc.cuda_context);
9725 if let Some(csc) = enc.csc.take() {
9726 csc.release(&cu, &enc.nvenc_funcs, enc.encoder_session);
9727 }
9728 (cu.cuCtxPopCurrent_v2)(ptr::null_mut());
9729 }
9730 check_stage(&mut enc, &mut dec, "hardware");
9731 }
9732
9733 #[test]
9736 #[ignore]
9737 fn gpu_bench_tuning() {
9738 let (w, h) = (1920u32, 1080u32);
9739 let n: usize = std::env::var("NVENC_BENCH_FRAMES")
9740 .ok()
9741 .and_then(|v| v.parse().ok())
9742 .unwrap_or(240);
9743 let mut s = settings(w as i32, h as i32, 60.0);
9744 s.video_cbr_mode = true;
9745 s.video_bitrate_kbps = 8000;
9746 let frames: Vec<Vec<u8>> = (0..8u8)
9747 .map(|k| frame(w as usize, h as usize, 10 + 30 * k))
9748 .collect();
9749 let stride = (w * 4) as usize;
9750 let presets = [
9751 ("P1", NV_ENC_PRESET_P1_GUID),
9752 ("P2", NV_ENC_PRESET_P2_GUID),
9753 ("P3", NV_ENC_PRESET_P3_GUID),
9754 ("P4", NV_ENC_PRESET_P4_GUID),
9755 ("P5", NV_ENC_PRESET_P5_GUID),
9756 ("P6", NV_ENC_PRESET_P6_GUID),
9757 ("P7", NV_ENC_PRESET_P7_GUID),
9758 ];
9759 let passes = [
9760 ("single pass", NV_ENC_MULTI_PASS::NV_ENC_MULTI_PASS_DISABLED),
9761 (
9762 "two-pass quarter",
9763 NV_ENC_MULTI_PASS::NV_ENC_TWO_PASS_QUARTER_RESOLUTION,
9764 ),
9765 (
9766 "two-pass full",
9767 NV_ENC_MULTI_PASS::NV_ENC_TWO_PASS_FULL_RESOLUTION,
9768 ),
9769 ];
9770 let mut cases: Vec<(String, NvencTuning)> = Vec::new();
9771 for (name, preset) in presets {
9772 cases.push((
9773 format!("{name} two-pass quarter"),
9774 NvencTuning {
9775 preset,
9776 ..NvencTuning::default()
9777 },
9778 ));
9779 }
9780 for (name, multipass) in passes {
9781 cases.push((
9782 format!("P4 {name}"),
9783 NvencTuning {
9784 multipass,
9785 ..NvencTuning::default()
9786 },
9787 ));
9788 }
9789 cases.push((
9790 "P4 two-pass quarter spatial AQ".into(),
9791 NvencTuning {
9792 spatial_aq: true,
9793 ..NvencTuning::default()
9794 },
9795 ));
9796 cases.push((
9797 "P4 two-pass quarter temporal AQ".into(),
9798 NvencTuning {
9799 temporal_aq: true,
9800 ..NvencTuning::default()
9801 },
9802 ));
9803 for codec in [Codec::H264, Codec::H265] {
9804 s.codec = codec;
9805 for (label, tuning) in &cases {
9806 let mut enc = match NvencEncoder::new_tuned(&s, ptr::null(), *tuning) {
9807 Ok(enc) => enc,
9808 Err(e) => {
9809 println!("{codec:?} {label}: {e}");
9810 continue;
9811 }
9812 };
9813 enc.encode_cpu_packed(&frames[0], stride, false, 0, 25, true)
9814 .expect("warm-up");
9815 let mut bytes = 0usize;
9816 per_frame(&format!("{codec:?} {label}"), n, |i| {
9817 bytes += enc
9818 .encode_cpu_packed(&frames[i % 8], stride, false, 1 + i as u64, 25, false)
9819 .expect("encode")
9820 .len();
9821 });
9822 println!(" {} kbps", bytes * 8 * 60 / n / 1000);
9823 }
9824 }
9825 }
9826
9827 #[test]
9835 #[ignore]
9836 fn gpu_bench_tuning_on_content() {
9837 use crate::webcam::decode::VideoDecoder;
9838 let n: usize = std::env::var("NVENC_BENCH_FRAMES")
9839 .ok()
9840 .and_then(|v| v.parse().ok())
9841 .unwrap_or(90);
9842 let glyph = |c: u32, gx: usize, gy: usize| {
9843 let s = c.wrapping_mul(2654435761) >> 7;
9844 (s & 1 == 1 && gx == 1)
9845 || (s & 2 == 2 && gx == 5)
9846 || (s & 4 == 4 && gy == 1)
9847 || (s & 8 == 8 && gy == 6)
9848 || (s & 16 == 16 && gy == 11)
9849 || (s & 32 == 32 && gx * 2 == gy)
9850 };
9851 let only = |var: &str, item: String| {
9852 std::env::var(var).map_or(true, |v| v.split(',').any(|x| x == item))
9853 };
9854 use NV_ENC_SPLIT_ENCODE_MODE as Split;
9855 for (w, h, kbps) in [
9856 (1920usize, 1080usize, 8000),
9857 (2560, 1440, 14000),
9858 (3840, 2160, 30000),
9859 ] {
9860 if !only("NVENC_BENCH_HEIGHTS", h.to_string()) {
9861 continue;
9862 }
9863 let mut page = vec![255u8; w * 2 * h * 4];
9864 for y in 0..2 * h {
9865 let (cy, gy) = (y / 18, y % 18);
9866 if cy % 9 == 8 || gy >= 13 {
9867 continue;
9868 }
9869 for x in 0..w {
9870 let (cx, gx) = (x / 10, x % 10);
9871 let c = (cx as u32).wrapping_mul(31).wrapping_add(cy as u32 * 977);
9872 if gx < 7 && (cx + cy * 3) % 13 != 0 && glyph(c, gx, gy) {
9873 page[(y * w + x) * 4..(y * w + x) * 4 + 3].copy_from_slice(&[40, 30, 20]);
9874 }
9875 }
9876 }
9877 let (tw, th) = (w * 2, h * 2);
9878 let mut tex = vec![0u8; tw * th * 4];
9879 for y in 0..th {
9880 for x in 0..tw {
9881 let (fx, fy) = (x as f32, y as f32);
9882 let v = 128.0
9883 + 50.0 * (fx / 97.0).sin() * (fy / 61.0).cos()
9884 + 30.0 * (fx / 13.0 + fy / 29.0).sin()
9885 + ((x as u32).wrapping_mul(2654435761) ^ (y as u32).wrapping_mul(40503))
9886 as f32
9887 / u32::MAX as f32
9888 * 24.0;
9889 let i = (y * tw + x) * 4;
9890 tex[i] = v as u8;
9891 tex[i + 1] = (v * 0.8) as u8;
9892 tex[i + 2] = (255.0 - v) as u8;
9893 }
9894 }
9895 let mut bufs = [vec![0u8; w * h * 4], vec![0u8; w * h * 4]];
9896 let draw = |scene: usize, t: usize, out: &mut Vec<u8>| {
9897 for y in 0..h {
9898 let row = &mut out[y * w * 4..(y + 1) * w * 4];
9899 if scene == 0 {
9900 let src = (y + t * 4) % (2 * h);
9901 row.copy_from_slice(&page[src * w * 4..(src + 1) * w * 4]);
9902 } else {
9903 let (ox, oy) = ((t * 6) % w, (y + t * 3) % h);
9904 row.copy_from_slice(&tex[(oy * tw + ox) * 4..(oy * tw + ox + w) * 4]);
9905 }
9906 }
9907 };
9908 for codec in [Codec::H264, Codec::H265, Codec::Av1] {
9909 if !only("NVENC_BENCH_CODECS", format!("{codec:?}")) {
9910 continue;
9911 }
9912 let mut rows: Vec<(String, NvencTuning)> = vec![
9913 ("production".into(), NvencTuning::default()),
9914 (
9915 "P1".into(),
9916 NvencTuning {
9917 preset: NV_ENC_PRESET_P1_GUID,
9918 ..NvencTuning::default()
9919 },
9920 ),
9921 (
9922 "P2".into(),
9923 NvencTuning {
9924 preset: NV_ENC_PRESET_P2_GUID,
9925 ..NvencTuning::default()
9926 },
9927 ),
9928 (
9929 "P3 L0=1".into(),
9930 NvencTuning {
9931 ref_l0: Some(NV_ENC_NUM_REF_FRAMES::NV_ENC_NUM_REF_FRAMES_1),
9932 ..NvencTuning::default()
9933 },
9934 ),
9935 ];
9936 if codec != Codec::H264 {
9937 for (name, split) in [
9938 ("split off", Split::NV_ENC_SPLIT_DISABLE_MODE),
9939 ("split driver's", Split::NV_ENC_SPLIT_AUTO_MODE),
9940 ("split forced", Split::NV_ENC_SPLIT_AUTO_FORCED_MODE),
9941 ("split two", Split::NV_ENC_SPLIT_TWO_FORCED_MODE),
9942 ("split three", Split::NV_ENC_SPLIT_THREE_FORCED_MODE),
9943 ] {
9944 rows.push((
9945 format!("P3 {name}"),
9946 NvencTuning {
9947 split: Some(split),
9948 ..NvencTuning::default()
9949 },
9950 ));
9951 }
9952 }
9953 for (scene, scene_name) in [(0usize, "text"), (1, "pan")] {
9954 for (label, tuning) in &rows {
9955 let mut s = settings(w as i32, h as i32, 60.0);
9956 s.codec = codec;
9957 s.video_cbr_mode = true;
9958 s.video_bitrate_kbps = kbps;
9959 let mut enc = match NvencEncoder::new_tuned(&s, ptr::null(), *tuning) {
9960 Ok(enc) => enc,
9961 Err(e) => {
9962 println!("{codec:?} {w}x{h} {label}: {e}");
9963 continue;
9964 }
9965 };
9966 let engines = unsafe {
9967 query_cap(
9968 &enc.nvenc_funcs,
9969 enc.encoder_session,
9970 codec_guid(codec).unwrap(),
9971 NV_ENC_CAPS::NV_ENC_CAPS_NUM_ENCODER_ENGINES,
9972 )
9973 };
9974 let mut dec = VideoDecoder::new(codec).unwrap();
9975 let (mut times, mut bytes, mut psnr) =
9976 (Vec::with_capacity(n), 0usize, 0f64);
9977 for t in 0..n + 5 {
9978 let k = t % 2;
9979 draw(scene, t, &mut bufs[k]);
9980 let t0 = std::time::Instant::now();
9981 let out = enc
9982 .encode_cpu_packed(&bufs[k], w * 4, false, t as u64, 25, t == 0)
9983 .expect("encode");
9984 let ms = t0.elapsed().as_secs_f64() * 1e3;
9985 let p = luma_psnr(&mut dec, &out, &bufs[k], w, h);
9986 if t >= 5 {
9987 times.push(ms);
9988 bytes += out.len();
9989 psnr += p;
9990 }
9991 }
9992 times.sort_by(|a, b| a.total_cmp(b));
9993 println!(
9994 "TUNING {codec:?} {w}x{h} {scene_name} {label} (engines {engines:?}): encode {:.2} ms p50 {:.2} p99, {:.2} Mbit/s against {:.1}, PSNR {:.2} dB",
9995 times[n / 2],
9996 times[(n * 99) / 100],
9997 bytes as f64 * 8.0 * 60.0 / n as f64 / 1e6,
9998 f64::from(kbps) / 1000.0,
9999 psnr / n as f64
10000 );
10001 }
10002 }
10003 }
10004 }
10005 }
10006
10007 #[test]
10011 #[ignore]
10012 fn gpu_bench_dmabuf_paths() {
10013 let (w, h) = (1920u32, 1080u32);
10014 let n: usize = std::env::var("NVENC_BENCH_FRAMES")
10015 .ok()
10016 .and_then(|v| v.parse().ok())
10017 .unwrap_or(300);
10018 let s = settings(w as i32, h as i32, 60.0);
10019 let Some((gbm, mut renderer)) = gpu_render() else {
10020 return;
10021 };
10022 let egl_display = renderer.egl_context().display().get_display_handle().handle;
10023 let bufs: Vec<_> = (1..=2u32)
10024 .map(|seed| painted_dmabuf(&gbm, &mut renderer, w, h, seed).1)
10025 .collect();
10026 let mut enc = NvencEncoder::new(&s, egl_display).expect("NVENC init");
10027 for pass in 0..2 {
10028 enc.direct_dmabuf = pass == 0;
10029 enc.reconfigure_resolution(&s)
10030 .expect("reconfigure drains the import cache");
10031 enc.encode(&bufs[0], 0, 25, true).expect("warm-up");
10032 enc.encode(&bufs[1], 1, 25, false).expect("warm-up");
10033 let label = if all_direct(&enc) {
10034 format!("dmabuf registered in place ({})", mapped_kind(&enc))
10035 } else if pass == 0 {
10036 format!(
10037 "dmabuf copy arm (direct registration unavailable, mapped {})",
10038 mapped_kind(&enc)
10039 )
10040 } else {
10041 format!("dmabuf per-frame copy (mapped {})", mapped_kind(&enc))
10042 };
10043 per_frame(&label, n, |i| {
10044 enc.encode(&bufs[i % 2], 2 + i as u64, 25, false)
10045 .expect("encode");
10046 });
10047 }
10048 }
10049
10050 #[test]
10054 #[ignore]
10055 fn gpu_bench_readback_upload() {
10056 let (w, h) = (1920u32, 1080u32);
10057 let n: usize = std::env::var("NVENC_BENCH_FRAMES")
10058 .ok()
10059 .and_then(|v| v.parse().ok())
10060 .unwrap_or(300);
10061 let s = settings(w as i32, h as i32, 60.0);
10062 let frames: Vec<Vec<u8>> = (0..4u8)
10063 .map(|k| frame(w as usize, h as usize, 10 + 40 * k))
10064 .collect();
10065 let stride = (w * 4) as usize;
10066 let mut enc = host_session(&s).expect("NVENC init");
10067
10068 enc.reconfigure_resolution(&s).expect("reconfigure");
10069 enc.encode_cpu_packed(&frames[0], stride, false, 0, 25, true)
10070 .expect("warm-up");
10071 per_frame(
10072 "encode_cpu_packed BGRA (pinned, async upload, hardware CSC)",
10073 n,
10074 |i| {
10075 enc.encode_cpu_packed(&frames[i % 4], stride, false, 1 + i as u64, 25, false)
10076 .expect("packed");
10077 },
10078 );
10079
10080 enc.reconfigure_resolution(&s).expect("reconfigure");
10081 enc.encode_cpu_packed(&frames[0], stride, true, 0, 25, true)
10082 .expect("warm-up");
10083 per_frame(
10084 "encode_cpu_packed RGBA (pinned, async upload, hardware CSC)",
10085 n,
10086 |i| {
10087 enc.encode_cpu_packed(&frames[i % 4], stride, true, 1 + i as u64, 25, false)
10088 .expect("packed");
10089 },
10090 );
10091
10092 enc.reconfigure_resolution(&s).expect("reconfigure");
10093 enc.encode_cpu_packed(&frames[0], stride, false, 0, 25, true)
10094 .expect("warm-up");
10095 per_frame(
10096 "packed BGRA with a synchronous cuMemcpy2D upload",
10097 n,
10098 |i| unsafe {
10099 let _ = (enc.cuda.cuCtxPushCurrent_v2)(enc.cuda_context);
10100 let src = &frames[i % 4];
10101 enc.pin_host_source(src.as_ptr() as usize, src.len());
10102 let copy = CUDA_MEMCPY2D {
10103 srcMemoryType: CUmemorytype::CU_MEMORYTYPE_HOST,
10104 srcHost: src.as_ptr() as *const c_void,
10105 srcPitch: stride,
10106 dstMemoryType: CUmemorytype::CU_MEMORYTYPE_DEVICE,
10107 dstDevice: enc.input_device_ptr,
10108 dstPitch: enc.input_pitch,
10109 WidthInBytes: stride,
10110 Height: h as usize,
10111 ..Default::default()
10112 };
10113 assert_eq!((enc.cuda.cuMemcpy2D_v2)(©), CUresult::CUDA_SUCCESS);
10114 enc.submit_frame(
10115 enc.mapped_input_buffer,
10116 enc.input_format,
10117 1 + i as u64,
10118 false,
10119 )
10120 .expect("submit");
10121 (enc.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
10122 },
10123 );
10124 }
10125
10126 #[derive(Clone, Copy, Debug)]
10129 enum LockWait {
10130 Block,
10131 Spin,
10132 Sleep(std::time::Duration),
10133 }
10134
10135 #[test]
10141 #[ignore]
10142 fn gpu_bench_lock_strategies() {
10143 let s = settings(1920, 1080, 60.0);
10144 let mut enc = host_session(&s).expect("NVENC init");
10145 let frames = [frame(1920, 1080, 10), frame(1920, 1080, 90)];
10146 let n = 240usize;
10147 let strategies = [
10148 LockWait::Spin,
10149 LockWait::Block,
10150 LockWait::Sleep(std::time::Duration::from_micros(50)),
10151 LockWait::Sleep(std::time::Duration::from_micros(200)),
10152 LockWait::Sleep(std::time::Duration::from_micros(1000)),
10153 ];
10154 for pass in 0..2 {
10155 for &strategy in &strategies {
10156 let mut waits: Vec<f64> = Vec::with_capacity(n);
10157 let mut busy_total = 0u64;
10158 let c0 = thread_cpu();
10159 for i in 0..n {
10160 let (wait, busy) =
10161 unsafe { lock_round_trip(&mut enc, &frames[i % 2], i as u64, strategy) };
10162 waits.push(wait);
10163 busy_total += busy;
10164 }
10165 let cpu_us = (thread_cpu() - c0).as_secs_f64() * 1e6 / n as f64;
10166 waits.sort_by(|a, b| a.partial_cmp(b).unwrap());
10167 let mean = waits.iter().sum::<f64>() / n as f64;
10168 let pct = |q: f64| waits[((n as f64 - 1.0) * q) as usize];
10169 let empty = EMPTY_SUCCESSES.swap(0, std::sync::atomic::Ordering::Relaxed);
10170 if pass == 1 {
10171 println!(
10172 "{strategy:?}: submit->locked mean {mean:.0} us p50 {:.0} p95 {:.0} p99 {:.0} max {:.0}; {cpu_us:.0} us cpu/frame; {:.1} not-ready answers/frame; {:.2} empty successes/frame",
10173 pct(0.5),
10174 pct(0.95),
10175 pct(0.99),
10176 waits[n - 1],
10177 busy_total as f64 / n as f64,
10178 empty as f64 / n as f64
10179 );
10180 }
10181 }
10182 }
10183 }
10184
10185 static EMPTY_SUCCESSES: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10187
10188 unsafe fn lock_round_trip(
10191 enc: &mut NvencEncoder,
10192 pixels: &[u8],
10193 frame_number: u64,
10194 strategy: LockWait,
10195 ) -> (f64, u64) {
10196 let _ = (enc.cuda.cuCtxPushCurrent_v2)(enc.cuda_context);
10197 let copy = CUDA_MEMCPY2D {
10198 srcMemoryType: CUmemorytype::CU_MEMORYTYPE_HOST,
10199 srcHost: pixels.as_ptr() as *const c_void,
10200 srcPitch: (enc.width * 4) as usize,
10201 dstMemoryType: CUmemorytype::CU_MEMORYTYPE_DEVICE,
10202 dstDevice: enc.input_device_ptr,
10203 dstPitch: enc.input_pitch,
10204 WidthInBytes: (enc.width * 4) as usize,
10205 Height: enc.height as usize,
10206 ..Default::default()
10207 };
10208 assert_eq!((enc.cuda.cuMemcpy2D_v2)(©), CUresult::CUDA_SUCCESS);
10209 let output_bitstream = enc.bitstream_buffers[enc.current_buffer_idx];
10210 enc.current_buffer_idx = (enc.current_buffer_idx + 1) % enc.bitstream_buffers.len();
10211 let mut pic_params = NV_ENC_PIC_PARAMS {
10212 version: sv(NvStruct::PicParams),
10213 inputWidth: enc.width,
10214 inputHeight: enc.height,
10215 inputBuffer: enc.mapped_input_buffer,
10216 outputBitstream: output_bitstream,
10217 bufferFmt: enc.input_format,
10218 pictureStruct: NV_ENC_PIC_STRUCT::NV_ENC_PIC_STRUCT_FRAME,
10219 encodePicFlags: if frame_number == 0 {
10220 NV_ENC_PIC_FLAGS::NV_ENC_PIC_FLAG_FORCEIDR as u32
10221 } else {
10222 0
10223 },
10224 ..Default::default()
10225 };
10226 let t0 = std::time::Instant::now();
10227 let st =
10228 (enc.nvenc_funcs.nvEncEncodePicture.unwrap())(enc.encoder_session, &mut pic_params);
10229 assert_eq!(st, NVENCSTATUS::NV_ENC_SUCCESS);
10230 let mut negotiated = Negotiated::new(NV_ENC_LOCK_BITSTREAM {
10231 version: sv(NvStruct::LockBitstream),
10232 outputBitstream: output_bitstream,
10233 ..Default::default()
10234 });
10235 let lock_params = &mut negotiated.value;
10236 lock_params.set_doNotWait(
10237 matches!(strategy, LockWait::Block)
10238 .then_some(0)
10239 .unwrap_or(1),
10240 );
10241 let lock_fn = enc.nvenc_funcs.nvEncLockBitstream.unwrap();
10242 let mut busy = 0u64;
10243 let mut empty_successes = 0u64;
10244 loop {
10245 match lock_fn(enc.encoder_session, lock_params) {
10246 NVENCSTATUS::NV_ENC_SUCCESS if lock_params.bitstreamSizeInBytes > 0 => break,
10247 NVENCSTATUS::NV_ENC_SUCCESS => {
10248 empty_successes += 1;
10249 (enc.nvenc_funcs.nvEncUnlockBitstream.unwrap())(
10250 enc.encoder_session,
10251 output_bitstream,
10252 );
10253 if let LockWait::Sleep(d) = strategy {
10254 std::thread::sleep(d);
10255 }
10256 if matches!(strategy, LockWait::Block) {
10257 panic!("blocking lock returned an empty bitstream");
10258 }
10259 }
10260 NVENCSTATUS::NV_ENC_ERR_LOCK_BUSY => {
10261 busy += 1;
10262 if let LockWait::Sleep(d) = strategy {
10263 std::thread::sleep(d);
10264 }
10265 }
10266 other => panic!("lock failed: {other:?}"),
10267 }
10268 }
10269 let wait = t0.elapsed().as_secs_f64() * 1e6;
10270 if empty_successes > 0 {
10271 EMPTY_SUCCESSES.fetch_add(empty_successes, std::sync::atomic::Ordering::Relaxed);
10272 }
10273 (enc.nvenc_funcs.nvEncUnlockBitstream.unwrap())(enc.encoder_session, output_bitstream);
10274 (enc.cuda.cuCtxPopCurrent_v2)(ptr::null_mut());
10275 (wait, busy)
10276 }
10277}
10278
10279#[cfg(test)]
10280mod version_tests {
10281 use super::*;
10282
10283 const ALL: [(NvStruct, u32); 13] = [
10286 (NvStruct::FunctionList, NV_ENCODE_API_FUNCTION_LIST_VER),
10287 (
10288 NvStruct::OpenSessionExParams,
10289 NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER,
10290 ),
10291 (NvStruct::Config, NV_ENC_CONFIG_VER),
10292 (NvStruct::RcParams, NV_ENC_RC_PARAMS_VER),
10293 (NvStruct::PresetConfig, NV_ENC_PRESET_CONFIG_VER),
10294 (NvStruct::InitializeParams, NV_ENC_INITIALIZE_PARAMS_VER),
10295 (NvStruct::ReconfigureParams, NV_ENC_RECONFIGURE_PARAMS_VER),
10296 (NvStruct::RegisterResource, NV_ENC_REGISTER_RESOURCE_VER),
10297 (NvStruct::MapInputResource, NV_ENC_MAP_INPUT_RESOURCE_VER),
10298 (
10299 NvStruct::CreateBitstreamBuffer,
10300 NV_ENC_CREATE_BITSTREAM_BUFFER_VER,
10301 ),
10302 (NvStruct::PicParams, NV_ENC_PIC_PARAMS_VER),
10303 (NvStruct::LockBitstream, NV_ENC_LOCK_BITSTREAM_VER),
10304 (NvStruct::CapsParam, NV_ENC_CAPS_PARAM_VER),
10305 ];
10306
10307 #[test]
10312 fn table_is_identity_for_pinned_version() {
10313 let maj = NVENCAPI_VERSION & 0xFF;
10314 let min = (NVENCAPI_VERSION >> 24) & 0xFF;
10315 for (s, base) in ALL {
10316 assert_eq!(nvenc_struct_ver(s, maj, min), base, "{:?}", s);
10317 }
10318 assert_eq!(maj | (min << 24), NVENCAPI_VERSION);
10319 }
10320
10321 #[test]
10324 fn reconfigure_flags_sit_where_each_version_reads_them() {
10325 for (maj, min, at) in [
10326 (10, 0, 1816),
10327 (11, 0, 1816),
10328 (11, 1, 1816),
10329 (12, 0, 1816),
10330 (12, 1, 1816),
10331 (12, 2, 1808),
10332 (13, 0, 1808),
10333 ] {
10334 let params =
10335 reconfigure_params(&NV_ENC_INITIALIZE_PARAMS::default(), (maj, min), true, true);
10336 let bytes = unsafe {
10337 std::slice::from_raw_parts(
10338 (¶ms as *const Negotiated<_>).cast::<u8>(),
10339 std::mem::size_of_val(¶ms),
10340 )
10341 };
10342 let word = |at: usize| u32::from_ne_bytes(bytes[at..at + 4].try_into().unwrap());
10343 assert_eq!(
10344 (word(1808), word(1816)),
10345 if at == 1816 { (0, 3) } else { (3, 0) },
10346 "{maj}.{min}"
10347 );
10348 }
10349 }
10350
10351 #[test]
10354 fn qp_map_sits_where_each_version_reads_it() {
10355 let mut map = vec![0i8; 40];
10356 for (maj, min, at) in [
10357 (10, 0, 1696),
10358 (11, 0, 1696),
10359 (11, 1, 1696),
10360 (12, 0, 1712),
10361 (12, 1, 1712),
10362 (12, 2, 1712),
10363 (13, 0, 1712),
10364 ] {
10365 let mut pic = NV_ENC_PIC_PARAMS::default();
10366 point_at_qp_map(&mut pic, &mut map, (maj, min));
10367 let bytes = unsafe {
10368 std::slice::from_raw_parts(
10369 (&pic as *const NV_ENC_PIC_PARAMS).cast::<u8>(),
10370 std::mem::size_of_val(&pic),
10371 )
10372 };
10373 let word = |at: usize| u64::from_ne_bytes(bytes[at..at + 8].try_into().unwrap());
10374 let other = if at == 1696 { 1712 } else { 1696 };
10375 assert_eq!(word(at), map.as_ptr() as u64, "{maj}.{min}");
10376 assert_eq!(word(at + 8) as u32, 40, "{maj}.{min}");
10377 assert_eq!((word(other), word(other + 8)), (0, 0), "{maj}.{min}");
10378 }
10379 }
10380
10381 #[test]
10387 fn bit_depth_sits_where_each_version_reads_it() {
10388 let word = |c: &NV_ENC_CONFIG, at: usize| {
10389 let bytes = unsafe {
10390 std::slice::from_raw_parts((c as *const NV_ENC_CONFIG).cast::<u8>(), 3584)
10391 };
10392 let at = 168 + at;
10393 u32::from_ne_bytes(bytes[at..at + 4].try_into().unwrap())
10394 };
10395 for (codec, depths, bits) in [
10396 (Codec::H265, (200, 204), 2 << 11),
10397 (Codec::Av1, (112, 116), 2 << 12 | 2 << 15),
10398 ] {
10399 for (maj, min) in [(10, 0), (11, 1), (12, 0), (12, 1), (12, 2), (13, 0)] {
10400 if codec == Codec::Av1 && maj < 12 {
10401 continue;
10402 }
10403 let mut c = NV_ENC_CONFIG::default();
10404 set_bit_depth(&mut c, codec, 10, (maj, min));
10405 let named = (maj, min) >= (12, 2);
10406 assert_eq!(
10407 (word(&c, depths.0), word(&c, depths.1)),
10408 if named { (10, 10) } else { (0, 0) },
10409 "{codec:?} {maj}.{min}"
10410 );
10411 assert_eq!(
10412 word(&c, 16),
10413 if named { 0 } else { bits },
10414 "{codec:?} {maj}.{min}"
10415 );
10416 }
10417 }
10418 }
10419
10420 #[test]
10424 fn negotiated_structs_hold_the_longest_layout() {
10425 assert_eq!(
10426 std::mem::size_of::<Negotiated<NV_ENC_INITIALIZE_PARAMS>>(),
10427 1808
10428 );
10429 assert_eq!(
10430 std::mem::size_of::<Negotiated<NV_ENC_RECONFIGURE_PARAMS>>(),
10431 1824
10432 );
10433 assert_eq!(
10434 std::mem::size_of::<Negotiated<NV_ENC_LOCK_BITSTREAM>>(),
10435 1552
10436 );
10437 }
10438
10439 #[test]
10447 fn table_matches_historical_headers() {
10448 #[rustfmt::skip]
10449 let expected: [(u32, u32, [u32; 12]); 7] = [
10450 (10, 0, [0x7002000A, 0x7001000A, 0xF007000A, 0x7001000A, 0xF004000A, 0xF005000A, 0xF001000A,
10451 0x7003000A, 0x7004000A, 0x7001000A, 0xF004000A, 0x7001000A]),
10452 (11, 0, [0x7002000B, 0x7001000B, 0xF007000B, 0x7001000B, 0xF004000B, 0xF005000B, 0xF001000B,
10453 0x7003000B, 0x7004000B, 0x7001000B, 0xF004000B, 0x7001000B]),
10454 (11, 1, [0x7102000B, 0x7101000B, 0xF107000B, 0x7101000B, 0xF104000B, 0xF105000B, 0xF101000B,
10455 0x7103000B, 0x7104000B, 0x7101000B, 0xF104000B, 0x7101000B]),
10456 (12, 0, [0x7002000C, 0x7001000C, 0xF008000C, 0x7001000C, 0xF004000C, 0xF005000C, 0xF001000C,
10457 0x7004000C, 0x7004000C, 0x7001000C, 0xF006000C, 0x7002000C]),
10458 (12, 1, [0x7102000C, 0x7101000C, 0xF108000C, 0x7101000C, 0xF104000C, 0xF106000C, 0xF101000C,
10459 0x7104000C, 0x7104000C, 0x7101000C, 0xF106000C, 0xF101000C]),
10460 (12, 2, [0x7202000C, 0x7201000C, 0xF209000C, 0x7201000C, 0xF205000C, 0xF207000C, 0xF202000C,
10461 0x7205000C, 0x7204000C, 0x7201000C, 0xF207000C, 0xF202000C]),
10462 (13, 0, [0x7002000D, 0x7001000D, 0xF009000D, 0x7001000D, 0xF005000D, 0xF007000D, 0xF002000D,
10463 0x7005000D, 0x7004000D, 0x7001000D, 0xF007000D, 0xF002000D]),
10464 ];
10465 for (maj, min, words) in expected {
10466 for ((s, _), want) in ALL.iter().zip(words) {
10467 assert_eq!(
10468 nvenc_struct_ver(*s, maj, min),
10469 want,
10470 "{:?} at {}.{}",
10471 s,
10472 maj,
10473 min
10474 );
10475 }
10476 }
10477 }
10478}
10479
10480#[cfg(test)]
10481mod decision_tests {
10482 use super::*;
10483
10484 #[test]
10488 fn caps_chroma_downgrade() {
10489 let d = decide_caps(true, 1920, 1080, Some(0), None, None);
10490 assert!(!d.fullcolor && d.downgraded_color && d.too_large.is_none());
10491
10492 let d = decide_caps(true, 1920, 1080, Some(1), None, None);
10493 assert!(d.fullcolor && !d.downgraded_color);
10494
10495 let d = decide_caps(true, 1920, 1080, None, None, None);
10496 assert!(d.fullcolor && !d.downgraded_color);
10497
10498 let d = decide_caps(false, 1920, 1080, Some(0), None, None);
10500 assert!(!d.fullcolor && !d.downgraded_color);
10501 }
10502
10503 #[test]
10507 fn caps_dimension_gate() {
10508 assert_eq!(
10509 decide_caps(false, 5120, 2160, None, Some(4096), Some(4096)).too_large,
10510 Some((4096, 4096))
10511 );
10512 assert_eq!(
10513 decide_caps(false, 3840, 4320, None, Some(4096), Some(4096)).too_large,
10514 Some((4096, 4096))
10515 );
10516 assert!(
10517 decide_caps(false, 3840, 2160, None, Some(4096), Some(4096))
10518 .too_large
10519 .is_none()
10520 );
10521 assert!(
10522 decide_caps(false, 7680, 4320, None, None, None)
10523 .too_large
10524 .is_none()
10525 );
10526 assert!(
10528 decide_caps(false, 3840, 2160, None, Some(0), Some(0))
10529 .too_large
10530 .is_none()
10531 );
10532 }
10533
10534 #[test]
10537 fn headroom_is_floored_and_capped() {
10538 assert_eq!(nvenc_headroom(1920, 4096, Some(8192)), 4096);
10539 assert_eq!(nvenc_headroom(3840, 4096, Some(4096)), 4096);
10540 assert_eq!(nvenc_headroom(6000, 4096, Some(8192)), 6000);
10541 assert_eq!(nvenc_headroom(1920, 4096, None), 4096);
10542 assert_eq!(nvenc_headroom(1920, 4096, Some(2048)), 2048);
10543 }
10544
10545 #[test]
10550 fn nvenc_level_follows_geometry_except_av1() {
10551 assert_eq!(nvenc_level(Codec::H264, 1920, 1080, 60, 0, true), 42);
10554 assert_eq!(nvenc_level(Codec::H265, 1920, 1080, 60, 0, true), 123);
10555 assert_eq!(nvenc_level(Codec::H264, 3840, 2160, 60, 0, true), 52);
10557 assert_eq!(nvenc_level(Codec::H265, 3840, 2160, 60, 0, true), 153);
10558 assert_eq!(h265_level(1280, 720, 144, 0, true), 123);
10560 assert_eq!(nvenc_level(Codec::H265, 1280, 720, 144, 0, true), 150);
10561 for (w, h) in [(1280u32, 720u32), (1920, 1080), (3840, 2160)] {
10563 let macroblocks = (u64::from(w) / 16) * (u64::from(h) / 16);
10564 assert!(
10565 h264_max_macroblocks(nvenc_level(Codec::H264, w, h, 60, 0, true)) >= macroblocks,
10566 "H.264 level at {w}x{h} cannot hold the picture"
10567 );
10568 assert!(
10569 h265_max_picture(nvenc_level(Codec::H265, w, h, 60, 0, true))
10570 >= u64::from(w) * u64::from(h),
10571 "HEVC level at {w}x{h} cannot hold the picture"
10572 );
10573 }
10574 let pixels = u64::from(HEADROOM_WIDTH * HEADROOM_HEIGHT);
10577 for (w, h) in [(1280, 720), (1920, 1080), (3840, 2160)] {
10578 assert_eq!(
10579 nvenc_level(Codec::Av1, w, h, 60, 0, true),
10580 nvenc_level(Codec::Av1, HEADROOM_WIDTH, HEADROOM_HEIGHT, 60, 0, true),
10581 "AV1 level moved with the capture at {w}x{h}"
10582 );
10583 assert!(
10584 av1_max_picture(nvenc_level(Codec::Av1, w, h, 60, 0, true)) >= pixels,
10585 "AV1 level at {w}x{h} cannot hold the headroom"
10586 );
10587 }
10588 for codec in [Codec::H264, Codec::H265, Codec::Av1] {
10590 assert_eq!(
10591 nvenc_level(codec, 1920, 1080, 30, 0, true),
10592 nvenc_level(codec, 1920, 1080, 60, 0, true),
10593 "{codec:?} level moved between 30 and 60 fps"
10594 );
10595 }
10596 assert_eq!(
10599 nvenc_level(Codec::H264, 1920, 1080, 60, 100_000_000, true),
10600 50
10601 );
10602 assert_eq!(
10603 nvenc_level(Codec::H265, 1920, 1080, 60, 60_000_000, true),
10604 150
10605 );
10606 assert_eq!(
10607 nvenc_level(Codec::H265, 1920, 1080, 60, 60_000_000, false),
10608 156
10609 );
10610 for (bps, annex_a, weighted) in [
10613 (26_666_666, 13, 13),
10614 (26_666_667, 13, 14),
10615 (40_000_000, 13, 14),
10616 (40_000_001, 14, 17),
10617 (45_000_000, 14, 17),
10618 (60_000_001, 17, 17),
10619 (66_666_667, 17, 18),
10620 (100_000_001, 18, 18),
10621 (106_666_666, 18, 18),
10622 ] {
10623 for (weigh, level) in [(false, annex_a), (true, weighted)] {
10624 assert_eq!(
10625 nvenc_level(
10626 Codec::Av1,
10627 1920,
10628 1080,
10629 60,
10630 level_rate(Codec::Av1, bps, weigh),
10631 true
10632 ),
10633 level,
10634 "AV1 at {bps} bit/s, weighed {weigh}"
10635 );
10636 }
10637 }
10638 assert_eq!(level_rate(Codec::H265, 60_000_000, true), 60_000_000);
10639 }
10640
10641 #[test]
10645 fn split_mode_follows_the_codec_and_picture() {
10646 use NV_ENC_SPLIT_ENCODE_MODE::{
10647 NV_ENC_SPLIT_AUTO_FORCED_MODE as Forced, NV_ENC_SPLIT_AUTO_MODE as Driver,
10648 NV_ENC_SPLIT_THREE_FORCED_MODE as Three,
10649 };
10650 for (codec, w, h, engines, api, want) in [
10651 (Codec::Av1, 1280, 720, Some(2), (13, 0), Forced),
10652 (Codec::Av1, 3840, 2160, Some(3), (12, 1), Three),
10653 (Codec::Av1, 1920, 1080, Some(3), (13, 0), Three),
10654 (Codec::Av1, 1920, 1080, Some(4), (13, 0), Forced),
10655 (Codec::Av1, 1920, 1080, Some(3), (12, 0), Driver),
10656 (Codec::H265, 3840, 2160, Some(3), (13, 0), Forced),
10657 (Codec::H265, 1920, 1080, Some(3), (13, 0), Driver),
10658 (Codec::Av1, 1920, 1080, Some(1), (13, 0), Driver),
10659 (Codec::Av1, 1920, 1080, None, (13, 0), Driver),
10660 (Codec::H265, 2560, 1440, Some(2), (13, 0), Driver),
10661 (Codec::H265, 3840, 2160, Some(2), (13, 0), Forced),
10662 (Codec::H265, 2160, 3840, Some(2), (13, 0), Forced),
10663 (Codec::H265, 3840, 2160, Some(2), (12, 0), Driver),
10664 (Codec::H264, 3840, 2160, Some(2), (13, 0), Driver),
10665 ] {
10666 assert_eq!(
10667 split_mode(codec, w, h, engines, api),
10668 want,
10669 "{codec:?} {w}x{h} on {engines:?} engines, API {api:?}"
10670 );
10671 }
10672 }
10673
10674 #[test]
10677 fn cbr_targets_hold_to_the_top_level() {
10678 for (codec, kbps, bps) in [
10679 (Codec::Av1, 8_000, 8_000_000),
10680 (Codec::Av1, 106_666, 106_666_000),
10681 (Codec::Av1, 200_000, 106_666_666),
10682 (Codec::H265, 800_000, 800_000_000),
10683 (Codec::H265, 1_000_000, 800_000_000),
10684 (Codec::H264, 1_000_000, 1_000_000_000),
10685 (Codec::H264, 5_000_000, 1_000_000_000),
10686 ] {
10687 let s = CaptureSettings {
10688 codec,
10689 video_bitrate_kbps: kbps,
10690 ..Default::default()
10691 };
10692 assert_eq!(cbr_bps(&s), bps, "{codec:?} at {kbps} kbps");
10693 }
10694 assert_eq!(
10695 nvenc_level(
10696 Codec::Av1,
10697 1920,
10698 1080,
10699 240,
10700 u64::from(nvenc_rate_ceiling(Codec::Av1)),
10701 true
10702 ),
10703 18
10704 );
10705 }
10706
10707 fn av1_max_picture(level: u32) -> u64 {
10709 match level {
10710 8 | 9 => 2_359_296,
10711 12..=15 => 8_912_896,
10712 _ => 35_651_584,
10713 }
10714 }
10715
10716 fn h265_max_picture(level: u32) -> u64 {
10718 match level {
10719 123 => 2_228_224,
10720 150 | 153 | 156 => 8_912_896,
10721 _ => 35_651_584,
10722 }
10723 }
10724
10725 fn h264_max_macroblocks(level: u32) -> u64 {
10727 match level {
10728 41 => 8192,
10729 42 => 8704,
10730 50 => 22080,
10731 51 | 52 => 36864,
10732 _ => 139264,
10733 }
10734 }
10735
10736 fn egl_frame(frame_type: u32, w: u32, h: u32, pitch: u32, plane: usize) -> CUeglFrame {
10740 let mut f: CUeglFrame = unsafe { std::mem::zeroed() };
10741 f.frame_type = frame_type;
10742 f.plane_count = 1;
10743 f.width = w;
10744 f.height = h;
10745 f.pitch = pitch;
10746 f.num_channels = 4;
10747 f.cu_format = CU_AD_FORMAT_U8;
10748 f.frame.p_pitch = [plane as *mut c_void, ptr::null_mut(), ptr::null_mut()];
10749 f
10750 }
10751
10752 #[test]
10757 fn pitch_linear_frame_direct_registration_rules() {
10758 let pitch_ok = egl_frame(CU_EGL_FRAME_TYPE_PITCH, 1920, 1080, 7680, 0x1000);
10759 assert_eq!(
10760 direct_plane(&pitch_ok, 1920, 1080),
10761 Some(DirectPlane::Pitch(7680))
10762 );
10763 let padded = egl_frame(CU_EGL_FRAME_TYPE_PITCH, 1920, 1080, 8192, 0x1000);
10764 assert_eq!(
10765 direct_plane(&padded, 1920, 1080),
10766 Some(DirectPlane::Pitch(8192))
10767 );
10768 let larger = egl_frame(CU_EGL_FRAME_TYPE_PITCH, 2048, 1200, 8192, 0x1000);
10769 assert_eq!(
10770 direct_plane(&larger, 1920, 1080),
10771 Some(DirectPlane::Pitch(8192))
10772 );
10773
10774 let null_plane = egl_frame(CU_EGL_FRAME_TYPE_PITCH, 1920, 1080, 7680, 0);
10775 assert_eq!(direct_plane(&null_plane, 1920, 1080), None);
10776 let short_pitch = egl_frame(CU_EGL_FRAME_TYPE_PITCH, 1920, 1080, 7676, 0x1000);
10777 assert_eq!(direct_plane(&short_pitch, 1920, 1080), None);
10778 let unaligned = egl_frame(CU_EGL_FRAME_TYPE_PITCH, 1920, 1080, 7682, 0x1000);
10779 assert_eq!(direct_plane(&unaligned, 1920, 1080), None);
10780 let smaller = egl_frame(CU_EGL_FRAME_TYPE_PITCH, 1280, 720, 7680, 0x1000);
10781 assert_eq!(direct_plane(&smaller, 1920, 1080), None);
10782 let mut no_planes = pitch_ok;
10783 no_planes.plane_count = 0;
10784 assert_eq!(direct_plane(&no_planes, 1920, 1080), None);
10785 assert_eq!(direct_plane(&pitch_ok, 0, 1080), None);
10786 let mut unknown_kind = pitch_ok;
10787 unknown_kind.frame_type = 7;
10788 assert_eq!(direct_plane(&unknown_kind, 1920, 1080), None);
10789 }
10790
10791 #[test]
10795 fn cuda_array_frame_direct_registration_rules() {
10796 let array = egl_frame(CU_EGL_FRAME_TYPE_ARRAY, 1920, 1080, 0, 0x2000);
10797 assert_eq!(
10798 direct_plane(&array, 1920, 1080),
10799 Some(DirectPlane::Array(7680))
10800 );
10801 let wider = egl_frame(CU_EGL_FRAME_TYPE_ARRAY, 2048, 1080, 0, 0x2000);
10802 assert_eq!(
10803 direct_plane(&wider, 1920, 1080),
10804 Some(DirectPlane::Array(8192))
10805 );
10806
10807 let null_array = egl_frame(CU_EGL_FRAME_TYPE_ARRAY, 1920, 1080, 0, 0);
10808 assert_eq!(direct_plane(&null_array, 1920, 1080), None);
10809 let mut one_channel = array;
10810 one_channel.num_channels = 1;
10811 assert_eq!(direct_plane(&one_channel, 1920, 1080), None);
10812 let mut wide_elements = array;
10813 wide_elements.cu_format = 3;
10814 assert_eq!(direct_plane(&wide_elements, 1920, 1080), None);
10815 let smaller = egl_frame(CU_EGL_FRAME_TYPE_ARRAY, 1280, 720, 0, 0x2000);
10816 assert_eq!(direct_plane(&smaller, 1920, 1080), None);
10817 }
10818
10819 #[test]
10823 fn fourcc_selects_nvenc_byte_order() {
10824 for code in [Fourcc::Argb8888, Fourcc::Xrgb8888] {
10825 assert_eq!(
10826 fourcc_nvenc_format(code),
10827 Some(NV_ENC_BUFFER_FORMAT::NV_ENC_BUFFER_FORMAT_ARGB)
10828 );
10829 }
10830 for code in [Fourcc::Abgr8888, Fourcc::Xbgr8888] {
10831 assert_eq!(
10832 fourcc_nvenc_format(code),
10833 Some(NV_ENC_BUFFER_FORMAT::NV_ENC_BUFFER_FORMAT_ABGR)
10834 );
10835 }
10836 for code in [
10837 Fourcc::Rgb565,
10838 Fourcc::Nv12,
10839 Fourcc::Argb2101010,
10840 Fourcc::Bgra8888,
10841 Fourcc::Rgba8888,
10842 ] {
10843 assert_eq!(fourcc_nvenc_format(code), None, "{code:?}");
10844 }
10845 }
10846
10847 #[test]
10849 fn egl_import_names_alpha_twins() {
10850 assert_eq!(egl_import_fourcc(Fourcc::Xrgb8888), Fourcc::Argb8888);
10851 assert_eq!(egl_import_fourcc(Fourcc::Xbgr8888), Fourcc::Abgr8888);
10852 for code in [Fourcc::Argb8888, Fourcc::Abgr8888, Fourcc::Nv12] {
10853 assert_eq!(egl_import_fourcc(code), code);
10854 }
10855 }
10856}