1use std::ffi::c_void;
15
16use oxicuda_driver::ffi::{
17 CUDA_ARRAY_DESCRIPTOR, CUDA_ARRAY3D_DESCRIPTOR, CUDA_RESOURCE_DESC, CUDA_RESOURCE_VIEW_DESC,
18 CUDA_TEXTURE_DESC, CUaddress_mode, CUarray, CUarray_format, CUfilter_mode, CUmipmappedArray,
19 CUresourceViewFormat, CUresourcetype, CUsurfObject, CUtexObject, CudaResourceDescArray,
20 CudaResourceDescLinear, CudaResourceDescMipmap, CudaResourceDescPitch2d, CudaResourceDescRes,
21};
22use oxicuda_driver::loader::try_driver;
23use oxicuda_driver::{
24 CU_TRSF_NORMALIZED_COORDINATES, CU_TRSF_READ_AS_INTEGER, CU_TRSF_SRGB, CUDA_ARRAY3D_CUBEMAP,
25 CUDA_ARRAY3D_LAYERED, CUDA_ARRAY3D_SURFACE_LDST, CUDA_ARRAY3D_TEXTURE_GATHER,
26};
27
28use crate::error::{CudaRtError, CudaRtResult};
29use crate::memory::DevicePtr;
30use crate::stream::CudaStream;
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub enum ArrayFormat {
39 UnsignedInt8,
41 UnsignedInt16,
43 UnsignedInt32,
45 SignedInt8,
47 SignedInt16,
49 SignedInt32,
51 Half,
53 Float,
55}
56
57impl ArrayFormat {
58 #[must_use]
60 pub const fn as_cu_format(self) -> CUarray_format {
61 match self {
62 Self::UnsignedInt8 => CUarray_format::UnsignedInt8,
63 Self::UnsignedInt16 => CUarray_format::UnsignedInt16,
64 Self::UnsignedInt32 => CUarray_format::UnsignedInt32,
65 Self::SignedInt8 => CUarray_format::SignedInt8,
66 Self::SignedInt16 => CUarray_format::SignedInt16,
67 Self::SignedInt32 => CUarray_format::SignedInt32,
68 Self::Half => CUarray_format::Half,
69 Self::Float => CUarray_format::Float,
70 }
71 }
72
73 #[must_use]
75 pub const fn bytes_per_channel(self) -> usize {
76 match self {
77 Self::UnsignedInt8 | Self::SignedInt8 => 1,
78 Self::UnsignedInt16 | Self::SignedInt16 | Self::Half => 2,
79 Self::UnsignedInt32 | Self::SignedInt32 | Self::Float => 4,
80 }
81 }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
88pub enum AddressMode {
89 Wrap,
91 Clamp,
93 Mirror,
95 Border,
97}
98
99impl AddressMode {
100 #[must_use]
101 const fn as_cu(self) -> CUaddress_mode {
102 match self {
103 Self::Wrap => CUaddress_mode::Wrap,
104 Self::Clamp => CUaddress_mode::Clamp,
105 Self::Mirror => CUaddress_mode::Mirror,
106 Self::Border => CUaddress_mode::Border,
107 }
108 }
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
115pub enum FilterMode {
116 Point,
118 Linear,
120}
121
122impl FilterMode {
123 #[must_use]
124 const fn as_cu(self) -> CUfilter_mode {
125 match self {
126 Self::Point => CUfilter_mode::Point,
127 Self::Linear => CUfilter_mode::Linear,
128 }
129 }
130}
131
132pub struct CudaArray {
141 handle: CUarray,
142 width: usize,
143 height: usize,
144 format: ArrayFormat,
145 num_channels: u32,
146}
147
148impl CudaArray {
149 pub fn create_1d(width: usize, format: ArrayFormat, num_channels: u32) -> CudaRtResult<Self> {
159 let api = try_driver().map_err(|_| CudaRtError::DriverNotAvailable)?;
160 let create_fn = api.cu_array_create_v2.ok_or(CudaRtError::NotSupported)?;
161 let desc = CUDA_ARRAY_DESCRIPTOR {
162 width,
163 height: 0,
164 format: format.as_cu_format(),
165 num_channels,
166 };
167 let mut handle = CUarray::default();
168 let rc = unsafe { create_fn(&raw mut handle, &desc) };
170 if rc != 0 {
171 return Err(CudaRtError::from_code(rc).unwrap_or(CudaRtError::MemoryAllocation));
172 }
173 Ok(Self {
174 handle,
175 width,
176 height: 0,
177 format,
178 num_channels,
179 })
180 }
181
182 pub fn create_2d(
190 width: usize,
191 height: usize,
192 format: ArrayFormat,
193 num_channels: u32,
194 ) -> CudaRtResult<Self> {
195 let api = try_driver().map_err(|_| CudaRtError::DriverNotAvailable)?;
196 let create_fn = api.cu_array_create_v2.ok_or(CudaRtError::NotSupported)?;
197 let desc = CUDA_ARRAY_DESCRIPTOR {
198 width,
199 height,
200 format: format.as_cu_format(),
201 num_channels,
202 };
203 let mut handle = CUarray::default();
204 let rc = unsafe { create_fn(&raw mut handle, &desc) };
206 if rc != 0 {
207 return Err(CudaRtError::from_code(rc).unwrap_or(CudaRtError::MemoryAllocation));
208 }
209 Ok(Self {
210 handle,
211 width,
212 height,
213 format,
214 num_channels,
215 })
216 }
217
218 pub unsafe fn copy_from_host_raw(
234 &self,
235 src: *const c_void,
236 byte_count: usize,
237 ) -> CudaRtResult<()> {
238 let api = try_driver().map_err(|_| CudaRtError::DriverNotAvailable)?;
239 let f = api.cu_memcpy_htoa_v2.ok_or(CudaRtError::NotSupported)?;
240 let rc = unsafe { f(self.handle, 0, src, byte_count) };
242 if rc != 0 {
243 return Err(CudaRtError::from_code(rc).unwrap_or(CudaRtError::InvalidMemcpyDirection));
244 }
245 Ok(())
246 }
247
248 pub fn copy_from_host<T: Copy>(&self, src: &[T]) -> CudaRtResult<()> {
254 unsafe {
256 self.copy_from_host_raw(src.as_ptr().cast::<c_void>(), std::mem::size_of_val(src))
257 }
258 }
259
260 pub unsafe fn copy_to_host_raw(&self, dst: *mut c_void, byte_count: usize) -> CudaRtResult<()> {
272 let api = try_driver().map_err(|_| CudaRtError::DriverNotAvailable)?;
273 let f = api.cu_memcpy_atoh_v2.ok_or(CudaRtError::NotSupported)?;
274 let rc = unsafe { f(dst, self.handle, 0, byte_count) };
276 if rc != 0 {
277 return Err(CudaRtError::from_code(rc).unwrap_or(CudaRtError::InvalidMemcpyDirection));
278 }
279 Ok(())
280 }
281
282 pub fn copy_to_host<T: Copy>(&self, dst: &mut [T]) -> CudaRtResult<()> {
288 unsafe {
290 self.copy_to_host_raw(
291 dst.as_mut_ptr().cast::<c_void>(),
292 std::mem::size_of_val(dst),
293 )
294 }
295 }
296
297 pub unsafe fn copy_from_host_async_raw(
310 &self,
311 src: *const c_void,
312 byte_count: usize,
313 stream: CudaStream,
314 ) -> CudaRtResult<()> {
315 let api = try_driver().map_err(|_| CudaRtError::DriverNotAvailable)?;
316 let f = api
317 .cu_memcpy_htoa_async_v2
318 .ok_or(CudaRtError::NotSupported)?;
319 let rc = unsafe { f(self.handle, 0, src, byte_count, stream.raw()) };
321 if rc != 0 {
322 return Err(CudaRtError::from_code(rc).unwrap_or(CudaRtError::InvalidMemcpyDirection));
323 }
324 Ok(())
325 }
326
327 #[must_use]
329 pub fn raw(&self) -> CUarray {
330 self.handle
331 }
332
333 #[must_use]
335 pub const fn width(&self) -> usize {
336 self.width
337 }
338
339 #[must_use]
341 pub const fn height(&self) -> usize {
342 self.height
343 }
344
345 #[must_use]
347 pub const fn format(&self) -> ArrayFormat {
348 self.format
349 }
350
351 #[must_use]
353 pub const fn num_channels(&self) -> u32 {
354 self.num_channels
355 }
356}
357
358impl Drop for CudaArray {
359 fn drop(&mut self) {
360 if let Ok(api) = try_driver() {
361 if let Some(f) = api.cu_array_destroy {
362 unsafe { f(self.handle) };
364 }
365 }
366 }
367}
368
369#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
373pub struct Array3DFlags(pub u32);
374
375impl Array3DFlags {
376 pub const DEFAULT: Self = Self(0);
378 pub const LAYERED: Self = Self(CUDA_ARRAY3D_LAYERED);
380 pub const SURFACE_LDST: Self = Self(CUDA_ARRAY3D_SURFACE_LDST);
382 pub const CUBEMAP: Self = Self(CUDA_ARRAY3D_CUBEMAP);
384 pub const TEXTURE_GATHER: Self = Self(CUDA_ARRAY3D_TEXTURE_GATHER);
386
387 #[must_use]
389 pub const fn or(self, other: Self) -> Self {
390 Self(self.0 | other.0)
391 }
392}
393
394pub struct CudaArray3D {
396 handle: CUarray,
397 width: usize,
398 height: usize,
399 depth: usize,
400 format: ArrayFormat,
401 num_channels: u32,
402 flags: Array3DFlags,
403}
404
405impl CudaArray3D {
406 pub fn create(
417 width: usize,
418 height: usize,
419 depth: usize,
420 format: ArrayFormat,
421 num_channels: u32,
422 flags: Array3DFlags,
423 ) -> CudaRtResult<Self> {
424 let api = try_driver().map_err(|_| CudaRtError::DriverNotAvailable)?;
425 let create_fn = api.cu_array3d_create_v2.ok_or(CudaRtError::NotSupported)?;
426 let desc = CUDA_ARRAY3D_DESCRIPTOR {
427 width,
428 height,
429 depth,
430 format: format.as_cu_format(),
431 num_channels,
432 flags: flags.0,
433 };
434 let mut handle = CUarray::default();
435 let rc = unsafe { create_fn(&raw mut handle, &desc) };
437 if rc != 0 {
438 return Err(CudaRtError::from_code(rc).unwrap_or(CudaRtError::MemoryAllocation));
439 }
440 Ok(Self {
441 handle,
442 width,
443 height,
444 depth,
445 format,
446 num_channels,
447 flags,
448 })
449 }
450
451 #[must_use]
453 pub fn raw(&self) -> CUarray {
454 self.handle
455 }
456
457 #[must_use]
459 pub const fn width(&self) -> usize {
460 self.width
461 }
462 #[must_use]
464 pub const fn height(&self) -> usize {
465 self.height
466 }
467 #[must_use]
469 pub const fn depth(&self) -> usize {
470 self.depth
471 }
472 #[must_use]
474 pub const fn format(&self) -> ArrayFormat {
475 self.format
476 }
477 #[must_use]
479 pub const fn num_channels(&self) -> u32 {
480 self.num_channels
481 }
482 #[must_use]
484 pub const fn flags(&self) -> Array3DFlags {
485 self.flags
486 }
487}
488
489impl Drop for CudaArray3D {
490 fn drop(&mut self) {
491 if let Ok(api) = try_driver() {
492 if let Some(f) = api.cu_array_destroy {
493 unsafe { f(self.handle) };
495 }
496 }
497 }
498}
499
500#[derive(Clone, Copy)]
507pub enum ResourceDesc {
508 Array {
510 handle: CUarray,
512 },
513 MipmappedArray {
515 handle: CUmipmappedArray,
517 },
518 Linear {
520 dev_ptr: DevicePtr,
522 format: ArrayFormat,
524 num_channels: u32,
526 size_in_bytes: usize,
528 },
529 Pitch2d {
531 dev_ptr: DevicePtr,
533 format: ArrayFormat,
535 num_channels: u32,
537 width_in_elements: usize,
539 height: usize,
541 pitch_in_bytes: usize,
543 },
544}
545
546impl ResourceDesc {
547 #[must_use]
549 pub fn as_raw(&self) -> CUDA_RESOURCE_DESC {
550 match *self {
551 Self::Array { handle } => CUDA_RESOURCE_DESC {
552 res_type: CUresourcetype::Array,
553 res: CudaResourceDescRes {
554 array: CudaResourceDescArray { h_array: handle },
555 },
556 flags: 0,
557 },
558 Self::MipmappedArray { handle } => CUDA_RESOURCE_DESC {
559 res_type: CUresourcetype::MipmappedArray,
560 res: CudaResourceDescRes {
561 mipmap: CudaResourceDescMipmap {
562 h_mipmapped_array: handle,
563 },
564 },
565 flags: 0,
566 },
567 Self::Linear {
568 dev_ptr,
569 format,
570 num_channels,
571 size_in_bytes,
572 } => CUDA_RESOURCE_DESC {
573 res_type: CUresourcetype::Linear,
574 res: CudaResourceDescRes {
575 linear: CudaResourceDescLinear {
576 dev_ptr: dev_ptr.0,
577 format: format.as_cu_format(),
578 num_channels,
579 size_in_bytes,
580 },
581 },
582 flags: 0,
583 },
584 Self::Pitch2d {
585 dev_ptr,
586 format,
587 num_channels,
588 width_in_elements,
589 height,
590 pitch_in_bytes,
591 } => CUDA_RESOURCE_DESC {
592 res_type: CUresourcetype::Pitch2d,
593 res: CudaResourceDescRes {
594 pitch2d: CudaResourceDescPitch2d {
595 dev_ptr: dev_ptr.0,
596 format: format.as_cu_format(),
597 num_channels,
598 width_in_elements,
599 height,
600 pitch_in_bytes,
601 },
602 },
603 flags: 0,
604 },
605 }
606 }
607}
608
609#[derive(Clone, Copy)]
615pub struct TextureDesc {
616 pub address_u: AddressMode,
618 pub address_v: AddressMode,
620 pub address_w: AddressMode,
622 pub filter_mode: FilterMode,
624 pub normalized_coords: bool,
626 pub read_as_integer: bool,
628 pub srgb: bool,
630 pub max_anisotropy: u32,
632 pub mipmap_filter: FilterMode,
634 pub mipmap_bias: f32,
636 pub min_lod: f32,
638 pub max_lod: f32,
640 pub border_color: [f32; 4],
642}
643
644impl TextureDesc {
645 #[must_use]
652 pub const fn default_2d() -> Self {
653 Self {
654 address_u: AddressMode::Clamp,
655 address_v: AddressMode::Clamp,
656 address_w: AddressMode::Clamp,
657 filter_mode: FilterMode::Point,
658 normalized_coords: true,
659 read_as_integer: false,
660 srgb: false,
661 max_anisotropy: 1,
662 mipmap_filter: FilterMode::Point,
663 mipmap_bias: 0.0,
664 min_lod: 0.0,
665 max_lod: 0.0,
666 border_color: [0.0; 4],
667 }
668 }
669
670 #[must_use]
672 pub fn as_raw(&self) -> CUDA_TEXTURE_DESC {
673 let mut flags: u32 = 0;
674 if self.normalized_coords {
675 flags |= CU_TRSF_NORMALIZED_COORDINATES;
676 }
677 if self.read_as_integer {
678 flags |= CU_TRSF_READ_AS_INTEGER;
679 }
680 if self.srgb {
681 flags |= CU_TRSF_SRGB;
682 }
683 CUDA_TEXTURE_DESC {
684 address_mode: [
685 self.address_u.as_cu(),
686 self.address_v.as_cu(),
687 self.address_w.as_cu(),
688 ],
689 filter_mode: self.filter_mode.as_cu(),
690 flags,
691 max_anisotropy: self.max_anisotropy,
692 mipmap_filter_mode: self.mipmap_filter.as_cu(),
693 mipmap_level_bias: self.mipmap_bias,
694 min_mipmap_level_clamp: self.min_lod,
695 max_mipmap_level_clamp: self.max_lod,
696 border_color: self.border_color,
697 reserved: [0i32; 12],
698 }
699 }
700}
701
702#[derive(Clone, Copy)]
709pub struct ResourceViewDesc {
710 pub format: CUresourceViewFormat,
712 pub width: usize,
714 pub height: usize,
716 pub depth: usize,
718 pub first_mip_level: u32,
720 pub last_mip_level: u32,
722 pub first_layer: u32,
724 pub last_layer: u32,
726}
727
728impl ResourceViewDesc {
729 #[must_use]
731 pub fn as_raw(&self) -> CUDA_RESOURCE_VIEW_DESC {
732 CUDA_RESOURCE_VIEW_DESC {
733 format: self.format,
734 width: self.width,
735 height: self.height,
736 depth: self.depth,
737 first_mipmap_level: self.first_mip_level,
738 last_mipmap_level: self.last_mip_level,
739 first_layer: self.first_layer,
740 last_layer: self.last_layer,
741 reserved: [0u32; 16],
742 }
743 }
744}
745
746pub struct CudaTextureObject {
753 handle: CUtexObject,
754}
755
756impl CudaTextureObject {
757 pub fn create(
767 resource: &ResourceDesc,
768 texture: &TextureDesc,
769 view: Option<&ResourceViewDesc>,
770 ) -> CudaRtResult<Self> {
771 let api = try_driver().map_err(|_| CudaRtError::DriverNotAvailable)?;
772 let create_fn = api.cu_tex_object_create.ok_or(CudaRtError::NotSupported)?;
773
774 let raw_res = resource.as_raw();
775 let raw_tex = texture.as_raw();
776 let (raw_view_ptr, _raw_view_storage);
777 if let Some(v) = view {
778 _raw_view_storage = v.as_raw();
779 raw_view_ptr = &_raw_view_storage as *const CUDA_RESOURCE_VIEW_DESC;
780 } else {
781 _raw_view_storage = unsafe { std::mem::zeroed() };
782 raw_view_ptr = std::ptr::null();
783 }
784
785 let mut handle = CUtexObject::default();
786 let rc = unsafe { create_fn(&raw mut handle, &raw_res, &raw_tex, raw_view_ptr) };
788 if rc != 0 {
789 return Err(CudaRtError::from_code(rc).unwrap_or(CudaRtError::InvalidValue));
790 }
791 Ok(Self { handle })
792 }
793
794 #[must_use]
796 pub fn raw(&self) -> CUtexObject {
797 self.handle
798 }
799}
800
801impl Drop for CudaTextureObject {
802 fn drop(&mut self) {
803 if let Ok(api) = try_driver() {
804 if let Some(f) = api.cu_tex_object_destroy {
805 unsafe { f(self.handle) };
807 }
808 }
809 }
810}
811
812pub struct CudaSurfaceObject {
822 handle: CUsurfObject,
823}
824
825impl CudaSurfaceObject {
826 pub fn create(resource: &ResourceDesc) -> CudaRtResult<Self> {
836 let api = try_driver().map_err(|_| CudaRtError::DriverNotAvailable)?;
837 let create_fn = api.cu_surf_object_create.ok_or(CudaRtError::NotSupported)?;
838 let raw_res = resource.as_raw();
839 let mut handle = CUsurfObject::default();
840 let rc = unsafe { create_fn(&raw mut handle, &raw_res) };
842 if rc != 0 {
843 return Err(CudaRtError::from_code(rc).unwrap_or(CudaRtError::InvalidValue));
844 }
845 Ok(Self { handle })
846 }
847
848 #[must_use]
850 pub fn raw(&self) -> CUsurfObject {
851 self.handle
852 }
853}
854
855impl Drop for CudaSurfaceObject {
856 fn drop(&mut self) {
857 if let Ok(api) = try_driver() {
858 if let Some(f) = api.cu_surf_object_destroy {
859 unsafe { f(self.handle) };
861 }
862 }
863 }
864}
865
866#[cfg(test)]
869mod tests {
870 use super::*;
871
872 #[test]
873 fn array_format_byte_widths() {
874 assert_eq!(ArrayFormat::UnsignedInt8.bytes_per_channel(), 1);
875 assert_eq!(ArrayFormat::UnsignedInt16.bytes_per_channel(), 2);
876 assert_eq!(ArrayFormat::Half.bytes_per_channel(), 2);
877 assert_eq!(ArrayFormat::Float.bytes_per_channel(), 4);
878 assert_eq!(ArrayFormat::SignedInt32.bytes_per_channel(), 4);
879 }
880
881 #[test]
882 fn array_format_cu_round_trip() {
883 let fmt = ArrayFormat::Float;
884 assert!(matches!(fmt.as_cu_format(), CUarray_format::Float));
885 let fmt_int = ArrayFormat::SignedInt8;
886 assert!(matches!(fmt_int.as_cu_format(), CUarray_format::SignedInt8));
887 }
888
889 #[test]
890 fn texture_desc_default_flags() {
891 let desc = TextureDesc::default_2d();
892 let raw = desc.as_raw();
893 assert!(raw.flags & CU_TRSF_NORMALIZED_COORDINATES != 0);
895 assert!(raw.flags & CU_TRSF_READ_AS_INTEGER == 0);
897 assert!(raw.flags & CU_TRSF_SRGB == 0);
898 assert!(matches!(raw.filter_mode, CUfilter_mode::Point));
900 assert!(matches!(raw.address_mode[0], CUaddress_mode::Clamp));
902 assert!(matches!(raw.address_mode[1], CUaddress_mode::Clamp));
903 assert!(matches!(raw.address_mode[2], CUaddress_mode::Clamp));
904 }
905
906 #[test]
907 fn resource_desc_array_round_trip() {
908 let handle = CUarray::default();
909 let rd = ResourceDesc::Array { handle };
910 let raw = rd.as_raw();
911 assert!(matches!(raw.res_type, CUresourcetype::Array));
912 let arr = unsafe { raw.res.array };
914 assert!(arr.h_array.is_null()); }
916
917 #[test]
918 fn resource_desc_linear_round_trip() {
919 let rd = ResourceDesc::Linear {
920 dev_ptr: DevicePtr(0x1000),
921 format: ArrayFormat::Float,
922 num_channels: 4,
923 size_in_bytes: 1024,
924 };
925 let raw = rd.as_raw();
926 assert!(matches!(raw.res_type, CUresourcetype::Linear));
927 let lin = unsafe { raw.res.linear };
929 assert_eq!(lin.dev_ptr, 0x1000);
930 assert_eq!(lin.num_channels, 4);
931 assert_eq!(lin.size_in_bytes, 1024);
932 assert!(matches!(lin.format, CUarray_format::Float));
933 }
934
935 #[test]
936 fn cuda_array_create_no_gpu() {
937 match CudaArray::create_2d(64, 64, ArrayFormat::Float, 4) {
941 Ok(_) => { }
942 Err(CudaRtError::DriverNotAvailable)
943 | Err(CudaRtError::NotSupported)
944 | Err(CudaRtError::NoGpu)
945 | Err(CudaRtError::InitializationError)
946 | Err(CudaRtError::InvalidDevice)
947 | Err(CudaRtError::DeviceUninitialized) => { }
948 Err(e) => panic!("unexpected error: {e}"),
949 }
950 }
951
952 #[test]
953 fn cuda_texture_object_create_no_gpu() {
954 let handle = CUarray::default();
957 let res = ResourceDesc::Array { handle };
958 let tex = TextureDesc::default_2d();
959 match CudaTextureObject::create(&res, &tex, None) {
960 Ok(_) => {}
961 Err(CudaRtError::DriverNotAvailable)
962 | Err(CudaRtError::NotSupported)
963 | Err(CudaRtError::NoGpu)
964 | Err(CudaRtError::InitializationError)
965 | Err(CudaRtError::InvalidDevice)
966 | Err(CudaRtError::InvalidValue)
967 | Err(CudaRtError::DeviceUninitialized) => {}
968 Err(e) => panic!("unexpected error: {e}"),
969 }
970 }
971
972 #[test]
973 fn cuda_surface_object_create_no_gpu() {
974 let handle = CUarray::default();
977 let res = ResourceDesc::Array { handle };
978 match CudaSurfaceObject::create(&res) {
979 Ok(_) => {}
980 Err(CudaRtError::DriverNotAvailable)
981 | Err(CudaRtError::NotSupported)
982 | Err(CudaRtError::NoGpu)
983 | Err(CudaRtError::InitializationError)
984 | Err(CudaRtError::InvalidDevice)
985 | Err(CudaRtError::InvalidValue)
986 | Err(CudaRtError::DeviceUninitialized) => {}
987 Err(e) => panic!("unexpected error: {e}"),
988 }
989 }
990
991 #[test]
992 fn array_3d_flags_combine() {
993 let flags = Array3DFlags::LAYERED.or(Array3DFlags::SURFACE_LDST);
994 assert_eq!(flags.0, CUDA_ARRAY3D_LAYERED | CUDA_ARRAY3D_SURFACE_LDST);
995 }
996
997 #[test]
998 fn address_mode_variants_compile() {
999 let _ = AddressMode::Wrap.as_cu();
1000 let _ = AddressMode::Clamp.as_cu();
1001 let _ = AddressMode::Mirror.as_cu();
1002 let _ = AddressMode::Border.as_cu();
1003 }
1004}