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::{RenderPass, 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 From<u8> for Index {
14    fn from(value: u8) -> Self {
15        Self(value)
16    }
17}
18
19impl From<Index> for i32 {
20    fn from(value: Index) -> Self {
21        value.0.into()
22    }
23}
24
25/// A uniform which may be bound as input to a shader program
26#[non_exhaustive]
27#[derive(Debug, PartialEq, Clone, Copy)]
28pub enum Uniform {
29    /// Single float uniform (`.fvec name`)
30    #[doc(alias = "C3D_FVUnifSet")]
31    Float(FVec4),
32    /// Two element float uniform (`.fvec name[2]`)
33    #[doc(alias = "C3D_FVUnifMtx2x4")]
34    Float2([FVec4; 2]),
35    /// Three element float uniform (`.fvec name [3]`)
36    #[doc(alias = "C3D_FVUnifMtx3x4")]
37    Float3([FVec4; 3]),
38    /// Matrix/4 element float uniform (`.fvec name[4]`)
39    #[doc(alias = "C3D_FVUnifMtx4x4")]
40    Float4(Matrix4),
41    /// Bool uniform (`.bool name`)
42    #[doc(alias = "C3D_BoolUnifSet")]
43    Bool(bool),
44    /// Integer uniform (`.ivec name`)
45    #[doc(alias = "C3D_IVUnifSet")]
46    Int(IVec),
47}
48impl Uniform {
49    /// Get range of valid indexes for this uniform to bind to
50    pub fn index_range(&self) -> Range<Index> {
51        // these indexes are from the uniform table in the shader see: https://www.3dbrew.org/wiki/SHBIN#Uniform_Table_Entry
52        // the input registers then are excluded by libctru, see: https://github.com/devkitPro/libctru/blob/0da8705527f03b4b08ff7fee4dd1b7f28df37905/libctru/source/gpu/shbin.c#L93
53        match self {
54            Self::Float(_) | Self::Float2(_) | Self::Float3(_) | Self::Float4(_) => {
55                Index(0)..Index(0x60)
56            }
57            Self::Int(_) => Index(0x60)..Index(0x64),
58            // this gap is intentional
59            Self::Bool(_) => Index(0x68)..Index(0x79),
60        }
61    }
62    /// Get length of uniform, i.e. how many registers it will write to
63    #[allow(clippy::len_without_is_empty)] // is_empty doesn't make sense here
64    pub fn len(&self) -> usize {
65        match self {
66            Self::Float(_) => 1,
67            Self::Float2(_) => 2,
68            Self::Float3(_) => 3,
69            Self::Float4(_) => 4,
70            Self::Bool(_) | Uniform::Int(_) => 1,
71        }
72    }
73
74    /// Bind a uniform
75    ///
76    /// Note: `_pass` is here to ensure unique access to the global uniform buffers
77    /// otherwise we could race and/or violate aliasing
78    pub(crate) fn bind(self, _pass: &mut RenderPass, ty: shader::Type, index: Index) {
79        assert!(
80            self.index_range().contains(&index),
81            "tried to bind uniform to an invalid index (index: {:?}, valid range: {:?})",
82            index,
83            self.index_range(),
84        );
85        assert!(
86            self.index_range().end.0 as usize >= self.len() + index.0 as usize,
87            "tried to bind a uniform that would overflow the uniform buffer. index was {:?}, size was {} max is {:?}",
88            index,
89            self.len(),
90            self.index_range().end
91        );
92
93        let set_fvs = |fs: &[FVec4]| {
94            for (off, f) in fs.iter().enumerate() {
95                unsafe {
96                    citro3d_sys::C3D_FVUnifSet(
97                        ty.into(),
98                        (index.0 as usize + off) as i32,
99                        f.x(),
100                        f.y(),
101                        f.z(),
102                        f.w(),
103                    );
104                }
105            }
106        };
107
108        match self {
109            Self::Bool(b) => unsafe {
110                citro3d_sys::C3D_BoolUnifSet(ty.into(), index.into(), b);
111            },
112            Self::Int(i) => unsafe {
113                citro3d_sys::C3D_IVUnifSet(
114                    ty.into(),
115                    index.into(),
116                    i.x() as i32,
117                    i.y() as i32,
118                    i.z() as i32,
119                    i.w() as i32,
120                );
121            },
122            Self::Float(f) => set_fvs(&[f]),
123            Self::Float2(fs) => {
124                set_fvs(&fs);
125            }
126            Self::Float3(fs) => set_fvs(&fs),
127            Self::Float4(m) => {
128                set_fvs(&m.rows_wzyx());
129            }
130        }
131    }
132}
133
134impl From<Matrix4> for Uniform {
135    fn from(value: Matrix4) -> Self {
136        Self::Float4(value)
137    }
138}
139impl From<[FVec4; 3]> for Uniform {
140    fn from(value: [FVec4; 3]) -> Self {
141        Self::Float3(value)
142    }
143}
144
145impl From<[FVec4; 2]> for Uniform {
146    fn from(value: [FVec4; 2]) -> Self {
147        Self::Float2(value)
148    }
149}
150impl From<FVec4> for Uniform {
151    fn from(value: FVec4) -> Self {
152        Self::Float(value)
153    }
154}
155impl From<IVec> for Uniform {
156    fn from(value: IVec) -> Self {
157        Self::Int(value)
158    }
159}
160impl From<bool> for Uniform {
161    fn from(value: bool) -> Self {
162        Self::Bool(value)
163    }
164}
165impl From<&Matrix4> for Uniform {
166    fn from(value: &Matrix4) -> Self {
167        (*value).into()
168    }
169}
170
171#[cfg(feature = "glam")]
172impl From<glam::Vec4> for Uniform {
173    fn from(value: glam::Vec4) -> Self {
174        Self::Float(value.into())
175    }
176}
177
178#[cfg(feature = "glam")]
179impl From<glam::Mat4> for Uniform {
180    fn from(value: glam::Mat4) -> Self {
181        Self::Float4(value.into())
182    }
183}