citro3d/
uniform.rs

1//! Common definitions for binding uniforms to shaders. This is primarily
2//! done by implementing the [`Uniform`] trait for a given type.
3
4use std::ops::Range;
5
6use crate::math::{FVec4, IVec, Matrix4};
7use crate::{Frame, shader};
8
9/// The index of a uniform within a [`shader::Program`].
10#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
11pub struct Index(u8);
12
13impl Index {
14    pub fn inner(&self) -> u8 {
15        self.0
16    }
17}
18
19impl From<u8> for Index {
20    fn from(value: u8) -> Self {
21        Self(value)
22    }
23}
24
25impl From<Index> for i32 {
26    fn from(value: Index) -> Self {
27        value.0.into()
28    }
29}
30
31/// A uniform which may be bound as input to a shader program
32#[non_exhaustive]
33#[derive(Debug, PartialEq, Clone, Copy)]
34pub enum Uniform<'a> {
35    /// Single float uniform (`.fvec name`)
36    #[doc(alias = "C3D_FVUnifSet")]
37    Float(FVec4),
38    /// Two element float uniform (`.fvec name[2]`)
39    #[doc(alias = "C3D_FVUnifMtx2x4")]
40    Float2([FVec4; 2]),
41    /// Three element float uniform (`.fvec name[3]`)
42    #[doc(alias = "C3D_FVUnifMtx3x4")]
43    Float3([FVec4; 3]),
44    /// Matrix/4 element float uniform (`.fvec name[4]`)
45    #[doc(alias = "C3D_FVUnifMtx4x4")]
46    Float4(Matrix4),
47    /// Uniform of an arbitrary number of floats (`.fvec name[X]`)
48    FloatArray(&'a [FVec4]),
49    /// Uniform of an arbitrary number of Matrices/4x floats (`.fvec name[X]`)
50    Float4Array(&'a [Matrix4]),
51    /// Bool uniform (`.bool name`)
52    #[doc(alias = "C3D_BoolUnifSet")]
53    Bool(bool),
54    /// Integer uniform (`.ivec name`)
55    #[doc(alias = "C3D_IVUnifSet")]
56    Int(IVec),
57
58    #[cfg(feature = "glam")]
59    GlamFloatArray(&'a [glam::Vec4]),
60    #[cfg(feature = "glam")]
61    GlamMatrixArray(&'a [glam::Mat4]),
62}
63impl Uniform<'_> {
64    /// Get range of valid indexes for this uniform to bind to
65    pub fn index_range(&self) -> Range<Index> {
66        // these indexes are from the uniform table in the shader see: https://www.3dbrew.org/wiki/SHBIN#Uniform_Table_Entry
67        // the input registers then are excluded by libctru, see: https://github.com/devkitPro/libctru/blob/0da8705527f03b4b08ff7fee4dd1b7f28df37905/libctru/source/gpu/shbin.c#L93
68        match self {
69            Self::Float(_)
70            | Self::Float2(_)
71            | Self::Float3(_)
72            | Self::Float4(_)
73            | Self::FloatArray(_)
74            | Self::Float4Array(_) => Index(0)..Index(0x60),
75            Self::Int(_) => Index(0x60)..Index(0x64),
76            // this gap is intentional
77            Self::Bool(_) => Index(0x68)..Index(0x79),
78
79            #[cfg(feature = "glam")]
80            Self::GlamFloatArray(_) | Self::GlamMatrixArray(_) => Index(0)..Index(0x60),
81        }
82    }
83    /// Get length of uniform, i.e. how many registers it will write to
84    #[allow(clippy::len_without_is_empty)] // is_empty doesn't make sense here
85    pub fn len(&self) -> usize {
86        match self {
87            Self::Float(_) => 1,
88            Self::Float2(_) => 2,
89            Self::Float3(_) => 3,
90            Self::Float4(_) => 4,
91            Self::FloatArray(arr) => arr.len(),
92            Self::Float4Array(arr) => 4 * arr.len(),
93            Self::Bool(_) | Uniform::Int(_) => 1,
94
95            #[cfg(feature = "glam")]
96            Self::GlamFloatArray(arr) => arr.len(),
97            #[cfg(feature = "glam")]
98            Self::GlamMatrixArray(arr) => 4 * arr.len(),
99        }
100    }
101
102    /// Bind a uniform
103    ///
104    /// Note: `_frame` is here to ensure unique access to the global uniform buffers
105    /// otherwise we could race and/or violate aliasing
106    pub(crate) fn bind(self, _frame: &mut Frame, ty: shader::Type, index: Index) {
107        assert!(
108            self.index_range().contains(&index),
109            "tried to bind uniform to an invalid index (index: {:?}, valid range: {:?})",
110            index,
111            self.index_range(),
112        );
113        assert!(
114            self.index_range().end.0 as usize >= self.len() + index.0 as usize,
115            "tried to bind a uniform that would overflow the uniform buffer. index was {:?}, size was {} max is {:?}",
116            index,
117            self.len(),
118            self.index_range().end
119        );
120
121        unsafe {
122            match self {
123                Self::Bool(b) => citro3d_sys::C3D_BoolUnifSet(ty.into(), index.into(), b),
124                Self::Int(i) => citro3d_sys::C3D_IVUnifSet(
125                    ty.into(),
126                    index.into(),
127                    i.x() as i32,
128                    i.y() as i32,
129                    i.z() as i32,
130                    i.w() as i32,
131                ),
132                Self::Float(f) => set_float_uniforms([f], self.len(), ty, index),
133                Self::Float2(fs) => {
134                    set_float_uniforms(fs, self.len(), ty, index);
135                }
136                Self::Float3(fs) => set_float_uniforms(fs, self.len(), ty, index),
137                Self::Float4(m) => {
138                    set_float_uniforms(m.rows_wzyx(), self.len(), ty, index);
139                }
140                Self::FloatArray(arr) => {
141                    set_float_uniforms(arr.iter().copied(), self.len(), ty, index);
142                }
143                Self::Float4Array(arr) => {
144                    set_float_uniforms(
145                        arr.iter().flat_map(|m| m.rows_wzyx()),
146                        self.len(),
147                        ty,
148                        index,
149                    );
150                }
151                #[cfg(feature = "glam")]
152                Self::GlamFloatArray(arr) => {
153                    set_float_uniforms(arr.iter().copied(), self.len(), ty, index);
154                }
155                #[cfg(feature = "glam")]
156                Self::GlamMatrixArray(arr) => {
157                    set_float_uniforms(
158                        arr.iter()
159                            .copied()
160                            .flat_map(|m| [m.row(0), m.row(1), m.row(2), m.row(3)]),
161                        self.len(),
162                        ty,
163                        index,
164                    );
165                }
166            }
167        }
168    }
169}
170
171/// SAFETY:
172/// The provided fvecs and index should be vallidated to be entirely in-range before calling this function, and the length of the iterator should be equal to or less than the length provided.
173unsafe fn set_float_uniforms<I: IntoIterator<Item: Into<[f32; 4]>>>(
174    fs: I,
175    max_len: usize,
176    ty: shader::Type,
177    index: Index,
178) {
179    let vecs =
180        unsafe { citro3d_sys::C3D_FVUnifWritePtr(ty.into(), index.0 as i32, max_len as i32) };
181    let vecs = unsafe { core::slice::from_raw_parts_mut(vecs, max_len) };
182
183    for (i, f) in fs.into_iter().enumerate() {
184        let f: [f32; 4] = f.into();
185        vecs[i].__bindgen_anon_1.x = f[0];
186        vecs[i].__bindgen_anon_1.y = f[1];
187        vecs[i].__bindgen_anon_1.z = f[2];
188        vecs[i].__bindgen_anon_1.w = f[3];
189    }
190}
191
192impl From<Matrix4> for Uniform<'static> {
193    fn from(value: Matrix4) -> Self {
194        Self::Float4(value)
195    }
196}
197impl<'a> From<&'a [Matrix4]> for Uniform<'a> {
198    fn from(value: &'a [Matrix4]) -> Self {
199        Self::Float4Array(value)
200    }
201}
202impl From<[FVec4; 3]> for Uniform<'static> {
203    fn from(value: [FVec4; 3]) -> Self {
204        Self::Float3(value)
205    }
206}
207impl From<[FVec4; 2]> for Uniform<'static> {
208    fn from(value: [FVec4; 2]) -> Self {
209        Self::Float2(value)
210    }
211}
212impl<'a> From<&'a [FVec4]> for Uniform<'a> {
213    fn from(value: &'a [FVec4]) -> Self {
214        Self::FloatArray(value)
215    }
216}
217impl From<FVec4> for Uniform<'static> {
218    fn from(value: FVec4) -> Self {
219        Self::Float(value)
220    }
221}
222impl From<IVec> for Uniform<'static> {
223    fn from(value: IVec) -> Self {
224        Self::Int(value)
225    }
226}
227impl From<bool> for Uniform<'static> {
228    fn from(value: bool) -> Self {
229        Self::Bool(value)
230    }
231}
232impl From<&Matrix4> for Uniform<'static> {
233    fn from(value: &Matrix4) -> Self {
234        (*value).into()
235    }
236}
237
238#[cfg(feature = "glam")]
239impl From<glam::Vec4> for Uniform<'static> {
240    fn from(value: glam::Vec4) -> Self {
241        Self::Float(value.into())
242    }
243}
244
245#[cfg(feature = "glam")]
246impl<'a> From<&'a [glam::Vec4]> for Uniform<'a> {
247    fn from(value: &'a [glam::Vec4]) -> Self {
248        Self::GlamFloatArray(value)
249    }
250}
251
252#[cfg(feature = "glam")]
253impl<'a> From<&'a Vec<glam::Vec4>> for Uniform<'a> {
254    fn from(value: &'a Vec<glam::Vec4>) -> Self {
255        Self::GlamFloatArray(value.as_slice())
256    }
257}
258
259#[cfg(feature = "glam")]
260impl From<glam::Mat4> for Uniform<'static> {
261    fn from(value: glam::Mat4) -> Self {
262        Self::Float4(value.into())
263    }
264}
265
266#[cfg(feature = "glam")]
267impl<'a> From<&'a [glam::Mat4]> for Uniform<'a> {
268    fn from(value: &'a [glam::Mat4]) -> Self {
269        Self::GlamMatrixArray(value)
270    }
271}