fil_rustacuda/memory/
array.rs

1//! Routines for allocating and using CUDA Array Objects.
2//!
3//! Detailed documentation about allocating CUDA Arrays can be found in the
4//! [CUDA Driver API](https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__MEM.html#group__CUDA__MEM_1gc2322c70b38c2984536c90ed118bb1d7)
5
6use 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/// Describes the format used for a CUDA Array.
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum ArrayFormat {
18    /// Unsigned 8-bit integer
19    UnsignedInt8,
20    /// Unsigned 16-bit integer
21    UnsignedInt16,
22    /// Unsigned 32-bit integer
23    UnsignedInt32,
24    /// Signed 8-bit integer
25    SignedInt8,
26    /// Signed 16-bit integer
27    SignedInt16,
28    /// Signed 32-bit integer
29    SignedInt32,
30    /// Half-precision floating point number
31    Half,
32    /// Single-precision floating point number
33    Float,
34}
35
36impl ArrayFormat {
37    /// Creates ArrayFormat from the CUDA Driver API enum
38    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    /// Converts ArrayFormat to the CUDA Driver API enum
52    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    /// Flags which modify the behavior of CUDA array creation.
68    #[derive(Default)]
69    pub struct ArrayObjectFlags: c_uint {
70        /// Enables creation of layered CUDA arrays. When this flag is set, depth specifies the
71        /// number of layers, not the depth of a 3D array.
72        const LAYERED = cuda_driver_sys::CUDA_ARRAY3D_LAYERED;
73
74        /// Enables surface references to be bound to the CUDA array.
75        const SURFACE_LDST = cuda_driver_sys::CUDA_ARRAY3D_SURFACE_LDST;
76
77        /// Enables creation of cubemaps. If this flag is set, Width must be equal to Height, and
78        /// Depth must be six. If the `LAYERED` flag is also set, then Depth must be a multiple of
79        /// six.
80        const CUBEMAP = cuda_driver_sys::CUDA_ARRAY3D_CUBEMAP;
81
82        /// Indicates that the CUDA array will be used for texture gather. Texture gather can only
83        /// be performed on 2D CUDA arrays.
84        const TEXTURE_GATHER = cuda_driver_sys::CUDA_ARRAY3D_TEXTURE_GATHER;
85    }
86}
87
88impl ArrayObjectFlags {
89    /// Creates a default flags object with no flags set.
90    pub fn new() -> Self {
91        Self::default()
92    }
93}
94
95/// Describes a CUDA Array
96#[derive(Clone, Copy, Debug)]
97pub struct ArrayDescriptor {
98    desc: cuda_driver_sys::CUDA_ARRAY3D_DESCRIPTOR,
99}
100
101impl ArrayDescriptor {
102    /// Constructs an ArrayDescriptor from a CUDA Driver API Array Descriptor.
103    pub fn from_raw(desc: cuda_driver_sys::CUDA_ARRAY3D_DESCRIPTOR) -> Self {
104        Self { desc }
105    }
106
107    /// Constructs an ArrayDescriptor from dimensions, format, num_channels, and flags.
108    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    /// Creates a new ArrayDescriptor from a set of dimensions and format.
127    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    /// Returns the dimensions of the ArrayDescriptor
141    pub fn dims(&self) -> [usize; 3] {
142        [self.desc.Width, self.desc.Height, self.desc.Depth]
143    }
144
145    /// Sets the dimensions of the ArrayDescriptor
146    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    /// Returns the width of the ArrayDescripor
153    pub fn width(&self) -> usize {
154        self.desc.Width
155    }
156
157    /// Sets the width of the ArrayDescriptor
158    pub fn set_width(&mut self, width: usize) {
159        self.desc.Width = width;
160    }
161
162    /// Returns the height of the ArrayDescripor
163    pub fn height(&self) -> usize {
164        self.desc.Height
165    }
166
167    /// Sets the height of the ArrayDescriptor
168    pub fn set_height(&mut self, height: usize) {
169        self.desc.Height = height;
170    }
171
172    /// Returns the depth of the ArrayDescripor
173    pub fn depth(&self) -> usize {
174        self.desc.Depth
175    }
176
177    /// Sets the depth of the ArrayDescriptor
178    pub fn set_depth(&mut self, depth: usize) {
179        self.desc.Depth = depth;
180    }
181
182    /// Returns the format of the ArrayDescripor
183    pub fn format(&self) -> ArrayFormat {
184        ArrayFormat::from_raw(self.desc.Format)
185    }
186
187    /// Sets the format of the ArrayDescriptor
188    pub fn set_format(&mut self, format: ArrayFormat) {
189        self.desc.Format = format.to_raw();
190    }
191
192    /// Returns the number of channels in the ArrayDescriptor
193    pub fn num_channels(&self) -> c_uint {
194        self.desc.NumChannels
195    }
196
197    /// Sets the number of channels in the ArrayDescriptor
198    pub fn set_num_channels(&mut self, num_channels: c_uint) {
199        self.desc.NumChannels = num_channels;
200    }
201
202    /// Returns the flags of the ArrayDescriptor
203    pub fn flags(&self) -> ArrayObjectFlags {
204        ArrayObjectFlags::from_bits_truncate(self.desc.Flags)
205    }
206
207    /// Sets the flags of the ArrayDescriptor.
208    pub fn set_flags(&mut self, flags: ArrayObjectFlags) {
209        self.desc.Flags = flags.bits();
210    }
211}
212
213/// A CUDA Array. Can be bound to a texture or surface.
214pub struct ArrayObject {
215    handle: CUarray,
216}
217
218impl ArrayObject {
219    /// Constructs a generic ArrayObject from an `ArrayDescriptor`.
220    pub fn from_descriptor(descriptor: &ArrayDescriptor) -> CudaResult<Self> {
221        // We validate the descriptor up front in debug mode. This provides a good error message to
222        // the user when they get something wrong, but doesn't re-validate in release mode.
223        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            // Exhaustively check bounds of arrays
271            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    /// Allocates a new CUDA Array that is up to 3-dimensions.
389    ///
390    /// `dims` contains the extents of the array. `dims[0]` must be non-zero. `dims[1]` must be
391    /// non-zero if `dims[2]` is non-zero. The rank of the array is equal to the number of non-zero
392    /// `dims`.
393    ///
394    /// `format` determines the data-type of the array.
395    ///
396    /// `num_channels` determines the number of channels per array element (1, 2, or 4).
397    ///
398    /// ```
399    /// # use rustacuda::*;
400    /// # use std::error::Error;
401    /// # fn main() -> Result<(), Box<dyn Error>> {
402    /// # let _ctx = quick_init()?;
403    /// use rustacuda::memory::array::{ArrayObject, ArrayFormat};
404    ///
405    /// let one_dim_array = ArrayObject::new([10, 0, 0], ArrayFormat::Float, 1)?;
406    /// let two_dim_array = ArrayObject::new([10, 12, 0], ArrayFormat::Float, 1)?;
407    /// let three_dim_array = ArrayObject::new([10, 12, 14], ArrayFormat::Float, 1)?;
408    /// # Ok(())
409    /// # }
410    /// ```
411    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    /// Allocates a new 1D CUDA Array.
421    ///
422    /// `width` must be non-zero.
423    ///
424    /// `format` determines the data-type of the array.
425    ///
426    /// `num_channels` determines the number of channels per array element (1, 2, or 4).
427    ///
428    /// ```
429    /// # use rustacuda::*;
430    /// # use std::error::Error;
431    /// # fn main() -> Result<(), Box<dyn Error>> {
432    /// # let _ctx = quick_init()?;
433    /// use rustacuda::memory::array::{ArrayObject, ArrayFormat};
434    ///
435    /// // Allocates a 1D array of 10 single-precision, single-channel floating point values.
436    /// let one_dim_array = ArrayObject::new_1d(10, ArrayFormat::Float, 1)?;
437    /// # Ok(())
438    /// # }
439    /// ```
440    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    /// Allocates a new CUDA Array that is up to 2-dimensions.
450    ///
451    /// `dims` contains the extents of the array. `dims[0]` must be non-zero. The rank of the array
452    /// is equal to the number of non-zero `dims`.
453    ///
454    /// `format` determines the data-type of the array.
455    ///
456    /// `num_channels` determines the number of channels per array element (1, 2, or 4).
457    ///
458    /// ```
459    /// # use rustacuda::*;
460    /// # use std::error::Error;
461    /// # fn main() -> Result<(), Box<dyn Error>> {
462    /// # let _ctx = quick_init()?;
463    /// use rustacuda::memory::array::{ArrayObject, ArrayFormat};
464    ///
465    /// // Allocates an 8x24 array of single-precision, single-channel floating point values.
466    /// let one_dim_array = ArrayObject::new_2d([8, 24], ArrayFormat::Float, 1)?;
467    /// # Ok(())
468    /// # }
469    /// ```
470    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    /// Creates a new Layered 1D or 2D CUDA Array.
480    ///
481    /// `dims` contains the extents of the array. `dims[0]` must be non-zero. The rank of the array
482    /// is equivalent to the number of non-zero dimensions.
483    ///
484    /// `num_layers` determines the number of layers in the array.
485    ///
486    /// `format` determines the data-type of the array.
487    ///
488    /// `num_channels` determines the number of channels per array element (1, 2, or 4).
489    ///
490    /// ```
491    /// # use rustacuda::*;
492    /// # use std::error::Error;
493    /// # fn main() -> Result<(), Box<dyn Error>> {
494    /// # let _ctx = quick_init()?;
495    /// use rustacuda::memory::array::{ArrayObject, ArrayFormat};
496    ///
497    /// // Allocates a 7x8 array with 10 layers of single-precision, single-channel floating
498    /// // point values.
499    /// let layered_array = ArrayObject::new_layered([7, 8], 10, ArrayFormat::Float, 1)?;
500    /// # Ok(())
501    /// # }
502    /// ```
503    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    /// Creates a new Layered 1D CUDA Array.
518    ///
519    /// `width` must be non-zero.
520    ///
521    /// `num_layers` determines the number of layers in the array.
522    ///
523    /// `format` determines the data-type of the array.
524    ///
525    /// `num_channels` determines the number of channels per array element (1, 2, or 4).
526    ///
527    /// ```
528    /// # use rustacuda::*;
529    /// # use std::error::Error;
530    /// # fn main() -> Result<(), Box<dyn Error>> {
531    /// # let _ctx = quick_init()?;
532    /// use rustacuda::memory::array::{ArrayObject, ArrayFormat};
533    ///
534    /// // Allocates a 5-element array with 10 layers of single-precision, single-channel floating
535    /// // point values.
536    /// let layered_array = ArrayObject::new_layered_1d(5, 10, ArrayFormat::Float, 1)?;
537    /// # Ok(())
538    /// # }
539    /// ```
540    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    /// Creates a new Cubemap CUDA Array. The array is represented as 6 side x side 2D arrays.
555    ///
556    /// `side` is the length of an edge of the cube.
557    ///
558    /// `format` determines the data-type of the array.
559    ///
560    /// `num_channels` determines the number of channels per array element (1, 2, or 4).
561    ///
562    /// ```
563    /// # use rustacuda::*;
564    /// # use std::error::Error;
565    /// # fn main() -> Result<(), Box<dyn Error>> {
566    /// # let _ctx = quick_init()?;
567    /// use rustacuda::memory::array::{ArrayObject, ArrayFormat};
568    ///
569    /// // Allocates an 8x8 Cubemap array of single-precision, single-channel floating point
570    /// // numbers.
571    /// let layered_array = ArrayObject::new_cubemap(8, ArrayFormat::Float, 1)?;
572    ///
573    /// // All non-layered cubemap arrays have a depth of 6.
574    /// assert_eq!(6, layered_array.descriptor()?.depth());
575    /// # Ok(())
576    /// # }
577    /// ```
578    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    /// Creates a new Layered Cubemap CUDA Array. The array is represented as multiple 6 side x side
588    /// 2D arrays.
589    ///
590    /// `side` is the length of an edge of the cube.
591    ///
592    /// `num_layers` is the number of cubemaps in the array. The actual "depth" of the array is
593    /// `num_layers * 6`.
594    ///
595    /// `format` determines the data-type of the array.
596    ///
597    /// `num_channels` determines the number of channels per array element (1, 2, or 4).
598    ///
599    /// ```
600    /// # use rustacuda::*;
601    /// # use std::error::Error;
602    /// # fn main() -> Result<(), Box<dyn Error>> {
603    /// # let _ctx = quick_init()?;
604    /// use rustacuda::memory::array::{ArrayObject, ArrayFormat};
605    ///
606    /// // Allocates an 8x8 Layered Cubemap array of single-precision, single-channel floating point
607    /// // values with 5 layers.
608    /// let layered_array = ArrayObject::new_layered_cubemap(8, 5, ArrayFormat::Float, 1)?;
609    ///
610    /// // The depth of a layered cubemap array is equal to the number of layers * 6.
611    /// assert_eq!(30, layered_array.descriptor()?.depth());
612    /// # Ok(())
613    /// # }
614    /// ```
615    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    /// Gets the descriptor associated with this array.
630    pub fn descriptor(&self) -> CudaResult<ArrayDescriptor> {
631        // Use "zeroed" incase CUDA_ARRAY3D_DESCRIPTOR has uninitialized padding
632        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    /// Try to destroy an `ArrayObject`. Can fail - if it does, returns the CUDA error and the
644    /// un-destroyed array object
645    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}