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}