Skip to main content

baracuda_runtime/
array.rs

1//! CUDA arrays + texture / surface objects (Runtime API).
2//!
3//! Mirrors [`baracuda_driver::array`]. An [`Array`] is an opaque
4//! on-device layout optimized for texture fetches. [`TextureObject`] /
5//! [`SurfaceObject`] wrap the CUDA 5+ object-based texture API; the
6//! legacy reference-based API is intentionally not wrapped.
7
8use core::ffi::c_void;
9use std::sync::Arc;
10
11use baracuda_cuda_sys::runtime::runtime;
12use baracuda_cuda_sys::runtime::types::{
13    cudaArray_t, cudaChannelFormatDesc, cudaChannelFormatKind, cudaExtent, cudaMipmappedArray_t,
14    cudaResourceDesc, cudaResourceViewDesc, cudaSurfaceObject_t, cudaTextureDesc,
15    cudaTextureObject_t,
16};
17
18use crate::error::{check, Result};
19
20/// Construct a `cudaChannelFormatDesc` with 1/2/4 channels of `bits` bits
21/// of the given `kind` (matches the `cudaCreateChannelDesc<T>()` helpers
22/// in CUDA headers).
23pub fn channel_desc(
24    bits_x: i32,
25    bits_y: i32,
26    bits_z: i32,
27    bits_w: i32,
28    kind: i32,
29) -> cudaChannelFormatDesc {
30    cudaChannelFormatDesc {
31        x: bits_x,
32        y: bits_y,
33        z: bits_z,
34        w: bits_w,
35        kind,
36    }
37}
38
39/// `cudaCreateChannelDesc<u8>` — one 8-bit unsigned channel.
40#[inline]
41pub fn channel_desc_u8() -> cudaChannelFormatDesc {
42    channel_desc(8, 0, 0, 0, cudaChannelFormatKind::UNSIGNED)
43}
44
45/// `cudaCreateChannelDesc<f32>` — one 32-bit float channel.
46#[inline]
47pub fn channel_desc_f32() -> cudaChannelFormatDesc {
48    channel_desc(32, 0, 0, 0, cudaChannelFormatKind::FLOAT)
49}
50
51/// A 2-D / 3-D CUDA array handle.
52#[derive(Clone)]
53pub struct Array {
54    inner: Arc<ArrayInner>,
55}
56
57struct ArrayInner {
58    handle: cudaArray_t,
59    width: usize,
60    height: usize,
61    depth: usize,
62    desc: cudaChannelFormatDesc,
63}
64
65unsafe impl Send for ArrayInner {}
66unsafe impl Sync for ArrayInner {}
67
68impl core::fmt::Debug for ArrayInner {
69    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
70        f.debug_struct("Array")
71            .field("width", &self.width)
72            .field("height", &self.height)
73            .field("depth", &self.depth)
74            .field("desc", &self.desc)
75            .finish_non_exhaustive()
76    }
77}
78
79impl core::fmt::Debug for Array {
80    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
81        self.inner.fmt(f)
82    }
83}
84
85impl Array {
86    /// Allocate a 2-D array `width × height` with the given channel
87    /// descriptor. `flags = 0` for the default layout.
88    pub fn new_2d(
89        desc: &cudaChannelFormatDesc,
90        width: usize,
91        height: usize,
92        flags: u32,
93    ) -> Result<Self> {
94        let r = runtime()?;
95        let cu = r.cuda_malloc_array()?;
96        let mut arr: cudaArray_t = core::ptr::null_mut();
97        check(unsafe {
98            cu(
99                &mut arr,
100                desc as *const cudaChannelFormatDesc as *const c_void,
101                width,
102                height,
103                flags,
104            )
105        })?;
106        Ok(Self {
107            inner: Arc::new(ArrayInner {
108                handle: arr,
109                width,
110                height,
111                depth: 0,
112                desc: *desc,
113            }),
114        })
115    }
116
117    /// Allocate a 3-D array with the given extent + descriptor.
118    /// `flags = 0` for the default.
119    pub fn new_3d(desc: &cudaChannelFormatDesc, extent: cudaExtent, flags: u32) -> Result<Self> {
120        let r = runtime()?;
121        let cu = r.cuda_malloc_3d_array()?;
122        let mut arr: cudaArray_t = core::ptr::null_mut();
123        check(unsafe {
124            cu(
125                &mut arr,
126                desc as *const cudaChannelFormatDesc as *const c_void,
127                &extent as *const cudaExtent as *const c_void,
128                flags,
129            )
130        })?;
131        Ok(Self {
132            inner: Arc::new(ArrayInner {
133                handle: arr,
134                width: extent.width,
135                height: extent.height,
136                depth: extent.depth,
137                desc: *desc,
138            }),
139        })
140    }
141
142    /// Wrap an already-allocated `cudaArray_t`.
143    ///
144    /// # Safety
145    ///
146    /// `handle` must be a live CUDA array. The wrapper frees it on drop.
147    pub unsafe fn from_raw(
148        handle: cudaArray_t,
149        desc: cudaChannelFormatDesc,
150        width: usize,
151        height: usize,
152        depth: usize,
153    ) -> Self {
154        Self {
155            inner: Arc::new(ArrayInner {
156                handle,
157                width,
158                height,
159                depth,
160                desc,
161            }),
162        }
163    }
164
165    /// Raw `cudaArray_t` handle. Use with care — owned by `self`.
166    #[inline]
167    pub fn as_raw(&self) -> cudaArray_t {
168        self.inner.handle
169    }
170
171    /// Width of the array, in elements.
172    #[inline]
173    pub fn width(&self) -> usize {
174        self.inner.width
175    }
176    /// Height of the array, in elements. Zero for a 1-D array.
177    #[inline]
178    pub fn height(&self) -> usize {
179        self.inner.height
180    }
181    /// Depth of the array, in elements. Zero for a 1- or 2-D array.
182    #[inline]
183    pub fn depth(&self) -> usize {
184        self.inner.depth
185    }
186    /// The channel-format descriptor captured at allocation time.
187    #[inline]
188    pub fn desc(&self) -> &cudaChannelFormatDesc {
189        &self.inner.desc
190    }
191
192    /// Query the runtime-reported channel desc + extent.
193    pub fn info(&self) -> Result<(cudaChannelFormatDesc, cudaExtent, u32)> {
194        let r = runtime()?;
195        let cu = r.cuda_array_get_info()?;
196        let mut desc = cudaChannelFormatDesc::default();
197        let mut extent = cudaExtent::default();
198        let mut flags: core::ffi::c_uint = 0;
199        check(unsafe {
200            cu(
201                &mut desc as *mut cudaChannelFormatDesc as *mut c_void,
202                &mut extent as *mut cudaExtent as *mut c_void,
203                &mut flags,
204                self.inner.handle,
205            )
206        })?;
207        Ok((desc, extent, flags))
208    }
209}
210
211impl Drop for ArrayInner {
212    fn drop(&mut self) {
213        if self.handle.is_null() {
214            return;
215        }
216        if let Ok(r) = runtime() {
217            if let Ok(cu) = r.cuda_free_array() {
218                let _ = unsafe { cu(self.handle) };
219            }
220        }
221    }
222}
223
224/// A mipmapped CUDA array.
225#[derive(Clone)]
226pub struct MipmappedArray {
227    inner: Arc<MipmappedArrayInner>,
228}
229
230struct MipmappedArrayInner {
231    handle: cudaMipmappedArray_t,
232}
233
234unsafe impl Send for MipmappedArrayInner {}
235unsafe impl Sync for MipmappedArrayInner {}
236
237impl core::fmt::Debug for MipmappedArray {
238    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
239        f.debug_struct("MipmappedArray")
240            .field("handle", &self.inner.handle)
241            .finish()
242    }
243}
244
245impl MipmappedArray {
246    /// Allocate a mipmapped array.
247    pub fn new(
248        desc: &cudaChannelFormatDesc,
249        extent: cudaExtent,
250        num_levels: u32,
251        flags: u32,
252    ) -> Result<Self> {
253        let r = runtime()?;
254        let cu = r.cuda_malloc_mipmapped_array()?;
255        let mut h: cudaMipmappedArray_t = core::ptr::null_mut();
256        check(unsafe {
257            cu(
258                &mut h,
259                desc as *const cudaChannelFormatDesc as *const c_void,
260                &extent as *const cudaExtent as *const c_void,
261                num_levels,
262                flags,
263            )
264        })?;
265        Ok(Self {
266            inner: Arc::new(MipmappedArrayInner { handle: h }),
267        })
268    }
269
270    /// Raw `cudaMipmappedArray_t` handle. Use with care — owned by `self`.
271    #[inline]
272    pub fn as_raw(&self) -> cudaMipmappedArray_t {
273        self.inner.handle
274    }
275
276    /// Fetch the `level`-th mipmap as a regular `cudaArray_t` (view; does
277    /// NOT free on drop — the parent mipmapped array owns it).
278    pub fn level(&self, level: u32) -> Result<cudaArray_t> {
279        let r = runtime()?;
280        let cu = r.cuda_get_mipmapped_array_level()?;
281        let mut out: cudaArray_t = core::ptr::null_mut();
282        check(unsafe { cu(&mut out, self.inner.handle, level) })?;
283        Ok(out)
284    }
285}
286
287impl Drop for MipmappedArrayInner {
288    fn drop(&mut self) {
289        if let Ok(r) = runtime() {
290            if let Ok(cu) = r.cuda_free_mipmapped_array() {
291                let _ = unsafe { cu(self.handle) };
292            }
293        }
294    }
295}
296
297/// A texture object — a read-only sampler bound to an array (or linear
298/// device memory). Pass `as_raw()` as a u64 kernel argument.
299pub struct TextureObject {
300    handle: cudaTextureObject_t,
301    // Keep the backing array alive for the lifetime of the texture.
302    _backing: Option<Array>,
303}
304
305impl core::fmt::Debug for TextureObject {
306    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
307        f.debug_struct("TextureObject")
308            .field("handle", &self.handle)
309            .finish()
310    }
311}
312
313impl TextureObject {
314    /// Create a texture object over an array with the given sampler + view.
315    pub fn new(
316        array: &Array,
317        tex_desc: &cudaTextureDesc,
318        view_desc: Option<&cudaResourceViewDesc>,
319    ) -> Result<Self> {
320        let res_desc = cudaResourceDesc::from_array(array.as_raw());
321        let r = runtime()?;
322        let cu = r.cuda_create_texture_object()?;
323        let mut obj: cudaTextureObject_t = 0;
324        let view_ptr = view_desc
325            .map(|v| v as *const cudaResourceViewDesc as *const c_void)
326            .unwrap_or(core::ptr::null());
327        check(unsafe {
328            cu(
329                &mut obj,
330                &res_desc as *const cudaResourceDesc as *const c_void,
331                tex_desc as *const cudaTextureDesc as *const c_void,
332                view_ptr,
333            )
334        })?;
335        Ok(Self {
336            handle: obj,
337            _backing: Some(array.clone()),
338        })
339    }
340
341    /// Create a texture over a raw `cudaResourceDesc` (e.g. a linear
342    /// memory slab).
343    ///
344    /// # Safety
345    ///
346    /// `res_desc`'s backing memory must outlive the returned texture.
347    pub unsafe fn from_resource(
348        res_desc: &cudaResourceDesc,
349        tex_desc: &cudaTextureDesc,
350        view_desc: Option<&cudaResourceViewDesc>,
351    ) -> Result<Self> { unsafe {
352        let r = runtime()?;
353        let cu = r.cuda_create_texture_object()?;
354        let mut obj: cudaTextureObject_t = 0;
355        let view_ptr = view_desc
356            .map(|v| v as *const cudaResourceViewDesc as *const c_void)
357            .unwrap_or(core::ptr::null());
358        check(cu(
359            &mut obj,
360            res_desc as *const cudaResourceDesc as *const c_void,
361            tex_desc as *const cudaTextureDesc as *const c_void,
362            view_ptr,
363        ))?;
364        Ok(Self {
365            handle: obj,
366            _backing: None,
367        })
368    }}
369
370    /// Raw `cudaTextureObject_t` handle. Use with care — owned by `self`.
371    #[inline]
372    pub fn as_raw(&self) -> cudaTextureObject_t {
373        self.handle
374    }
375
376    /// Query the resource descriptor the texture was created with.
377    pub fn resource_desc(&self) -> Result<cudaResourceDesc> {
378        let r = runtime()?;
379        let cu = r.cuda_get_texture_object_resource_desc()?;
380        let mut d = cudaResourceDesc::default();
381        check(unsafe { cu(&mut d as *mut cudaResourceDesc as *mut c_void, self.handle) })?;
382        Ok(d)
383    }
384
385    /// Query the sampler (filter/address/normalize) state.
386    pub fn texture_desc(&self) -> Result<cudaTextureDesc> {
387        let r = runtime()?;
388        let cu = r.cuda_get_texture_object_texture_desc()?;
389        let mut d = cudaTextureDesc::default();
390        check(unsafe { cu(&mut d as *mut cudaTextureDesc as *mut c_void, self.handle) })?;
391        Ok(d)
392    }
393}
394
395impl Drop for TextureObject {
396    fn drop(&mut self) {
397        if let Ok(r) = runtime() {
398            if let Ok(cu) = r.cuda_destroy_texture_object() {
399                let _ = unsafe { cu(self.handle) };
400            }
401        }
402    }
403}
404
405/// A surface object — writable array access from kernels.
406pub struct SurfaceObject {
407    handle: cudaSurfaceObject_t,
408    _backing: Option<Array>,
409}
410
411impl core::fmt::Debug for SurfaceObject {
412    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
413        f.debug_struct("SurfaceObject")
414            .field("handle", &self.handle)
415            .finish()
416    }
417}
418
419impl SurfaceObject {
420    /// Create a surface object over a CUDA array.
421    pub fn new(array: &Array) -> Result<Self> {
422        let res_desc = cudaResourceDesc::from_array(array.as_raw());
423        let r = runtime()?;
424        let cu = r.cuda_create_surface_object()?;
425        let mut obj: cudaSurfaceObject_t = 0;
426        check(unsafe {
427            cu(
428                &mut obj,
429                &res_desc as *const cudaResourceDesc as *const c_void,
430            )
431        })?;
432        Ok(Self {
433            handle: obj,
434            _backing: Some(array.clone()),
435        })
436    }
437
438    /// Raw `cudaSurfaceObject_t` handle. Use with care — owned by `self`.
439    #[inline]
440    pub fn as_raw(&self) -> cudaSurfaceObject_t {
441        self.handle
442    }
443
444    /// Query the resource descriptor the surface was created with.
445    pub fn resource_desc(&self) -> Result<cudaResourceDesc> {
446        let r = runtime()?;
447        let cu = r.cuda_get_surface_object_resource_desc()?;
448        let mut d = cudaResourceDesc::default();
449        check(unsafe { cu(&mut d as *mut cudaResourceDesc as *mut c_void, self.handle) })?;
450        Ok(d)
451    }
452}
453
454impl Drop for SurfaceObject {
455    fn drop(&mut self) {
456        if let Ok(r) = runtime() {
457            if let Ok(cu) = r.cuda_destroy_surface_object() {
458                let _ = unsafe { cu(self.handle) };
459            }
460        }
461    }
462}