1use oxicuda_driver::error::{CudaError, CudaResult, check};
52use oxicuda_driver::ffi::{CUDA_MEMCPY2D, CUmemorytype};
53
54use crate::device_buffer::DeviceBuffer;
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub struct Memcpy2DParams {
71 pub src_pitch: usize,
73 pub dst_pitch: usize,
75 pub width: usize,
77 pub height: usize,
79}
80
81impl Memcpy2DParams {
82 pub fn new(src_pitch: usize, dst_pitch: usize, width: usize, height: usize) -> Self {
91 Self {
92 src_pitch,
93 dst_pitch,
94 width,
95 height,
96 }
97 }
98
99 pub fn validate(&self) -> CudaResult<()> {
107 if self.width == 0 || self.height == 0 {
108 return Err(CudaError::InvalidValue);
109 }
110 if self.width > self.src_pitch {
111 return Err(CudaError::InvalidValue);
112 }
113 if self.width > self.dst_pitch {
114 return Err(CudaError::InvalidValue);
115 }
116 Ok(())
117 }
118
119 pub fn src_byte_extent(&self) -> usize {
124 if self.height == 0 {
125 return 0;
126 }
127 self.height
128 .saturating_sub(1)
129 .saturating_mul(self.src_pitch)
130 .saturating_add(self.width)
131 }
132
133 pub fn dst_byte_extent(&self) -> usize {
135 if self.height == 0 {
136 return 0;
137 }
138 self.height
139 .saturating_sub(1)
140 .saturating_mul(self.dst_pitch)
141 .saturating_add(self.width)
142 }
143}
144
145impl std::fmt::Display for Memcpy2DParams {
146 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147 write!(
148 f,
149 "2D[{}x{}, src_pitch={}, dst_pitch={}]",
150 self.width, self.height, self.src_pitch, self.dst_pitch,
151 )
152 }
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
166pub struct Memcpy3DParams {
167 pub src_pitch: usize,
169 pub dst_pitch: usize,
171 pub width: usize,
173 pub height: usize,
175 pub depth: usize,
177 pub src_height: usize,
180 pub dst_height: usize,
182}
183
184impl Memcpy3DParams {
185 #[allow(clippy::too_many_arguments)]
187 pub fn new(
188 src_pitch: usize,
189 dst_pitch: usize,
190 width: usize,
191 height: usize,
192 depth: usize,
193 src_height: usize,
194 dst_height: usize,
195 ) -> Self {
196 Self {
197 src_pitch,
198 dst_pitch,
199 width,
200 height,
201 depth,
202 src_height,
203 dst_height,
204 }
205 }
206
207 pub fn validate(&self) -> CudaResult<()> {
216 if self.width == 0 || self.height == 0 || self.depth == 0 {
217 return Err(CudaError::InvalidValue);
218 }
219 if self.width > self.src_pitch {
220 return Err(CudaError::InvalidValue);
221 }
222 if self.width > self.dst_pitch {
223 return Err(CudaError::InvalidValue);
224 }
225 if self.height > self.src_height {
226 return Err(CudaError::InvalidValue);
227 }
228 if self.height > self.dst_height {
229 return Err(CudaError::InvalidValue);
230 }
231 Ok(())
232 }
233
234 pub fn src_slice_stride(&self) -> usize {
236 self.src_pitch.saturating_mul(self.src_height)
237 }
238
239 pub fn dst_slice_stride(&self) -> usize {
241 self.dst_pitch.saturating_mul(self.dst_height)
242 }
243
244 pub fn src_byte_extent(&self) -> usize {
246 if self.depth == 0 || self.height == 0 {
247 return 0;
248 }
249 let slice_stride = self.src_slice_stride();
250 self.depth
251 .saturating_sub(1)
252 .saturating_mul(slice_stride)
253 .saturating_add(
254 self.height
255 .saturating_sub(1)
256 .saturating_mul(self.src_pitch)
257 .saturating_add(self.width),
258 )
259 }
260
261 pub fn dst_byte_extent(&self) -> usize {
263 if self.depth == 0 || self.height == 0 {
264 return 0;
265 }
266 let slice_stride = self.dst_slice_stride();
267 self.depth
268 .saturating_sub(1)
269 .saturating_mul(slice_stride)
270 .saturating_add(
271 self.height
272 .saturating_sub(1)
273 .saturating_mul(self.dst_pitch)
274 .saturating_add(self.width),
275 )
276 }
277}
278
279impl std::fmt::Display for Memcpy3DParams {
280 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
281 write!(
282 f,
283 "3D[{}x{}x{}, src_pitch={}, dst_pitch={}, src_h={}, dst_h={}]",
284 self.width,
285 self.height,
286 self.depth,
287 self.src_pitch,
288 self.dst_pitch,
289 self.src_height,
290 self.dst_height,
291 )
292 }
293}
294
295fn validate_2d_buffer_size<T: Copy>(buf: &DeviceBuffer<T>, byte_extent: usize) -> CudaResult<()> {
301 if buf.byte_size() < byte_extent {
302 return Err(CudaError::InvalidValue);
303 }
304 Ok(())
305}
306
307fn validate_2d_slice_size<T: Copy>(slice: &[T], byte_extent: usize) -> CudaResult<()> {
309 let slice_bytes = slice.len().saturating_mul(std::mem::size_of::<T>());
310 if slice_bytes < byte_extent {
311 return Err(CudaError::InvalidValue);
312 }
313 Ok(())
314}
315
316pub fn copy_2d_dtod<T: Copy>(
328 dst: &mut DeviceBuffer<T>,
329 src: &DeviceBuffer<T>,
330 params: &Memcpy2DParams,
331) -> CudaResult<()> {
332 params.validate()?;
333 validate_2d_buffer_size(src, params.src_byte_extent())?;
334 validate_2d_buffer_size(dst, params.dst_byte_extent())?;
335
336 let api = oxicuda_driver::loader::try_driver()?;
337 let f = api.cu_memcpy_2d.ok_or(CudaError::NotSupported)?;
338
339 let m = CUDA_MEMCPY2D {
340 src_memory_type: CUmemorytype::Device as u32,
341 src_device: src.as_device_ptr(),
342 src_pitch: params.src_pitch,
343 dst_memory_type: CUmemorytype::Device as u32,
344 dst_device: dst.as_device_ptr(),
345 dst_pitch: params.dst_pitch,
346 width_in_bytes: params.width,
347 height: params.height,
348 ..CUDA_MEMCPY2D::default()
349 };
350
351 check(unsafe { f(&m) })
352}
353
354pub fn copy_2d_htod<T: Copy>(
366 dst: &mut DeviceBuffer<T>,
367 src: &[T],
368 params: &Memcpy2DParams,
369) -> CudaResult<()> {
370 params.validate()?;
371 validate_2d_slice_size(src, params.src_byte_extent())?;
372 validate_2d_buffer_size(dst, params.dst_byte_extent())?;
373
374 let api = oxicuda_driver::loader::try_driver()?;
375 let f = api.cu_memcpy_2d.ok_or(CudaError::NotSupported)?;
376
377 let m = CUDA_MEMCPY2D {
378 src_memory_type: CUmemorytype::Host as u32,
379 src_host: src.as_ptr().cast::<std::ffi::c_void>(),
380 src_pitch: params.src_pitch,
381 dst_memory_type: CUmemorytype::Device as u32,
382 dst_device: dst.as_device_ptr(),
383 dst_pitch: params.dst_pitch,
384 width_in_bytes: params.width,
385 height: params.height,
386 ..CUDA_MEMCPY2D::default()
387 };
388
389 check(unsafe { f(&m) })
390}
391
392pub fn copy_2d_dtoh<T: Copy>(
404 dst: &mut [T],
405 src: &DeviceBuffer<T>,
406 params: &Memcpy2DParams,
407) -> CudaResult<()> {
408 params.validate()?;
409 validate_2d_buffer_size(src, params.src_byte_extent())?;
410 validate_2d_slice_size(dst, params.dst_byte_extent())?;
411
412 let api = oxicuda_driver::loader::try_driver()?;
413 let f = api.cu_memcpy_2d.ok_or(CudaError::NotSupported)?;
414
415 let m = CUDA_MEMCPY2D {
416 src_memory_type: CUmemorytype::Device as u32,
417 src_device: src.as_device_ptr(),
418 src_pitch: params.src_pitch,
419 dst_memory_type: CUmemorytype::Host as u32,
420 dst_host: dst.as_mut_ptr().cast::<std::ffi::c_void>(),
421 dst_pitch: params.dst_pitch,
422 width_in_bytes: params.width,
423 height: params.height,
424 ..CUDA_MEMCPY2D::default()
425 };
426
427 check(unsafe { f(&m) })
428}
429
430fn validate_3d_buffer_size<T: Copy>(buf: &DeviceBuffer<T>, byte_extent: usize) -> CudaResult<()> {
436 if buf.byte_size() < byte_extent {
437 return Err(CudaError::InvalidValue);
438 }
439 Ok(())
440}
441
442pub fn copy_3d_dtod<T: Copy>(
461 dst: &mut DeviceBuffer<T>,
462 src: &DeviceBuffer<T>,
463 params: &Memcpy3DParams,
464) -> CudaResult<()> {
465 params.validate()?;
466 validate_3d_buffer_size(src, params.src_byte_extent())?;
467 validate_3d_buffer_size(dst, params.dst_byte_extent())?;
468
469 let _api = oxicuda_driver::loader::try_driver()?;
474 Err(CudaError::NotSupported)
475}
476
477#[cfg(test)]
482mod tests {
483 use super::*;
484
485 #[test]
488 fn params_2d_new() {
489 let p = Memcpy2DParams::new(512, 512, 480, 256);
490 assert_eq!(p.src_pitch, 512);
491 assert_eq!(p.dst_pitch, 512);
492 assert_eq!(p.width, 480);
493 assert_eq!(p.height, 256);
494 }
495
496 #[test]
497 fn params_2d_validate_ok() {
498 let p = Memcpy2DParams::new(512, 512, 480, 256);
499 assert!(p.validate().is_ok());
500 }
501
502 #[test]
503 fn params_2d_validate_zero_width() {
504 let p = Memcpy2DParams::new(512, 512, 0, 256);
505 assert_eq!(p.validate(), Err(CudaError::InvalidValue));
506 }
507
508 #[test]
509 fn params_2d_validate_zero_height() {
510 let p = Memcpy2DParams::new(512, 512, 480, 0);
511 assert_eq!(p.validate(), Err(CudaError::InvalidValue));
512 }
513
514 #[test]
515 fn params_2d_validate_width_exceeds_src_pitch() {
516 let p = Memcpy2DParams::new(256, 512, 480, 100);
517 assert_eq!(p.validate(), Err(CudaError::InvalidValue));
518 }
519
520 #[test]
521 fn params_2d_validate_width_exceeds_dst_pitch() {
522 let p = Memcpy2DParams::new(512, 256, 480, 100);
523 assert_eq!(p.validate(), Err(CudaError::InvalidValue));
524 }
525
526 #[test]
527 fn params_2d_byte_extent() {
528 let p = Memcpy2DParams::new(512, 256, 480, 3);
531 assert_eq!(p.src_byte_extent(), 2 * 512 + 480);
532 assert_eq!(p.dst_byte_extent(), 2 * 256 + 480);
533 }
534
535 #[test]
536 fn params_2d_byte_extent_single_row() {
537 let p = Memcpy2DParams::new(512, 512, 480, 1);
538 assert_eq!(p.src_byte_extent(), 480);
539 assert_eq!(p.dst_byte_extent(), 480);
540 }
541
542 #[test]
543 fn params_2d_byte_extent_zero_height() {
544 let p = Memcpy2DParams::new(512, 512, 480, 0);
545 assert_eq!(p.src_byte_extent(), 0);
546 assert_eq!(p.dst_byte_extent(), 0);
547 }
548
549 #[test]
550 fn params_2d_display() {
551 let p = Memcpy2DParams::new(512, 256, 480, 100);
552 let disp = format!("{p}");
553 assert!(disp.contains("480x100"));
554 assert!(disp.contains("src_pitch=512"));
555 assert!(disp.contains("dst_pitch=256"));
556 }
557
558 #[test]
559 fn params_2d_eq() {
560 let a = Memcpy2DParams::new(512, 512, 480, 256);
561 let b = Memcpy2DParams::new(512, 512, 480, 256);
562 assert_eq!(a, b);
563 }
564
565 #[test]
568 fn params_3d_new() {
569 let p = Memcpy3DParams::new(512, 512, 480, 256, 10, 256, 256);
570 assert_eq!(p.depth, 10);
571 assert_eq!(p.src_height, 256);
572 assert_eq!(p.dst_height, 256);
573 }
574
575 #[test]
576 fn params_3d_validate_ok() {
577 let p = Memcpy3DParams::new(512, 512, 480, 256, 10, 256, 256);
578 assert!(p.validate().is_ok());
579 }
580
581 #[test]
582 fn params_3d_validate_zero_depth() {
583 let p = Memcpy3DParams::new(512, 512, 480, 256, 0, 256, 256);
584 assert_eq!(p.validate(), Err(CudaError::InvalidValue));
585 }
586
587 #[test]
588 fn params_3d_validate_height_exceeds_src_height() {
589 let p = Memcpy3DParams::new(512, 512, 480, 300, 10, 256, 300);
590 assert_eq!(p.validate(), Err(CudaError::InvalidValue));
591 }
592
593 #[test]
594 fn params_3d_validate_height_exceeds_dst_height() {
595 let p = Memcpy3DParams::new(512, 512, 480, 300, 10, 300, 256);
596 assert_eq!(p.validate(), Err(CudaError::InvalidValue));
597 }
598
599 #[test]
600 fn params_3d_slice_stride() {
601 let p = Memcpy3DParams::new(512, 256, 480, 100, 10, 128, 128);
602 assert_eq!(p.src_slice_stride(), 512 * 128);
603 assert_eq!(p.dst_slice_stride(), 256 * 128);
604 }
605
606 #[test]
607 fn params_3d_byte_extent() {
608 let p = Memcpy3DParams::new(512, 512, 480, 3, 2, 4, 4);
610 assert_eq!(p.src_byte_extent(), (512 * 4) + 2 * 512 + 480);
613 }
614
615 #[test]
616 fn params_3d_byte_extent_single_slice() {
617 let p = Memcpy3DParams::new(512, 512, 480, 3, 1, 4, 4);
618 assert_eq!(p.src_byte_extent(), 2 * 512 + 480);
620 }
621
622 #[test]
623 fn params_3d_display() {
624 let p = Memcpy3DParams::new(512, 256, 480, 100, 10, 128, 128);
625 let disp = format!("{p}");
626 assert!(disp.contains("480x100x10"));
627 }
628
629 #[test]
632 fn copy_2d_dtod_signature_compiles() {
633 let _: fn(&mut DeviceBuffer<f32>, &DeviceBuffer<f32>, &Memcpy2DParams) -> CudaResult<()> =
634 copy_2d_dtod;
635 }
636
637 #[test]
638 fn copy_2d_htod_signature_compiles() {
639 let _: fn(&mut DeviceBuffer<f32>, &[f32], &Memcpy2DParams) -> CudaResult<()> = copy_2d_htod;
640 }
641
642 #[test]
643 fn copy_2d_dtoh_signature_compiles() {
644 let _: fn(&mut [f32], &DeviceBuffer<f32>, &Memcpy2DParams) -> CudaResult<()> = copy_2d_dtoh;
645 }
646
647 #[test]
648 fn copy_3d_dtod_signature_compiles() {
649 let _: fn(&mut DeviceBuffer<f32>, &DeviceBuffer<f32>, &Memcpy3DParams) -> CudaResult<()> =
650 copy_3d_dtod;
651 }
652
653 #[test]
654 fn params_2d_equal_pitch() {
655 let p = Memcpy2DParams::new(100, 100, 100, 50);
657 assert!(p.validate().is_ok());
658 assert_eq!(p.src_byte_extent(), 49 * 100 + 100);
659 assert_eq!(p.dst_byte_extent(), 49 * 100 + 100);
660 }
661
662 #[cfg(feature = "gpu-tests")]
670 #[test]
671 fn copy_3d_dtod_reports_not_supported_instead_of_fabricating_success() {
672 if oxicuda_driver::loader::try_driver().is_err() {
673 eprintln!("skipping: no CUDA driver");
674 return;
675 }
676 let params = Memcpy3DParams::new(512, 512, 480, 256, 4, 256, 256);
677 assert!(params.validate().is_ok());
678 let byte_len = params.src_byte_extent().max(params.dst_byte_extent());
679
680 let src = unsafe { DeviceBuffer::<u8>::from_raw(0x1000, byte_len) };
685 let mut dst = unsafe { DeviceBuffer::<u8>::from_raw(0x2000, byte_len) };
686
687 let result = copy_3d_dtod(&mut dst, &src, ¶ms);
688 assert_eq!(result, Err(CudaError::NotSupported));
689 }
690}