citro3d/
buffer.rs

1//! Configure vertex buffer objects to be sent to the GPU for rendering.
2//!
3//! See the [`attrib`] module for details on how to describe the shape and type
4//! of the VBO data.
5
6use std::mem::MaybeUninit;
7
8use ctru::linear::LinearAllocator;
9
10use crate::Error;
11use crate::attrib;
12
13/// Vertex buffer info. This struct is used to describe the shape of the buffer
14/// data to be sent to the GPU for rendering.
15#[derive(Debug)]
16#[doc(alias = "C3D_BufInfo")]
17pub struct Info(pub(crate) citro3d_sys::C3D_BufInfo);
18
19/// A slice of buffer data. This borrows the buffer data and can be thought of
20/// as similar to `&[T]` obtained by slicing a `Vec<T>`.
21#[derive(Debug, Clone, Copy)]
22pub struct Slice<'buf> {
23    index: libc::c_int,
24    size: libc::c_int,
25    buf_info: &'buf Info,
26    // TODO: should we encapsulate the primitive here too, and require it when the
27    // slice is registered? Could there ever be a use case to draw different primitives
28    // using the same backing data???
29}
30
31impl Slice<'_> {
32    /// Get the index into the buffer for this slice.
33    pub fn index(&self) -> libc::c_int {
34        self.index
35    }
36
37    /// Get the length of the slice.
38    #[must_use]
39    pub fn len(&self) -> libc::c_int {
40        self.size
41    }
42
43    /// Return whether or not the slice has any elements.
44    pub fn is_empty(&self) -> bool {
45        self.len() <= 0
46    }
47
48    /// Get the buffer info this slice is associated with.
49    pub fn info(&self) -> &Info {
50        self.buf_info
51    }
52
53    /// Get an index buffer for this slice using the given indices.
54    ///
55    /// # Errors
56    ///
57    /// Returns an error if:
58    /// - any of the given indices are out of bounds.
59    /// - the given slice is too long for its length to fit in a `libc::c_int`.
60    pub fn index_buffer<I>(&self, indices: &[I]) -> Result<Indices<'_, I>, Error>
61    where
62        I: Index + Copy + Into<libc::c_int>,
63    {
64        if libc::c_int::try_from(indices.len()).is_err() {
65            return Err(Error::InvalidSize);
66        }
67
68        for &idx in indices {
69            let idx = idx.into();
70            let len = self.len();
71            if idx >= len {
72                return Err(Error::IndexOutOfBounds { idx, len });
73            }
74        }
75
76        Ok(unsafe { self.index_buffer_unchecked(indices) })
77    }
78
79    /// Get an index buffer for this slice using the given indices without
80    /// bounds checking.
81    ///
82    /// # Safety
83    ///
84    /// If any indices are outside this buffer it can cause an invalid access by the GPU
85    /// (this crashes citra).
86    pub unsafe fn index_buffer_unchecked<I: Index + Clone>(&self, indices: &[I]) -> Indices<'_, I> {
87        let mut buffer = Vec::with_capacity_in(indices.len(), LinearAllocator);
88        buffer.extend_from_slice(indices);
89        Indices {
90            buffer,
91            _slice: *self,
92        }
93    }
94}
95
96/// An index buffer for indexed drawing. See [`Slice::index_buffer`] to obtain one.
97pub struct Indices<'buf, I> {
98    pub(crate) buffer: Vec<I, LinearAllocator>,
99    _slice: Slice<'buf>,
100}
101
102/// A type that can be used as an index for indexed drawing.
103pub trait Index: crate::private::Sealed {
104    /// The data type of the index, as used by [`citro3d_sys::C3D_DrawElements`]'s `type_` parameter.
105    const TYPE: libc::c_int;
106}
107
108impl Index for u8 {
109    const TYPE: libc::c_int = citro3d_sys::C3D_UNSIGNED_BYTE as _;
110}
111
112impl Index for u16 {
113    const TYPE: libc::c_int = citro3d_sys::C3D_UNSIGNED_SHORT as _;
114}
115
116/// The geometric primitive to draw (i.e. what shapes the buffer data describes).
117#[repr(u16)]
118#[derive(Debug, Clone, Copy)]
119#[doc(alias = "GPU_Primitive_t")]
120pub enum Primitive {
121    /// Draw triangles (3 vertices per triangle).
122    Triangles = ctru_sys::GPU_TRIANGLES,
123    /// Draw a triangle strip (each vertex shared by 1-3 triangles).
124    TriangleStrip = ctru_sys::GPU_TRIANGLE_STRIP,
125    /// Draw a triangle fan (first vertex shared by all triangles).
126    TriangleFan = ctru_sys::GPU_TRIANGLE_FAN,
127    /// Geometry primitive. Can be used for more complex use cases like geometry
128    /// shaders that output custom primitives.
129    GeometryPrim = ctru_sys::GPU_GEOMETRY_PRIM,
130}
131
132impl Default for Info {
133    #[doc(alias = "BufInfo_Init")]
134    fn default() -> Self {
135        let mut info = MaybeUninit::zeroed();
136        let info = unsafe {
137            citro3d_sys::BufInfo_Init(info.as_mut_ptr());
138            info.assume_init()
139        };
140        Self(info)
141    }
142}
143
144impl Info {
145    /// Construct buffer info without any registered data.
146    pub fn new() -> Self {
147        Self::default()
148    }
149
150    pub(crate) fn copy_from(raw: *const citro3d_sys::C3D_BufInfo) -> Option<Self> {
151        if raw.is_null() {
152            None
153        } else {
154            // This is less efficient than returning a pointer or something, but it's
155            // safer since we don't know the lifetime of the pointee
156            Some(Self(unsafe { *raw }))
157        }
158    }
159
160    /// Register vertex buffer object data. The resulting [`Slice`] will have its
161    /// lifetime tied to both this [`Info`] and the passed-in VBO. `vbo_data` is
162    /// assumed to use one `T` per drawn primitive, and its layout is assumed to
163    /// match the given `attrib_info`
164    ///
165    /// # Errors
166    ///
167    /// Registering VBO data may fail:
168    ///
169    /// * if `vbo_data` is not allocated with the [`ctru::linear`] allocator
170    /// * if the maximum number (12) of VBOs are already registered
171    #[doc(alias = "BufInfo_Add")]
172    pub fn add<'this, 'vbo, 'idx, T>(
173        &'this mut self,
174        vbo_data: &'vbo [T],
175        attrib_info: &attrib::Info,
176    ) -> crate::Result<Slice<'idx>>
177    where
178        'this: 'idx,
179        'vbo: 'idx,
180    {
181        let stride = std::mem::size_of::<T>().try_into()?;
182
183        // SAFETY: the lifetime of the VBO data is encapsulated in the return value's
184        // 'vbo lifetime, and the pointer to &mut self.0 is used to access values
185        // in the BufInfo, not copied to be used later.
186        let res = unsafe {
187            citro3d_sys::BufInfo_Add(
188                &mut self.0,
189                vbo_data.as_ptr().cast(),
190                stride,
191                attrib_info.attr_count(),
192                attrib_info.permutation(),
193            )
194        };
195
196        // Error codes from <https://github.com/devkitPro/citro3d/blob/master/source/buffers.c#L11>
197        match res {
198            ..=-3 => Err(crate::Error::System(res)),
199            -2 => Err(crate::Error::InvalidMemoryLocation),
200            -1 => Err(crate::Error::TooManyBuffers),
201            _ => Ok(Slice {
202                index: res,
203                size: vbo_data.len().try_into()?,
204                buf_info: self,
205            }),
206        }
207    }
208}