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::any::type_name;
7use std::ffi::c_void;
8use std::mem::MaybeUninit;
9use std::rc::Rc;
10
11use ctru::linear::LinearAllocator;
12
13use crate::Error;
14use crate::attrib;
15
16/// A buffer allocated in Linear memory.
17pub trait BufferData: 'static {
18    /// A pointer to the underlying data
19    fn buf_ptr(&self) -> *mut c_void;
20    /// The size (in bytes) of each element in the buffer
21    fn stride(&self) -> usize;
22    /// How many elements are in the buffer
23    fn buf_len(&self) -> usize;
24}
25
26impl<T: Sized + 'static> BufferData for Vec<T, LinearAllocator> {
27    fn buf_ptr(&self) -> *mut c_void {
28        self.as_ptr() as _
29    }
30
31    fn stride(&self) -> usize {
32        std::mem::size_of::<T>()
33    }
34
35    fn buf_len(&self) -> usize {
36        self.len()
37    }
38}
39
40impl<T: Sized + 'static> BufferData for Box<[T], LinearAllocator> {
41    fn buf_ptr(&self) -> *mut c_void {
42        self.as_ref() as *const _ as _
43    }
44
45    fn stride(&self) -> usize {
46        std::mem::size_of::<T>()
47    }
48
49    fn buf_len(&self) -> usize {
50        self.len()
51    }
52}
53
54impl<T: Sized + 'static> BufferData for Rc<[T], LinearAllocator> {
55    fn buf_ptr(&self) -> *mut c_void {
56        self.as_ref() as *const _ as _
57    }
58
59    fn stride(&self) -> usize {
60        std::mem::size_of::<T>()
61    }
62
63    fn buf_len(&self) -> usize {
64        self.len()
65    }
66}
67
68/// A handle to a VBO buffer in linear memory, to be used with [`Info`].
69/// This handle is reference counted so can be cheaply cloned and used
70/// with mutliple [`Info`] instances without duplicating memory.
71#[derive(Clone)]
72pub struct Buffer {
73    _data: Rc<dyn BufferData>,
74
75    // These fields could just be dynamically fetched since the buffer
76    // is dyn BufferVec, or we can spend 12 extra bytes per buffer to
77    // avoid some indirection each draw call.
78    data_ptr: *const c_void,
79    stride: isize,
80    len: usize,
81}
82
83impl Buffer {
84    /// Allocate a new `Buffer` in Linear memory and copy `data` into it.
85    /// Each element of `data` should correspond to data for a single vertex.
86    ///
87    /// If you already have an owned [`Vec`] in Linear memory then
88    /// [`Buffer::new_in_linear`] should be preferred to take ownership
89    /// of that allocation instead of reallocating and copying.
90    pub fn new<T: Sized + Copy + 'static>(data: &[T]) -> Buffer {
91        let mut linear_data = Vec::with_capacity_in(data.len(), LinearAllocator);
92        linear_data.extend_from_slice(data);
93
94        Buffer {
95            data_ptr: linear_data.as_ptr() as _,
96            len: linear_data.len(),
97            stride: linear_data
98                .stride()
99                .try_into()
100                .map_err(|_| format!("{} is too large to be used in a buffer.", type_name::<T>()))
101                .unwrap(),
102            _data: Rc::new(linear_data),
103        }
104    }
105
106    /// Allocate a new `Buffer` in Linear memory and copy `data` into it.
107    /// The `stride` should correspond to the number of bytes in data
108    /// per single vertex.
109    ///
110    /// If you already have an owned [`Vec`] in Linear memory then
111    /// [`Buffer::new_in_linear_with_stride`] should be preferred to take ownership
112    /// of that allocation instead of reallocating and copying.
113    ///
114    /// # Errors
115    /// * If the length of `data` is not a mutliple of `stride`
116    pub fn new_with_stride(data: &[u8], stride: usize) -> Option<Buffer> {
117        if !data.len().is_multiple_of(stride) {
118            return None;
119        }
120
121        let mut linear_data = Vec::with_capacity_in(data.len(), LinearAllocator);
122        linear_data.extend_from_slice(data);
123
124        Some(Buffer {
125            data_ptr: linear_data.as_ptr() as _,
126            len: linear_data.len() / stride,
127            stride: stride as isize,
128            _data: Rc::new(linear_data),
129        })
130    }
131
132    /// Convert an existing buffer allocated in Linear memory to a `Buffer` to be used
133    /// with [`Info`]. Each element in `data` should correspond with data for
134    /// a single vertex.
135    pub fn new_in_linear<B: BufferData>(data: B) -> Buffer {
136        Buffer {
137            data_ptr: data.buf_ptr() as _,
138            stride: data
139                .stride()
140                .try_into()
141                .map_err(|_| format!("{}'s buffer elements are too large.", type_name::<B>()))
142                .unwrap(),
143            len: data.buf_len(),
144            _data: Rc::new(data),
145        }
146    }
147
148    /// Convert an existing buffer of unstructured data allocated in Linear memory
149    /// e.g. `Vec<u8, LinearAllocator>` to a `Buffer` to be used with [`Info`].
150    /// Each element in `data` should correspond with data for a single vertex.
151    ///
152    /// # Errors
153    /// * If the length of `data` is not a mutliple of `stride`
154    pub fn new_in_linear_with_stride(data: impl BufferData, stride: usize) -> Option<Buffer> {
155        if !data.buf_len().is_multiple_of(stride) {
156            return None;
157        }
158
159        Some(Buffer {
160            data_ptr: data.buf_ptr() as _,
161            stride: stride
162                .try_into()
163                .map_err(|_| format!("{stride} is too large for a buffer element."))
164                .unwrap(),
165            len: data.buf_len() / stride,
166            _data: Rc::new(data),
167        })
168    }
169
170    pub fn as_ptr(&self) -> *const c_void {
171        self.data_ptr
172    }
173
174    pub fn stride(&self) -> isize {
175        self.stride
176    }
177
178    pub fn len(&self) -> usize {
179        self.len
180    }
181
182    pub fn is_empty(&self) -> bool {
183        self.len == 0
184    }
185}
186
187/// Vertex buffer info. This struct is used to describe the shape of the buffer
188/// data to be sent to the GPU for rendering.
189#[doc(alias = "C3D_BufInfo")]
190#[derive(Clone)]
191pub struct Info {
192    info: citro3d_sys::C3D_BufInfo,
193    buffers: Vec<Buffer>,
194}
195
196/// A type that can be used as an index for indexed drawing.
197pub trait Index {
198    /// The data type of the index, as used by [`citro3d_sys::C3D_DrawElements`]'s `type_` parameter.
199    const TYPE: libc::c_int;
200}
201
202impl Index for u8 {
203    const TYPE: libc::c_int = citro3d_sys::C3D_UNSIGNED_BYTE as _;
204}
205
206impl Index for u16 {
207    const TYPE: libc::c_int = citro3d_sys::C3D_UNSIGNED_SHORT as _;
208}
209
210/// The geometric primitive to draw (i.e. what shapes the buffer data describes).
211#[repr(u16)]
212#[derive(Debug, Clone, Copy)]
213#[doc(alias = "GPU_Primitive_t")]
214pub enum Primitive {
215    /// Draw triangles (3 vertices per triangle).
216    Triangles = ctru_sys::GPU_TRIANGLES,
217    /// Draw a triangle strip (each vertex shared by 1-3 triangles).
218    TriangleStrip = ctru_sys::GPU_TRIANGLE_STRIP,
219    /// Draw a triangle fan (first vertex shared by all triangles).
220    TriangleFan = ctru_sys::GPU_TRIANGLE_FAN,
221    /// Geometry primitive. Can be used for more complex use cases like geometry
222    /// shaders that output custom primitives.
223    GeometryPrim = ctru_sys::GPU_GEOMETRY_PRIM,
224}
225
226impl Default for Info {
227    #[doc(alias = "BufInfo_Init")]
228    fn default() -> Self {
229        let mut info = MaybeUninit::zeroed();
230        let info = unsafe {
231            citro3d_sys::BufInfo_Init(info.as_mut_ptr());
232            info.assume_init()
233        };
234        Self {
235            info,
236            buffers: Vec::new(),
237        }
238    }
239}
240
241impl Info {
242    pub fn as_raw(&self) -> *mut citro3d_sys::C3D_BufInfo {
243        &self.info as *const _ as _
244    }
245
246    /// Construct buffer info without any registered data.
247    pub fn new() -> Self {
248        Self::default()
249    }
250
251    pub fn len(&self) -> u16 {
252        self.buffers.first().map(|b| b.len() as _).unwrap_or(0)
253    }
254
255    pub fn is_empty(&self) -> bool {
256        self.len() == 0
257    }
258
259    /// Register vertex buffer object data with this [`Info`].
260    /// `vbo_buffer` is assumed to use one `T` per drawn primitive,
261    /// and its layout is assumed to match the given `permutation`.
262    ///
263    /// # Errors
264    ///
265    /// Registering VBO data may fail:
266    ///
267    /// * if `vbo_data` is (somehow) not allocated with the [`ctru::linear`] allocator
268    /// * if the maximum number (12) of VBOs are already registered
269    #[doc(alias = "BufInfo_Add")]
270    pub fn add<'this, 'idx>(
271        &'this mut self,
272        vbo_buffer: Buffer,
273        permutation: attrib::Permutation,
274    ) -> Result<(), Error>
275    where
276        'this: 'idx,
277    {
278        // SAFETY:
279        // * The lifetime of the VBO data is extended by the `Buffer` copy that is
280        // stored in `self.buffers` which reference counts the buffer allocation
281        // * The pointer to &mut self.0 is used to access values
282        // in the BufInfo, not copied to be used later.
283        let res = unsafe {
284            citro3d_sys::BufInfo_Add(
285                &mut self.info,
286                vbo_buffer.as_ptr().cast(),
287                vbo_buffer.stride(),
288                permutation.attrib_count as _,
289                permutation.permutation,
290            )
291        };
292
293        // Error codes from <https://github.com/devkitPro/citro3d/blob/master/source/buffers.c#L11>
294        match res {
295            ..=-3 => Err(crate::Error::System(res)),
296            -2 => Err(crate::Error::InvalidMemoryLocation),
297            -1 => Err(crate::Error::TooManyBuffers),
298            _ => {
299                self.buffers.push(vbo_buffer);
300                Ok(())
301            }
302        }
303    }
304}