nvfbc/
cuda.rs

1use std::ffi::c_void;
2use std::mem::MaybeUninit;
3use std::ptr::null_mut;
4
5use nvfbc_sys::{
6	NVFBC_TOCUDA_FLAGS_NVFBC_TOCUDA_GRAB_FLAGS_NOWAIT,
7	NVFBC_TOCUDA_FLAGS_NVFBC_TOCUDA_GRAB_FLAGS_NOFLAGS,
8	NVFBC_TOCUDA_FLAGS_NVFBC_TOCUDA_GRAB_FLAGS_NOWAIT_IF_NEW_FRAME_READY
9};
10
11use crate::{
12	BufferFormat,
13	CaptureType,
14	Error,
15	Status,
16};
17
18use crate::common::{
19	Handle,
20	check_ret,
21	create_capture_session,
22	create_handle,
23	destroy_capture_session,
24	destroy_handle,
25	status,
26};
27
28pub enum CaptureMethod {
29	/// Capturing does not wait for a new frame nor a mouse move.
30	///
31	/// It is therefore possible to capture the same frame multiple times.
32	/// When this occurs, the current_frame parameter of the
33	/// CudaFrameInfo struct is not incremented.
34	NoWait = NVFBC_TOCUDA_FLAGS_NVFBC_TOCUDA_GRAB_FLAGS_NOWAIT as isize,
35
36	/// Similar to NoWait, except that the capture will not wait if there
37	/// is already a frame available that the client has never seen yet.
38	NoWaitIfNewFrame = NVFBC_TOCUDA_FLAGS_NVFBC_TOCUDA_GRAB_FLAGS_NOWAIT_IF_NEW_FRAME_READY as isize,
39
40	/// Capturing waits for a new frame or mouse move.
41	Blocking = NVFBC_TOCUDA_FLAGS_NVFBC_TOCUDA_GRAB_FLAGS_NOFLAGS as isize,
42}
43
44/// Contains information about a frame captured in a CUDA device.
45#[derive(Copy, Clone)]
46pub struct CudaFrameInfo {
47	/// Address of the CUDA buffer where the frame is grabbed.
48	///
49	/// Note that this an address in CUDA memory, not in system memory.
50	pub device_buffer: usize,
51	/// Size of the frame in bytes.
52	pub device_buffer_len: u32,
53	/// Width of the captured frame.
54	pub width: u32,
55	/// Height of the captured frame.
56	pub height: u32,
57	/// Incremental ID of the current frame.
58	///
59	/// This can be used to identify a frame.
60	pub current_frame: u32,
61}
62
63impl std::fmt::Debug for CudaFrameInfo {
64	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
65		f.debug_struct("CudaFrameInfo")
66			.field("device_buffer", &(&self.device_buffer as *const usize))
67			.field("device_buffer_len", &self.device_buffer_len)
68			.field("width", &self.width)
69			.field("height", &self.height)
70			.field("current_frame", &self.current_frame)
71			.finish()
72	}
73}
74
75/// Uses NVFBC to capture frames in the form of a CUDA device pointer.
76pub struct CudaCapturer {
77	/// A handle to the internal NVFBC instance used for FFI interaction.
78	handle: Handle,
79}
80
81impl CudaCapturer {
82	/// Create a new CUDA capture object.
83	///
84	/// CUDA must be initialized before creating this object.
85	pub fn new() -> Result<Self, Error> {
86		Ok(Self { handle: create_handle()? })
87	}
88
89	/// Retrieve the status of NVFBC.
90	pub fn status(&self) -> Result<Status, Error> {
91		status(self.handle)
92	}
93
94	/// Start a capture session with the desired buffer format.
95	pub fn start(&self, buffer_format: BufferFormat, fps: u32) -> Result<(), Error> {
96		create_capture_session(
97			self.handle,
98			CaptureType::SharedCuda,
99			std::time::Duration::from_millis(1000 / fps as u64),
100		)?;
101
102		let mut params: nvfbc_sys::NVFBC_TOCUDA_SETUP_PARAMS = unsafe { MaybeUninit::zeroed().assume_init() };
103		params.dwVersion = nvfbc_sys::NVFBC_TOCUDA_SETUP_PARAMS_VER;
104		params.eBufferFormat = buffer_format as u32;
105		check_ret(self.handle, unsafe { nvfbc_sys::NvFBCToCudaSetUp(self.handle, &mut params) })
106	}
107
108	/// Stop a capture session.
109	pub fn stop(&self) -> Result<(), Error> {
110		destroy_capture_session(self.handle)
111	}
112
113	/// Retrieve the next frame from the GPU.
114	pub fn next_frame(&mut self, capture_method: CaptureMethod) -> Result<CudaFrameInfo, Error> {
115		let mut device_buffer: *mut c_void =  null_mut();
116		let mut frame_info: nvfbc_sys::NVFBC_FRAME_GRAB_INFO = unsafe { MaybeUninit::zeroed().assume_init() };
117		let mut params: nvfbc_sys::NVFBC_TOCUDA_GRAB_FRAME_PARAMS = unsafe { MaybeUninit::zeroed().assume_init() };
118		params.dwVersion = nvfbc_sys::NVFBC_TOCUDA_GRAB_FRAME_PARAMS_VER;
119		params.dwFlags = capture_method as u32;
120		params.pFrameGrabInfo = &mut frame_info;
121		params.pCUDADeviceBuffer = &mut device_buffer as *mut _ as *mut c_void;
122		check_ret(self.handle, unsafe { nvfbc_sys::NvFBCToCudaGrabFrame(self.handle, &mut params) })?;
123
124		Ok(CudaFrameInfo {
125			device_buffer: device_buffer as usize,
126			device_buffer_len: frame_info.dwByteSize,
127			width: frame_info.dwWidth,
128			height: frame_info.dwHeight,
129			current_frame: frame_info.dwCurrentFrame,
130		})
131	}
132
133	/// Releases the FBC context from the calling thread.
134	///
135	/// If the FBC context is bound to a different thread, nvfbc_sys::_NVFBCSTATUS_NVFBC_ERR_CONTEXT is
136	/// returned.
137	///
138	/// If the FBC context is already released, this function has no effect.
139	pub fn release_context(&self) -> Result<(), Error> {
140		let mut params: nvfbc_sys::NVFBC_RELEASE_CONTEXT_PARAMS = unsafe { MaybeUninit::zeroed().assume_init() };
141		params.dwVersion = nvfbc_sys::NVFBC_RELEASE_CONTEXT_PARAMS_VER;
142		check_ret(
143			self.handle,
144			unsafe { nvfbc_sys::NvFBCReleaseContext(self.handle, &mut params) }
145		)
146	}
147
148	/// Binds the FBC context to the calling thread.
149	///
150	/// The NvFBC library internally relies on objects that must be bound to a
151	/// thread. Such objects are OpenGL contexts and CUDA contexts.
152	///
153	/// This function binds these objects to the calling thread.
154	///
155	/// The FBC context must be bound to the calling thread for most NvFBC entry
156	/// points, otherwise nvfbc_sys::_NVFBCSTATUS_NVFBC_ERR_CONTEXT is returned.
157	///
158	/// If the FBC context is already bound to a different thread,
159	/// nvfbc_sys::_NVFBCSTATUS_NVFBC_ERR_CONTEXT is returned.
160	/// The other thread must release the context first by calling the release_context().
161	///
162	/// If the FBC context is already bound to the current thread, this function has
163	/// no effects.
164	pub fn bind_context(&self) -> Result<(), Error> {
165		let mut params: nvfbc_sys::NVFBC_BIND_CONTEXT_PARAMS = unsafe { MaybeUninit::zeroed().assume_init() };
166		params.dwVersion = nvfbc_sys::NVFBC_BIND_CONTEXT_PARAMS_VER;
167		check_ret(
168			self.handle,
169			unsafe { nvfbc_sys::NvFBCBindContext(self.handle, &mut params) }
170		)
171	}
172}
173
174impl Drop for CudaCapturer {
175	fn drop(&mut self) {
176		// TODO: Figure out why this crashes (nvfbc examples also fail here..)
177		destroy_handle(self.handle).ok();
178	}
179}