1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
//! Configure vertex buffer objects to be sent to the GPU for rendering.
//!
//! See the [`attrib`] module for details on how to describe the shape and type
//! of the VBO data.

use std::mem::MaybeUninit;

use crate::attrib;

/// Vertex buffer info. This struct is used to describe the shape of the buffer
/// data to be sent to the GPU for rendering.
#[derive(Debug)]
#[doc(alias = "C3D_BufInfo")]
pub struct Info(pub(crate) citro3d_sys::C3D_BufInfo);

/// A slice of buffer data. This borrows the buffer data and can be thought of
/// as similar to `&[T]` obtained by slicing a `Vec<T>`.
#[derive(Debug, Clone, Copy)]
pub struct Slice<'buf> {
    index: libc::c_int,
    size: libc::c_int,
    buf_info: &'buf Info,
    // TODO: should we encapsulate the primitive here too, and require it when the
    // slice is registered? Could there ever be a use case to draw different primitives
    // using the same backing data???
}

impl Slice<'_> {
    /// Get the index into the buffer for this slice.
    pub fn index(&self) -> libc::c_int {
        self.index
    }

    /// Get the length of the slice.
    #[must_use]
    pub fn len(&self) -> libc::c_int {
        self.size
    }

    /// Return whether or not the slice has any elements.
    pub fn is_empty(&self) -> bool {
        self.len() <= 0
    }

    /// Get the buffer info this slice is associated with.
    pub fn info(&self) -> &Info {
        self.buf_info
    }
}

/// The geometric primitive to draw (i.e. what shapes the buffer data describes).
#[repr(u16)]
#[derive(Debug, Clone, Copy)]
#[doc(alias = "GPU_Primitive_t")]
pub enum Primitive {
    /// Draw triangles (3 vertices per triangle).
    Triangles = ctru_sys::GPU_TRIANGLES,
    /// Draw a triangle strip (each vertex shared by 1-3 triangles).
    TriangleStrip = ctru_sys::GPU_TRIANGLE_STRIP,
    /// Draw a triangle fan (first vertex shared by all triangles).
    TriangleFan = ctru_sys::GPU_TRIANGLE_FAN,
    /// Geometry primitive. Can be used for more complex use cases like geometry
    /// shaders that output custom primitives.
    GeometryPrim = ctru_sys::GPU_GEOMETRY_PRIM,
}

impl Default for Info {
    #[doc(alias = "BufInfo_Init")]
    fn default() -> Self {
        let mut info = MaybeUninit::zeroed();
        let info = unsafe {
            citro3d_sys::BufInfo_Init(info.as_mut_ptr());
            info.assume_init()
        };
        Self(info)
    }
}

impl Info {
    /// Construct buffer info without any registered data.
    pub fn new() -> Self {
        Self::default()
    }

    pub(crate) fn copy_from(raw: *const citro3d_sys::C3D_BufInfo) -> Option<Self> {
        if raw.is_null() {
            None
        } else {
            // This is less efficient than returning a pointer or something, but it's
            // safer since we don't know the lifetime of the pointee
            Some(Self(unsafe { *raw }))
        }
    }

    /// Register vertex buffer object data. The resulting [`Slice`] will have its
    /// lifetime tied to both this [`Info`] and the passed-in VBO. `vbo_data` is
    /// assumed to use one `T` per drawn primitive, and its layout is assumed to
    /// match the given `attrib_info`
    ///
    /// # Errors
    ///
    /// Registering VBO data may fail:
    ///
    /// * if `vbo_data` is not allocated with the [`ctru::linear`] allocator
    /// * if the maximum number (12) of VBOs are already registered
    #[doc(alias = "BufInfo_Add")]
    pub fn add<'this, 'vbo, 'idx, T>(
        &'this mut self,
        vbo_data: &'vbo [T],
        attrib_info: &attrib::Info,
    ) -> crate::Result<Slice<'idx>>
    where
        'this: 'idx,
        'vbo: 'idx,
    {
        let stride = std::mem::size_of::<T>().try_into()?;

        // SAFETY: the lifetime of the VBO data is encapsulated in the return value's
        // 'vbo lifetime, and the pointer to &mut self.0 is used to access values
        // in the BufInfo, not copied to be used later.
        let res = unsafe {
            citro3d_sys::BufInfo_Add(
                &mut self.0,
                vbo_data.as_ptr().cast(),
                stride,
                attrib_info.attr_count(),
                attrib_info.permutation(),
            )
        };

        // Error codes from <https://github.com/devkitPro/citro3d/blob/master/source/buffers.c#L11>
        match res {
            ..=-3 => Err(crate::Error::System(res)),
            -2 => Err(crate::Error::InvalidMemoryLocation),
            -1 => Err(crate::Error::TooManyBuffers),
            _ => Ok(Slice {
                index: res,
                size: vbo_data.len().try_into()?,
                buf_info: self,
            }),
        }
    }
}