1#[cfg(feature = "cuda-runtime")]
4use std::sync::Arc;
5
6use j2k_core::{
7 copy_tight_pixels_to_strided_output, BackendKind, BufferError, DeviceMemoryRange,
8 DeviceSurface, ExecutionStats, PixelFormat, SurfaceMetadata,
9};
10#[cfg(feature = "cuda-runtime")]
11use j2k_cuda_runtime::CudaDeviceBuffer;
12
13use crate::allocation::try_vec_filled;
14#[cfg(feature = "cuda-runtime")]
15use crate::allocation::try_vec_with_capacity;
16#[cfg(feature = "cuda-runtime")]
17use crate::runtime::cuda_error;
18use crate::Error;
19
20pub use j2k_core::SurfaceResidency;
21
22#[derive(Debug)]
23pub(crate) enum Storage {
24 Host(Vec<u8>),
25 #[cfg(feature = "cuda-runtime")]
26 Cuda(CudaDeviceBuffer),
27 #[cfg(feature = "cuda-runtime")]
28 CudaRange {
29 buffer: Arc<CudaDeviceBuffer>,
30 offset: usize,
31 len: usize,
32 },
33}
34
35#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
37#[doc(hidden)]
38pub struct CudaSurfaceStats {
39 pub(crate) total: usize,
40 pub(crate) copy: usize,
41 pub(crate) decode: usize,
42}
43
44#[doc(hidden)]
45impl CudaSurfaceStats {
46 pub fn kernel_dispatches(self) -> usize {
48 self.total
49 }
50
51 pub fn copy_kernel_dispatches(self) -> usize {
53 self.copy
54 }
55
56 pub fn decode_kernel_dispatches(self) -> usize {
58 self.decode
59 }
60}
61
62#[derive(Clone, Copy, Debug)]
64pub struct CudaSurface<'a> {
65 device_ptr: u64,
66 _marker: core::marker::PhantomData<&'a ()>,
67 pub(crate) stats: CudaSurfaceStats,
68}
69
70impl CudaSurface<'_> {
71 pub fn device_ptr(&self) -> u64 {
73 self.device_ptr
74 }
75
76 #[doc(hidden)]
78 pub fn stats(&self) -> CudaSurfaceStats {
79 self.stats
80 }
81}
82
83#[derive(Debug)]
85pub struct Surface {
86 pub(crate) backend: BackendKind,
87 pub(crate) residency: SurfaceResidency,
88 pub(crate) dimensions: (u32, u32),
89 pub(crate) fmt: PixelFormat,
90 pub(crate) pitch_bytes: usize,
91 pub(crate) stats: CudaSurfaceStats,
92 pub(crate) storage: Storage,
93}
94
95impl Surface {
96 fn metadata(&self) -> SurfaceMetadata {
97 SurfaceMetadata::new(
98 self.backend,
99 self.residency,
100 self.dimensions,
101 self.fmt,
102 self.pitch_bytes,
103 )
104 }
105
106 pub fn residency(&self) -> SurfaceResidency {
108 self.residency
109 }
110
111 pub fn pitch_bytes(&self) -> usize {
113 self.pitch_bytes
114 }
115
116 pub fn as_host_bytes(&self) -> Option<&[u8]> {
118 match &self.storage {
119 Storage::Host(bytes) => Some(bytes),
120 #[cfg(feature = "cuda-runtime")]
121 Storage::Cuda(_) | Storage::CudaRange { .. } => None,
122 }
123 }
124
125 pub fn download_into(&self, out: &mut [u8], stride: usize) -> Result<(), Error> {
127 match &self.storage {
128 Storage::Host(bytes) => {
129 copy_tight_pixels_to_strided_output(bytes, self.dimensions, self.fmt, out, stride)
130 .map_err(Error::from)
131 }
132 #[cfg(feature = "cuda-runtime")]
133 Storage::Cuda(buffer) => {
134 let byte_len = self.byte_len();
135 if let Some(len) =
136 tight_cuda_download_len(byte_len, self.pitch_bytes, stride, out.len())
137 {
138 return buffer.copy_to_host(&mut out[..len]).map_err(cuda_error);
139 }
140 let mut tight = try_vec_filled(byte_len, 0u8, "j2k CUDA surface download staging")?;
141 buffer.copy_to_host(&mut tight).map_err(cuda_error)?;
142 copy_tight_pixels_to_strided_output(&tight, self.dimensions, self.fmt, out, stride)
143 .map_err(Error::from)
144 }
145 #[cfg(feature = "cuda-runtime")]
146 Storage::CudaRange {
147 buffer,
148 offset,
149 len,
150 } => {
151 let byte_len = self.byte_len();
152 if *len < byte_len {
153 return Err(BufferError::InputTooSmall {
154 required: byte_len,
155 have: *len,
156 }
157 .into());
158 }
159 if let Some(len) =
160 tight_cuda_download_len(byte_len, self.pitch_bytes, stride, out.len())
161 {
162 return buffer
163 .copy_range_to_host(*offset, &mut out[..len])
164 .map_err(cuda_error);
165 }
166 let mut tight = try_vec_filled(byte_len, 0u8, "j2k CUDA range download staging")?;
167 buffer
168 .copy_range_to_host(*offset, &mut tight)
169 .map_err(cuda_error)?;
170 copy_tight_pixels_to_strided_output(&tight, self.dimensions, self.fmt, out, stride)
171 .map_err(Error::from)
172 }
173 }
174 }
175
176 pub fn cuda_surface(&self) -> Option<CudaSurface<'_>> {
178 #[cfg(feature = "cuda-runtime")]
179 match &self.storage {
180 Storage::Cuda(buffer) => Some(CudaSurface {
181 device_ptr: buffer.device_ptr(),
182 _marker: core::marker::PhantomData,
183 stats: self.stats,
184 }),
185 Storage::CudaRange { buffer, offset, .. } => {
186 let offset = u64::try_from(*offset).ok()?;
187 let device_ptr = buffer.device_ptr().checked_add(offset)?;
188 Some(CudaSurface {
189 device_ptr,
190 _marker: core::marker::PhantomData,
191 stats: self.stats,
192 })
193 }
194 Storage::Host(_) => None,
195 }
196 #[cfg(not(feature = "cuda-runtime"))]
197 {
198 let _ = self.stats;
199 None
200 }
201 }
202
203 pub fn download_batch_tight(surfaces: &[Self]) -> Result<Vec<u8>, Error> {
209 let required = batch_tight_required_len(surfaces)?;
210 if required == 0 {
211 return Ok(Vec::new());
212 }
213
214 #[cfg(feature = "cuda-runtime")]
215 if let Some((buffer, offset)) = contiguous_cuda_batch_range(surfaces) {
216 let mut out = try_vec_with_capacity(required, "j2k CUDA contiguous batch download")?;
217 buffer
218 .copy_range_to_host_uninit(offset, &mut out.spare_capacity_mut()[..required])
219 .map_err(cuda_error)?;
220 unsafe {
223 out.set_len(required);
224 }
225 return Ok(out);
226 }
227
228 let mut out = try_vec_filled(required, 0u8, "j2k CUDA batch download")?;
229 Self::download_batch_tight_into(surfaces, &mut out)?;
230 Ok(out)
231 }
232
233 pub fn download_batch_tight_into(surfaces: &[Self], out: &mut [u8]) -> Result<(), Error> {
239 let required = batch_tight_required_len(surfaces)?;
240 if out.len() < required {
241 return Err(BufferError::OutputTooSmall {
242 required,
243 have: out.len(),
244 }
245 .into());
246 }
247 if required == 0 {
248 return Ok(());
249 }
250
251 #[cfg(feature = "cuda-runtime")]
252 if let Some((buffer, offset)) = contiguous_cuda_batch_range(surfaces) {
253 return buffer
254 .copy_range_to_host(offset, &mut out[..required])
255 .map_err(cuda_error);
256 }
257
258 let mut cursor = 0usize;
259 for surface in surfaces {
260 let len = surface.byte_len();
261 surface.download_into(&mut out[cursor..cursor + len], surface.pitch_bytes)?;
262 cursor += len;
263 }
264 Ok(())
265 }
266}
267
268fn batch_tight_required_len(surfaces: &[Surface]) -> Result<usize, Error> {
269 surfaces
270 .iter()
271 .try_fold(0usize, |sum, surface| sum.checked_add(surface.byte_len()))
272 .ok_or(BufferError::SizeOverflow {
273 what: "tight batch surface output",
274 })
275 .map_err(Error::from)
276}
277
278#[cfg(feature = "cuda-runtime")]
279pub(crate) fn cuda_range_storage(
280 buffer: Arc<CudaDeviceBuffer>,
281 offset: usize,
282 len: usize,
283) -> Storage {
284 Storage::CudaRange {
285 buffer,
286 offset,
287 len,
288 }
289}
290
291#[cfg(feature = "cuda-runtime")]
292fn contiguous_cuda_batch_range(surfaces: &[Surface]) -> Option<(&CudaDeviceBuffer, usize)> {
293 let first = surfaces.first()?;
294 let Storage::CudaRange {
295 buffer,
296 offset,
297 len,
298 } = &first.storage
299 else {
300 return None;
301 };
302 let first_buffer = buffer;
303 let first_offset = *offset;
304 let mut expected_offset = first_offset.checked_add(*len)?;
305 for surface in &surfaces[1..] {
306 let Storage::CudaRange {
307 buffer,
308 offset,
309 len,
310 } = &surface.storage
311 else {
312 return None;
313 };
314 if !Arc::ptr_eq(first_buffer, buffer) || *offset != expected_offset {
315 return None;
316 }
317 expected_offset = expected_offset.checked_add(*len)?;
318 }
319 Some((first_buffer.as_ref(), first_offset))
320}
321
322#[cfg(any(feature = "cuda-runtime", test))]
323fn tight_cuda_download_len(
324 byte_len: usize,
325 pitch_bytes: usize,
326 stride: usize,
327 out_len: usize,
328) -> Option<usize> {
329 (stride == pitch_bytes && out_len >= byte_len).then_some(byte_len)
330}
331
332#[doc(hidden)]
333impl DeviceSurface for Surface {
334 fn backend_kind(&self) -> BackendKind {
335 self.metadata().backend
336 }
337
338 fn residency(&self) -> j2k_core::SurfaceResidency {
339 self.metadata().residency
340 }
341
342 fn dimensions(&self) -> (u32, u32) {
343 self.metadata().dimensions
344 }
345
346 fn pixel_format(&self) -> PixelFormat {
347 self.metadata().pixel_format
348 }
349
350 fn byte_len(&self) -> usize {
351 self.metadata().byte_len()
352 }
353
354 fn execution_stats(&self) -> ExecutionStats {
355 ExecutionStats {
356 kernel_dispatches: self.stats.total as u64,
357 ..ExecutionStats::default()
358 }
359 }
360
361 fn memory_range(&self) -> Option<DeviceMemoryRange> {
362 match &self.storage {
363 Storage::Host(_) => None,
364 #[cfg(feature = "cuda-runtime")]
365 Storage::Cuda(buffer) => Some(DeviceMemoryRange::new(
366 BackendKind::Cuda,
367 buffer.device_ptr(),
368 0,
369 self.byte_len(),
370 )),
371 #[cfg(feature = "cuda-runtime")]
372 Storage::CudaRange {
373 buffer,
374 offset,
375 len,
376 } => Some(DeviceMemoryRange::new(
377 BackendKind::Cuda,
378 buffer.device_ptr(),
379 *offset,
380 *len,
381 )),
382 }
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::{tight_cuda_download_len, CudaSurfaceStats, Storage, Surface, SurfaceResidency};
389 use j2k_core::{BackendKind, PixelFormat};
390
391 #[test]
392 fn tight_cuda_download_len_accepts_exact_tight_output() {
393 assert_eq!(tight_cuda_download_len(32, 8, 8, 32), Some(32));
394 }
395
396 #[test]
397 fn download_batch_tight_returns_tightly_concatenated_host_surfaces() {
398 let surfaces = [
399 Surface {
400 backend: BackendKind::Cpu,
401 residency: SurfaceResidency::Host,
402 dimensions: (2, 1),
403 fmt: PixelFormat::Gray8,
404 pitch_bytes: 2,
405 stats: CudaSurfaceStats::default(),
406 storage: Storage::Host(vec![1, 2]),
407 },
408 Surface {
409 backend: BackendKind::Cpu,
410 residency: SurfaceResidency::Host,
411 dimensions: (1, 1),
412 fmt: PixelFormat::Rgb8,
413 pitch_bytes: 3,
414 stats: CudaSurfaceStats::default(),
415 storage: Storage::Host(vec![3, 4, 5]),
416 },
417 ];
418
419 let tight = Surface::download_batch_tight(&surfaces).expect("batch download");
420
421 assert_eq!(tight, vec![1, 2, 3, 4, 5]);
422 }
423}