1pub(crate) mod completion;
4mod events;
5mod memory_ops;
6mod queued;
7
8pub(crate) use completion::{select_uncertain_completion_error, CudaSynchronizationOutcome};
9pub(crate) use events::elapsed_event_us_ceil;
10pub(crate) use events::CudaEvent;
11pub use queued::{
12 CudaExecutionStats, CudaKernelBatchOutput, CudaKernelContiguousBatchOutput, CudaKernelOutput,
13 CudaPooledKernelOutput, CudaQueuedExecution,
14};
15
16#[cfg(test)]
17use crate::context::{CudaKernelModule, CudaKernelName};
18use crate::{
19 context::CudaContext,
20 driver::{CuDevicePtr, CuFunction},
21 error::CudaError,
22 kernels::{self, copy_u8_launch_geometry},
23 memory::CudaDeviceBuffer,
24};
25use std::{ffi::c_void, ops::Range};
26
27#[derive(Clone, Copy, Debug, Eq, PartialEq)]
28pub(crate) enum CudaLaunchMode {
29 Sync,
30 Async,
31}
32
33pub(crate) unsafe trait CudaKernelParam {}
40
41unsafe impl CudaKernelParam for CuDevicePtr {}
43unsafe impl CudaKernelParam for u32 {}
45unsafe impl CudaKernelParam for i32 {}
47unsafe impl CudaKernelParam for f32 {}
49
50pub(crate) fn cuda_kernel_param<T>(value: &mut T) -> *mut c_void
51where
52 T: CudaKernelParam,
53{
54 std::ptr::from_mut(value).cast::<c_void>()
55}
56
57impl CudaContext {
58 #[doc(hidden)]
59 pub fn copy_with_kernel(&self, bytes: &[u8]) -> Result<CudaKernelOutput, CudaError> {
61 let staging = self.upload(bytes)?;
62 let output = self.copy_device_to_device_with_kernel(&staging)?;
63 let copy_dispatches = usize::from(!bytes.is_empty());
64 Ok(CudaKernelOutput {
65 buffer: output,
66 execution: CudaExecutionStats {
67 kernel_dispatches: copy_dispatches,
68 copy_kernel_dispatches: copy_dispatches,
69 decode_kernel_dispatches: 0,
70 hardware_decode: false,
71 },
72 })
73 }
74
75 #[cfg(all(test, feature = "cuda-oxide-copy-u8", j2k_cuda_oxide_copy_u8_built))]
76 pub(crate) fn copy_with_cuda_oxide_kernel(
77 &self,
78 bytes: &[u8],
79 ) -> Result<CudaKernelOutput, CudaError> {
80 let staging = self.upload(bytes)?;
81 let output = self.copy_device_to_device_with_cuda_oxide_kernel(&staging)?;
82 let copy_dispatches = usize::from(!bytes.is_empty());
83 Ok(CudaKernelOutput {
84 buffer: output,
85 execution: CudaExecutionStats {
86 kernel_dispatches: copy_dispatches,
87 copy_kernel_dispatches: copy_dispatches,
88 decode_kernel_dispatches: 0,
89 hardware_decode: false,
90 },
91 })
92 }
93
94 pub(crate) fn launch_kernel(
95 &self,
96 function: CuFunction,
97 geometry: kernels::CudaLaunchGeometry,
98 params: &mut [*mut c_void],
99 ) -> Result<(), CudaError> {
100 self.launch_kernel_async(function, geometry, params)?;
101 self.synchronize()
105 }
106
107 pub(crate) fn launch_kernel_async(
108 &self,
109 function: CuFunction,
110 geometry: kernels::CudaLaunchGeometry,
111 params: &mut [*mut c_void],
112 ) -> Result<(), CudaError> {
113 if !geometry.is_valid() {
114 return Err(CudaError::InvalidArgument {
115 message: format!(
116 "CUDA launch geometry exceeds static limits: grid {:?}, block {:?}",
117 geometry.grid(),
118 geometry.block()
119 ),
120 });
121 }
122 let (grid_x, grid_y, grid_z) = geometry.grid();
123 let (block_x, block_y, block_z) = geometry.block();
124 self.inner.with_current_resource_operation(|| {
125 let launch_status = unsafe {
130 (self.inner.driver.cu_launch_kernel)(
131 function,
132 grid_x,
133 grid_y,
134 grid_z,
135 block_x,
136 block_y,
137 block_z,
138 0,
139 std::ptr::null_mut(),
140 params.as_mut_ptr(),
141 std::ptr::null_mut(),
142 )
143 };
144 self.inner.driver.check("cuLaunchKernel", launch_status)
145 })?;
146 self.record_kernel_launch();
147 Ok(())
148 }
149
150 pub(crate) fn copy_device_to_device_with_kernel(
151 &self,
152 src: &CudaDeviceBuffer,
153 ) -> Result<CudaDeviceBuffer, CudaError> {
154 self.copy_device_range_to_device_with_kernel(src, 0..src.byte_len())
155 }
156
157 pub(crate) fn copy_device_range_to_device_with_kernel(
158 &self,
159 src: &CudaDeviceBuffer,
160 range: Range<usize>,
161 ) -> Result<CudaDeviceBuffer, CudaError> {
162 self.copy_device_range_to_device_with_copy_u8_loader(src, range, |context| {
163 context.inner.cuda_oxide_copy_u8_kernel_function()
164 })
165 }
166
167 #[cfg(all(test, feature = "cuda-oxide-copy-u8", j2k_cuda_oxide_copy_u8_built))]
168 pub(crate) fn copy_device_to_device_with_cuda_oxide_kernel(
169 &self,
170 src: &CudaDeviceBuffer,
171 ) -> Result<CudaDeviceBuffer, CudaError> {
172 self.copy_device_range_to_device_with_copy_u8_loader(src, 0..src.byte_len(), |context| {
173 context.inner.cuda_oxide_copy_u8_kernel_function()
174 })
175 }
176
177 fn copy_device_range_to_device_with_copy_u8_loader(
178 &self,
179 src: &CudaDeviceBuffer,
180 range: Range<usize>,
181 load_function: impl FnOnce(&Self) -> Result<CuFunction, CudaError>,
182 ) -> Result<CudaDeviceBuffer, CudaError> {
183 if !src.is_owned_by(self) {
184 return Err(CudaError::InvalidArgument {
185 message: "CUDA copy source must belong to the launch context".to_string(),
186 });
187 }
188 if range.start > range.end {
189 return Err(CudaError::InvalidArgument {
190 message: "CUDA copy range start must not exceed its end".to_string(),
191 });
192 }
193 if range.end > src.byte_len() {
194 return Err(CudaError::OutputTooSmall {
195 required: range.end,
196 have: src.byte_len(),
197 });
198 }
199 let byte_len = range.end - range.start;
200 if byte_len == 0 {
201 self.inner.set_current()?;
202 return self.allocate(0);
203 }
204 let geometry =
205 copy_u8_launch_geometry(byte_len).ok_or(CudaError::LengthTooLarge { len: byte_len })?;
206 self.inner.set_current()?;
207 let dst = self.allocate(byte_len)?;
208
209 let source_offset = u64::try_from(range.start)
210 .map_err(|_| CudaError::LengthTooLarge { len: range.start })?;
211 let src_ptr = src
212 .device_ptr()
213 .checked_add(source_offset)
214 .ok_or(CudaError::LengthTooLarge { len: range.end })?;
215 let function = load_function(self)?;
216 let mut dst_ptr = dst.device_ptr();
217 let mut src_ptr = src_ptr;
218 let mut len =
219 u64::try_from(byte_len).map_err(|_| CudaError::LengthTooLarge { len: byte_len })?;
220 let mut params = cuda_kernel_params!(dst_ptr, src_ptr, len);
221
222 self.launch_kernel(function, geometry, &mut params)?;
223
224 Ok(dst)
225 }
226
227 pub fn synchronize(&self) -> Result<(), CudaError> {
229 self.synchronize_for_resource_release().into_result()
230 }
231
232 pub(crate) fn synchronize_for_resource_release(&self) -> CudaSynchronizationOutcome {
233 let result = self.inner.with_current_completion_operation(|| {
234 let status = unsafe { (self.inner.driver.cu_ctx_synchronize)() };
237 self.inner.driver.check("cuCtxSynchronize", status)
238 });
239 match result {
240 Ok(()) => {
241 self.record_context_host_synchronization();
242 CudaSynchronizationOutcome::Completed
243 }
244 Err(error) => {
245 CudaSynchronizationOutcome::CompletionUncertain(error)
249 }
250 }
251 }
252
253 pub(crate) fn error_after_synchronize(&self, error: CudaError) -> CudaError {
256 if self.inner.resource_lifetimes_poisoned() {
257 return select_uncertain_completion_error(error, None);
261 }
262 match self.synchronize() {
263 Ok(()) => error,
264 Err(completion_error) => {
265 select_uncertain_completion_error(error, Some(completion_error))
266 }
267 }
268 }
269
270 pub(crate) fn synchronize_then_error<T>(&self, error: CudaError) -> Result<T, CudaError> {
273 Err(self.error_after_synchronize(error))
274 }
275
276 #[cfg(test)]
278 pub(crate) fn preload_kernel_module(
279 &self,
280 kernel: CudaKernelName,
281 ) -> Result<CudaKernelModule, CudaError> {
282 let _ = self.inner.cuda_oxide_kernel_function(kernel.kernel())?;
283 Ok(CudaKernelModule {
284 entrypoint: kernel.entrypoint(),
285 })
286 }
287}