1use std::mem::MaybeUninit;
7use std::os::raw::c_uint;
8
9use cuda_driver_sys::{CUarray, CUarray_format, CUarray_format_enum};
10
11use crate::context::CurrentContext;
12use crate::device::DeviceAttribute;
13use crate::error::*;
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum ArrayFormat {
18 UnsignedInt8,
20 UnsignedInt16,
22 UnsignedInt32,
24 SignedInt8,
26 SignedInt16,
28 SignedInt32,
30 Half,
32 Float,
34}
35
36impl ArrayFormat {
37 pub fn from_raw(raw: CUarray_format) -> Self {
39 match raw {
40 CUarray_format_enum::CU_AD_FORMAT_UNSIGNED_INT8 => ArrayFormat::UnsignedInt8,
41 CUarray_format_enum::CU_AD_FORMAT_UNSIGNED_INT16 => ArrayFormat::UnsignedInt16,
42 CUarray_format_enum::CU_AD_FORMAT_UNSIGNED_INT32 => ArrayFormat::UnsignedInt32,
43 CUarray_format_enum::CU_AD_FORMAT_SIGNED_INT8 => ArrayFormat::SignedInt8,
44 CUarray_format_enum::CU_AD_FORMAT_SIGNED_INT16 => ArrayFormat::SignedInt16,
45 CUarray_format_enum::CU_AD_FORMAT_SIGNED_INT32 => ArrayFormat::SignedInt32,
46 CUarray_format_enum::CU_AD_FORMAT_HALF => ArrayFormat::Half,
47 CUarray_format_enum::CU_AD_FORMAT_FLOAT => ArrayFormat::Float,
48 }
49 }
50
51 pub fn to_raw(self) -> CUarray_format {
53 match self {
54 ArrayFormat::UnsignedInt8 => CUarray_format_enum::CU_AD_FORMAT_UNSIGNED_INT8,
55 ArrayFormat::UnsignedInt16 => CUarray_format_enum::CU_AD_FORMAT_UNSIGNED_INT16,
56 ArrayFormat::UnsignedInt32 => CUarray_format_enum::CU_AD_FORMAT_UNSIGNED_INT32,
57 ArrayFormat::SignedInt8 => CUarray_format_enum::CU_AD_FORMAT_SIGNED_INT8,
58 ArrayFormat::SignedInt16 => CUarray_format_enum::CU_AD_FORMAT_SIGNED_INT16,
59 ArrayFormat::SignedInt32 => CUarray_format_enum::CU_AD_FORMAT_SIGNED_INT32,
60 ArrayFormat::Half => CUarray_format_enum::CU_AD_FORMAT_HALF,
61 ArrayFormat::Float => CUarray_format_enum::CU_AD_FORMAT_FLOAT,
62 }
63 }
64}
65
66bitflags! {
67 #[derive(Default)]
69 pub struct ArrayObjectFlags: c_uint {
70 const LAYERED = cuda_driver_sys::CUDA_ARRAY3D_LAYERED;
73
74 const SURFACE_LDST = cuda_driver_sys::CUDA_ARRAY3D_SURFACE_LDST;
76
77 const CUBEMAP = cuda_driver_sys::CUDA_ARRAY3D_CUBEMAP;
81
82 const TEXTURE_GATHER = cuda_driver_sys::CUDA_ARRAY3D_TEXTURE_GATHER;
85 }
86}
87
88impl ArrayObjectFlags {
89 pub fn new() -> Self {
91 Self::default()
92 }
93}
94
95#[derive(Clone, Copy, Debug)]
97pub struct ArrayDescriptor {
98 desc: cuda_driver_sys::CUDA_ARRAY3D_DESCRIPTOR,
99}
100
101impl ArrayDescriptor {
102 pub fn from_raw(desc: cuda_driver_sys::CUDA_ARRAY3D_DESCRIPTOR) -> Self {
104 Self { desc }
105 }
106
107 pub fn new(
109 dims: [usize; 3],
110 format: ArrayFormat,
111 num_channels: c_uint,
112 flags: ArrayObjectFlags,
113 ) -> Self {
114 Self {
115 desc: cuda_driver_sys::CUDA_ARRAY3D_DESCRIPTOR {
116 Width: dims[0],
117 Height: dims[1],
118 Depth: dims[2],
119 Format: format.to_raw(),
120 NumChannels: num_channels,
121 Flags: flags.bits(),
122 },
123 }
124 }
125
126 pub fn from_dims_format(dims: [usize; 3], format: ArrayFormat) -> Self {
128 Self {
129 desc: cuda_driver_sys::CUDA_ARRAY3D_DESCRIPTOR {
130 Width: dims[0],
131 Height: dims[1],
132 Depth: dims[2],
133 Format: format.to_raw(),
134 NumChannels: 1,
135 Flags: ArrayObjectFlags::default().bits(),
136 },
137 }
138 }
139
140 pub fn dims(&self) -> [usize; 3] {
142 [self.desc.Width, self.desc.Height, self.desc.Depth]
143 }
144
145 pub fn set_dims(&mut self, dims: [usize; 3]) {
147 self.desc.Width = dims[0];
148 self.desc.Height = dims[1];
149 self.desc.Depth = dims[2];
150 }
151
152 pub fn width(&self) -> usize {
154 self.desc.Width
155 }
156
157 pub fn set_width(&mut self, width: usize) {
159 self.desc.Width = width;
160 }
161
162 pub fn height(&self) -> usize {
164 self.desc.Height
165 }
166
167 pub fn set_height(&mut self, height: usize) {
169 self.desc.Height = height;
170 }
171
172 pub fn depth(&self) -> usize {
174 self.desc.Depth
175 }
176
177 pub fn set_depth(&mut self, depth: usize) {
179 self.desc.Depth = depth;
180 }
181
182 pub fn format(&self) -> ArrayFormat {
184 ArrayFormat::from_raw(self.desc.Format)
185 }
186
187 pub fn set_format(&mut self, format: ArrayFormat) {
189 self.desc.Format = format.to_raw();
190 }
191
192 pub fn num_channels(&self) -> c_uint {
194 self.desc.NumChannels
195 }
196
197 pub fn set_num_channels(&mut self, num_channels: c_uint) {
199 self.desc.NumChannels = num_channels;
200 }
201
202 pub fn flags(&self) -> ArrayObjectFlags {
204 ArrayObjectFlags::from_bits_truncate(self.desc.Flags)
205 }
206
207 pub fn set_flags(&mut self, flags: ArrayObjectFlags) {
209 self.desc.Flags = flags.bits();
210 }
211}
212
213pub struct ArrayObject {
215 handle: CUarray,
216}
217
218impl ArrayObject {
219 pub fn from_descriptor(descriptor: &ArrayDescriptor) -> CudaResult<Self> {
221 if cfg!(debug_assertions) {
224 assert_ne!(
225 0,
226 descriptor.width(),
227 "Cannot allocate an array with 0 Width"
228 );
229
230 if !descriptor.flags().contains(ArrayObjectFlags::LAYERED) && descriptor.depth() > 0 {
231 assert_ne!(
232 0,
233 descriptor.height(),
234 "If Depth is non-zero and the descriptor is not LAYERED, then Height must also \
235 be non-zero."
236 );
237 }
238
239 if descriptor.flags().contains(ArrayObjectFlags::CUBEMAP) {
240 assert_eq!(
241 descriptor.height(),
242 descriptor.width(),
243 "Height and Width must be equal for CUBEMAP arrays."
244 );
245
246 if descriptor.flags().contains(ArrayObjectFlags::LAYERED) {
247 assert_eq!(
248 0,
249 descriptor.depth() % 6,
250 "Depth must be a multiple of 6 when the array descriptor is for a LAYERED \
251 CUBEMAP."
252 );
253 } else {
254 assert_eq!(
255 6,
256 descriptor.depth(),
257 "Depth must be equal to 6 when the array descriptor is for a CUBEMAP."
258 );
259 }
260 }
261
262 assert!(
263 descriptor.num_channels() == 1
264 || descriptor.num_channels() == 2
265 || descriptor.num_channels() == 4,
266 "NumChannels was set to {}. It must be 1, 2, or 4.",
267 descriptor.num_channels()
268 );
269
270 let device = CurrentContext::get_device()?;
272
273 let attr = |attr| Ok(1..=(device.get_attribute(attr)? as usize));
274
275 let (description, bounds) = if descriptor.flags().contains(ArrayObjectFlags::CUBEMAP) {
276 if descriptor.flags().contains(ArrayObjectFlags::LAYERED) {
277 (
278 "Layered Cubemap",
279 vec![[
280 attr(DeviceAttribute::MaximumTextureCubemapLayeredWidth)?,
281 attr(DeviceAttribute::MaximumTextureCubemapLayeredWidth)?,
282 attr(DeviceAttribute::MaximumTextureCubemapLayeredLayers)?,
283 ]],
284 )
285 } else {
286 (
287 "Cubemap",
288 vec![[
289 attr(DeviceAttribute::MaximumTextureCubemapWidth)?,
290 attr(DeviceAttribute::MaximumTextureCubemapWidth)?,
291 6..=6,
292 ]],
293 )
294 }
295 } else if descriptor.flags().contains(ArrayObjectFlags::LAYERED) {
296 if descriptor.height() > 0 {
297 (
298 "2D Layered",
299 vec![[
300 attr(DeviceAttribute::MaximumTexture2DLayeredWidth)?,
301 attr(DeviceAttribute::MaximumTexture2DLayeredHeight)?,
302 attr(DeviceAttribute::MaximumTexture2DLayeredLayers)?,
303 ]],
304 )
305 } else {
306 (
307 "1D Layered",
308 vec![[
309 attr(DeviceAttribute::MaximumTexture1DLayeredWidth)?,
310 0..=0,
311 attr(DeviceAttribute::MaximumTexture1DLayeredLayers)?,
312 ]],
313 )
314 }
315 } else if descriptor.depth() > 0 {
316 (
317 "3D",
318 vec![
319 [
320 attr(DeviceAttribute::MaximumTexture3DWidth)?,
321 attr(DeviceAttribute::MaximumTexture3DHeight)?,
322 attr(DeviceAttribute::MaximumTexture3DDepth)?,
323 ],
324 [
325 attr(DeviceAttribute::MaximumTexture3DWidthAlternate)?,
326 attr(DeviceAttribute::MaximumTexture3DHeightAlternate)?,
327 attr(DeviceAttribute::MaximumTexture3DDepthAlternate)?,
328 ],
329 ],
330 )
331 } else if descriptor.height() > 0 {
332 if descriptor
333 .flags()
334 .contains(ArrayObjectFlags::TEXTURE_GATHER)
335 {
336 (
337 "2D Texture Gather",
338 vec![[
339 attr(DeviceAttribute::MaximumTexture2DGatherWidth)?,
340 attr(DeviceAttribute::MaximumTexture2DGatherHeight)?,
341 0..=0,
342 ]],
343 )
344 } else {
345 (
346 "2D",
347 vec![[
348 attr(DeviceAttribute::MaximumTexture2DWidth)?,
349 attr(DeviceAttribute::MaximumTexture2DHeight)?,
350 0..=0,
351 ]],
352 )
353 }
354 } else {
355 assert!(descriptor.width() > 0);
356 (
357 "1D",
358 vec![[attr(DeviceAttribute::MaximumTexture1DWidth)?, 0..=0, 0..=0]],
359 )
360 };
361
362 let bounds_invalid = |x: &[::std::ops::RangeInclusive<usize>; 3]| {
363 (descriptor.width() >= *x[0].start() && descriptor.width() <= *x[0].end())
364 && (descriptor.height() >= *x[1].start() && descriptor.height() <= *x[1].end())
365 && (descriptor.depth() >= *x[2].start() && descriptor.depth() <= *x[2].end())
366 };
367
368 if !bounds.iter().any(bounds_invalid) {
369 panic!(
370 "The dimensions of the {} ArrayObject did not fall within the valid bounds for \
371 the array. descriptor = {:?}, dims = {:?}, valid bounds = {:?}",
372 description,
373 descriptor,
374 [descriptor.width(), descriptor.height(), descriptor.depth()],
375 bounds
376 );
377 }
378 }
379
380 let mut handle = MaybeUninit::uninit();
381 unsafe { cuda_driver_sys::cuArray3DCreate_v2(handle.as_mut_ptr(), &descriptor.desc) }
382 .to_result()?;
383 Ok(Self {
384 handle: unsafe { handle.assume_init() },
385 })
386 }
387
388 pub fn new(dims: [usize; 3], format: ArrayFormat, num_channels: c_uint) -> CudaResult<Self> {
412 Self::from_descriptor(&ArrayDescriptor::new(
413 dims,
414 format,
415 num_channels,
416 Default::default(),
417 ))
418 }
419
420 pub fn new_1d(width: usize, format: ArrayFormat, num_channels: c_uint) -> CudaResult<Self> {
441 Self::from_descriptor(&ArrayDescriptor::new(
442 [width, 0, 0],
443 format,
444 num_channels,
445 Default::default(),
446 ))
447 }
448
449 pub fn new_2d(dims: [usize; 2], format: ArrayFormat, num_channels: c_uint) -> CudaResult<Self> {
471 Self::from_descriptor(&ArrayDescriptor::new(
472 [dims[0], dims[1], 0],
473 format,
474 num_channels,
475 Default::default(),
476 ))
477 }
478
479 pub fn new_layered(
504 dims: [usize; 2],
505 num_layers: usize,
506 format: ArrayFormat,
507 num_channels: c_uint,
508 ) -> CudaResult<Self> {
509 Self::from_descriptor(&ArrayDescriptor::new(
510 [dims[0], dims[1], num_layers],
511 format,
512 num_channels,
513 ArrayObjectFlags::LAYERED,
514 ))
515 }
516
517 pub fn new_layered_1d(
541 width: usize,
542 num_layers: usize,
543 format: ArrayFormat,
544 num_channels: c_uint,
545 ) -> CudaResult<Self> {
546 Self::from_descriptor(&ArrayDescriptor::new(
547 [width, 0, num_layers],
548 format,
549 num_channels,
550 ArrayObjectFlags::LAYERED,
551 ))
552 }
553
554 pub fn new_cubemap(side: usize, format: ArrayFormat, num_channels: c_uint) -> CudaResult<Self> {
579 Self::from_descriptor(&ArrayDescriptor::new(
580 [side, side, 6],
581 format,
582 num_channels,
583 ArrayObjectFlags::CUBEMAP,
584 ))
585 }
586
587 pub fn new_layered_cubemap(
616 side: usize,
617 num_layers: usize,
618 format: ArrayFormat,
619 num_channels: c_uint,
620 ) -> CudaResult<Self> {
621 Self::from_descriptor(&ArrayDescriptor::new(
622 [side, side, num_layers * 6],
623 format,
624 num_channels,
625 ArrayObjectFlags::CUBEMAP | ArrayObjectFlags::LAYERED,
626 ))
627 }
628
629 pub fn descriptor(&self) -> CudaResult<ArrayDescriptor> {
631 let mut raw_descriptor = MaybeUninit::zeroed();
633 unsafe {
634 cuda_driver_sys::cuArray3DGetDescriptor_v2(raw_descriptor.as_mut_ptr(), self.handle)
635 }
636 .to_result()?;
637
638 Ok(ArrayDescriptor::from_raw(unsafe {
639 raw_descriptor.assume_init()
640 }))
641 }
642
643 pub fn drop(array: ArrayObject) -> DropResult<ArrayObject> {
646 match unsafe { cuda_driver_sys::cuArrayDestroy(array.handle) }.to_result() {
647 Ok(()) => Ok(()),
648 Err(e) => Err((e, array)),
649 }
650 }
651}
652
653impl std::fmt::Debug for ArrayObject {
654 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
655 self.descriptor().fmt(f)
656 }
657}
658
659impl Drop for ArrayObject {
660 fn drop(&mut self) {
661 unsafe { cuda_driver_sys::cuArrayDestroy(self.handle) }
662 .to_result()
663 .expect("Failed to destroy CUDA Array")
664 }
665}
666
667#[cfg(test)]
668mod test {
669 use super::*;
670
671 #[test]
672 fn descriptor_round_trip() {
673 let _context = crate::quick_init().unwrap();
674
675 let obj = ArrayObject::new([1, 2, 3], ArrayFormat::Float, 2).unwrap();
676
677 let descriptor = obj.descriptor().unwrap();
678 assert_eq!([1, 2, 3], descriptor.dims());
679 assert_eq!(ArrayFormat::Float, descriptor.format());
680 assert_eq!(2, descriptor.num_channels());
681 assert_eq!(ArrayObjectFlags::default(), descriptor.flags());
682 }
683
684 #[test]
685 fn allow_1d_arrays() {
686 let _context = crate::quick_init().unwrap();
687
688 let obj = ArrayObject::new([10, 0, 0], ArrayFormat::Float, 1).unwrap();
689
690 let descriptor = obj.descriptor().unwrap();
691 assert_eq!([10, 0, 0], descriptor.dims());
692 }
693
694 #[test]
695 fn allow_2d_arrays() {
696 let _context = crate::quick_init().unwrap();
697
698 let obj = ArrayObject::new([10, 20, 0], ArrayFormat::Float, 1).unwrap();
699
700 let descriptor = obj.descriptor().unwrap();
701 assert_eq!([10, 20, 0], descriptor.dims());
702 }
703
704 #[test]
705 fn allow_1d_layered_arrays() {
706 let _context = crate::quick_init().unwrap();
707
708 let obj = ArrayObject::new_layered([10, 0], 20, ArrayFormat::Float, 1).unwrap();
709
710 let descriptor = obj.descriptor().unwrap();
711 assert_eq!([10, 0, 20], descriptor.dims());
712 assert_eq!(ArrayObjectFlags::LAYERED, descriptor.flags());
713 }
714
715 #[test]
716 fn allow_cubemaps() {
717 let _context = crate::quick_init().unwrap();
718
719 let obj = ArrayObject::new_cubemap(4, ArrayFormat::Float, 1).unwrap();
720
721 let descriptor = obj.descriptor().unwrap();
722 assert_eq!([4, 4, 6], descriptor.dims());
723 assert_eq!(ArrayObjectFlags::CUBEMAP, descriptor.flags());
724 }
725
726 #[test]
727 fn allow_layered_cubemaps() {
728 let _context = crate::quick_init().unwrap();
729
730 let obj = ArrayObject::new_layered_cubemap(4, 4, ArrayFormat::Float, 1).unwrap();
731
732 let descriptor = obj.descriptor().unwrap();
733 assert_eq!([4, 4, 24], descriptor.dims());
734 assert_eq!(
735 ArrayObjectFlags::CUBEMAP | ArrayObjectFlags::LAYERED,
736 descriptor.flags()
737 );
738 }
739
740 #[test]
741 #[should_panic]
742 fn fail_on_zero_width_1d_array() {
743 let _context = crate::quick_init().unwrap();
744
745 let _ = ArrayObject::new_1d(0, ArrayFormat::Float, 1).unwrap();
746 }
747
748 #[test]
749 #[should_panic]
750 fn fail_on_zero_size_widths() {
751 let _context = crate::quick_init().unwrap();
752
753 let _ = ArrayObject::new([0, 10, 20], ArrayFormat::Float, 1).unwrap();
754 }
755
756 #[test]
757 #[should_panic]
758 fn fail_cubemaps_with_unmatching_width_height() {
759 let _context = crate::quick_init().unwrap();
760
761 let mut descriptor = ArrayDescriptor::from_dims_format([2, 3, 6], ArrayFormat::Float);
762 descriptor.set_flags(ArrayObjectFlags::CUBEMAP);
763
764 let _ = ArrayObject::from_descriptor(&descriptor).unwrap();
765 }
766
767 #[test]
768 #[should_panic]
769 fn fail_cubemaps_with_non_six_depth() {
770 let _context = crate::quick_init().unwrap();
771
772 let mut descriptor = ArrayDescriptor::from_dims_format([4, 4, 5], ArrayFormat::Float);
773 descriptor.set_flags(ArrayObjectFlags::CUBEMAP);
774
775 let _ = ArrayObject::from_descriptor(&descriptor).unwrap();
776 }
777
778 #[test]
779 #[should_panic]
780 fn fail_cubemaps_with_non_six_multiple_depth() {
781 let _context = crate::quick_init().unwrap();
782
783 let mut descriptor = ArrayDescriptor::from_dims_format([4, 4, 10], ArrayFormat::Float);
784 descriptor.set_flags(ArrayObjectFlags::LAYERED | ArrayObjectFlags::CUBEMAP);
785
786 let _ = ArrayObject::from_descriptor(&descriptor).unwrap();
787 }
788
789 #[test]
790 #[should_panic]
791 fn fail_with_depth_without_height() {
792 let _context = crate::quick_init().unwrap();
793
794 let _ = ArrayObject::new([10, 0, 20], ArrayFormat::Float, 1).unwrap();
795 }
796
797 #[test]
798 #[should_panic]
799 fn fails_on_invalid_num_channels() {
800 let _context = crate::quick_init().unwrap();
801
802 let _ = ArrayObject::new([1, 2, 3], ArrayFormat::Float, 3).unwrap();
803 }
804}