diff --git a/citro3d/Cargo.toml b/citro3d/Cargo.toml index 4147662..705d601 100644 --- a/citro3d/Cargo.toml +++ b/citro3d/Cargo.toml @@ -6,6 +6,7 @@ version = "0.1.0" edition = "2021" [dependencies] +glam = { version = "0.24.2", optional = true } approx = { version = "0.5.1", optional = true } bitflags = "1.3.2" bytemuck = { version = "1.10.0", features = ["extern_crate_std"] } @@ -17,9 +18,11 @@ document-features = "0.2.7" libc = "0.2.125" [features] -default = [] +default = ["glam"] ## Enable this feature to use the `approx` crate for comparing vectors and matrices. approx = ["dep:approx"] +## Enable for glam support in uniforms +glam = ["dep:glam"] [dev-dependencies] test-runner = { git = "https://github.com/rust3ds/test-runner.git" } diff --git a/citro3d/src/lib.rs b/citro3d/src/lib.rs index dd02280..ba93984 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -223,8 +223,8 @@ impl Instance { /// let mtx = Matrix::identity(); /// instance.bind_vertex_uniform(idx, &mtx); /// ``` - pub fn bind_vertex_uniform(&mut self, index: uniform::Index, uniform: impl Uniform) { - uniform.bind(self, shader::Type::Vertex, index); + pub fn bind_vertex_uniform(&mut self, index: uniform::Index, uniform: impl Into) { + uniform.into().bind(self, shader::Type::Vertex, index); } /// Bind a uniform to the given `index` in the geometry shader for the next draw call. @@ -241,8 +241,8 @@ impl Instance { /// let mtx = Matrix::identity(); /// instance.bind_geometry_uniform(idx, &mtx); /// ``` - pub fn bind_geometry_uniform(&mut self, index: uniform::Index, uniform: impl Uniform) { - uniform.bind(self, shader::Type::Geometry, index); + pub fn bind_geometry_uniform(&mut self, index: uniform::Index, uniform: impl Into) { + uniform.into().bind(self, shader::Type::Geometry, index); } /// Retrieve the [`TexEnv`] for the given stage, initializing it first if necessary. diff --git a/citro3d/src/math.rs b/citro3d/src/math.rs index 949bb42..ec2ebfb 100644 --- a/citro3d/src/math.rs +++ b/citro3d/src/math.rs @@ -9,16 +9,56 @@ mod ops; mod projection; pub use fvec::{FVec, FVec3, FVec4}; -pub use matrix::{Matrix, Matrix3, Matrix4}; +pub use matrix::Matrix4; pub use projection::{ AspectRatio, ClipPlanes, CoordinateOrientation, Orthographic, Perspective, Projection, ScreenOrientation, StereoDisplacement, }; /// A 4-vector of `u8`s. +/// +/// # Layout +/// Uses the PICA layout of WZYX #[doc(alias = "C3D_IVec")] +#[repr(transparent)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct IVec(citro3d_sys::C3D_IVec); +impl IVec { + pub fn new(x: u8, y: u8, z: u8, w: u8) -> Self { + Self(unsafe { citro3d_sys::IVec_Pack(x, y, z, w) }) + } + pub fn as_raw(&self) -> &citro3d_sys::C3D_IVec { + &self.0 + } + pub fn x(self) -> u8 { + self.0 as u8 + } + pub fn y(self) -> u8 { + (self.0 >> 8) as u8 + } + pub fn z(self) -> u8 { + (self.0 >> 16) as u8 + } + pub fn w(self) -> u8 { + (self.0 >> 24) as u8 + } +} + /// A quaternion, internally represented the same way as [`FVec`]. #[doc(alias = "C3D_FQuat")] pub struct FQuat(citro3d_sys::C3D_FQuat); + +#[cfg(test)] +mod tests { + use super::IVec; + + #[test] + fn ivec_getters_work() { + let iv = IVec::new(1, 2, 3, 4); + assert_eq!(iv.x(), 1); + assert_eq!(iv.y(), 2); + assert_eq!(iv.z(), 3); + assert_eq!(iv.w(), 4); + } +} diff --git a/citro3d/src/math/fvec.rs b/citro3d/src/math/fvec.rs index 37e66c7..8f97f19 100644 --- a/citro3d/src/math/fvec.rs +++ b/citro3d/src/math/fvec.rs @@ -3,8 +3,15 @@ use std::fmt; /// A vector of `f32`s. +/// +/// # Layout +/// Note that this matches the PICA layout so is actually WZYX, this means using it +/// in vertex data as an attribute it will be reversed +/// +/// It is guaranteed to have the same layout as [`citro3d_sys::C3D_FVec`] in memory #[derive(Clone, Copy)] #[doc(alias = "C3D_FVec")] +#[repr(transparent)] pub struct FVec(pub(crate) citro3d_sys::C3D_FVec); /// A 3-vector of `f32`s. @@ -48,6 +55,11 @@ impl FVec4 { unsafe { self.0.__bindgen_anon_1.w } } + /// Wrap a raw [`citro3d_sys::C3D_FVec`] + pub fn from_raw(raw: citro3d_sys::C3D_FVec) -> Self { + Self(raw) + } + /// Create a new [`FVec4`] from its components. /// /// # Example @@ -243,6 +255,32 @@ impl FVec3 { } } +#[cfg(feature = "glam")] +impl From for FVec4 { + fn from(value: glam::Vec4) -> Self { + Self::new(value.x, value.y, value.z, value.w) + } +} +#[cfg(feature = "glam")] +impl From for FVec3 { + fn from(value: glam::Vec3) -> Self { + Self::new(value.x, value.y, value.z) + } +} +#[cfg(feature = "glam")] +impl From for glam::Vec4 { + fn from(value: FVec4) -> Self { + glam::Vec4::new(value.x(), value.y(), value.z(), value.w()) + } +} + +#[cfg(feature = "glam")] +impl From for glam::Vec3 { + fn from(value: FVec3) -> Self { + glam::Vec3::new(value.x(), value.y(), value.z()) + } +} + #[cfg(test)] mod tests { use approx::assert_abs_diff_eq; diff --git a/citro3d/src/math/matrix.rs b/citro3d/src/math/matrix.rs index e07f777..aac8612 100644 --- a/citro3d/src/math/matrix.rs +++ b/citro3d/src/math/matrix.rs @@ -1,84 +1,60 @@ use std::mem::MaybeUninit; -pub use private::Matrix; - -use super::{CoordinateOrientation, FVec3}; - -mod private { - use std::fmt; - - /// An `M`x`N` row-major matrix of `f32`s. - #[doc(alias = "C3D_Mtx")] - #[derive(Clone)] - pub struct Matrix(citro3d_sys::C3D_Mtx); - - impl Matrix { - const ROW_SIZE: () = assert!(M == 3 || M == 4); - const COLUMN_SIZE: () = assert!(N > 0 && N <= 4); - - // This constructor validates, at compile time, that the - // constructed matrix is 3xN or 4xN matrix, where 0 < N ≤ 4. - // We put this struct in a submodule to enforce that nothing creates - // a Matrix without calling this constructor. - #[allow(clippy::let_unit_value)] - pub(crate) fn new(value: citro3d_sys::C3D_Mtx) -> Self { - let () = Self::ROW_SIZE; - let () = Self::COLUMN_SIZE; - Self(value) - } - - pub(crate) fn as_raw(&self) -> *const citro3d_sys::C3D_Mtx { - &self.0 - } +use super::{CoordinateOrientation, FVec3, FVec4}; - pub(crate) fn into_raw(self) -> citro3d_sys::C3D_Mtx { - self.0 - } +/// A 4x4 row-major matrix of `f32`s. +/// +/// # Layout details +/// Rows are actually stored as WZYX in memory. There are helper functions +/// for accessing the rows in XYZW form. The `Debug` implementation prints +/// the shows in WZYX form +/// +/// It is also guaranteed to have the same layout as [`citro3d_sys::C3D_Mtx`] +#[doc(alias = "C3D_Mtx")] +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct Matrix4(citro3d_sys::C3D_Mtx); - pub(crate) fn as_mut(&mut self) -> *mut citro3d_sys::C3D_Mtx { - &mut self.0 - } +impl Matrix4 { + /// Construct a Matrix4 from the cells + /// + /// # Note + /// This expects rows to be in WZYX order + pub fn from_cells_wzyx(cells: [f32; 16]) -> Self { + Self(citro3d_sys::C3D_Mtx { m: cells }) + } + /// Construct a Matrix4 from its rows + pub fn from_rows(rows: [FVec4; 4]) -> Self { + Self(citro3d_sys::C3D_Mtx { + r: rows.map(|r| r.0), + }) + } + /// Create a new matrix from a raw citro3d_sys one + pub fn from_raw(value: citro3d_sys::C3D_Mtx) -> Self { + Self(value) + } - /// Trim the matrix down to only the rows and columns we care about, - /// since the inner representation is always 4x4. - /// - /// NOTE: this probably shouldn't be used in hot paths since it copies - /// the underlying storage. For some use cases slicing might be better, - /// although the underlying slice would always contain extra values for - /// matrices smaller than 4x4. - pub(crate) fn as_rows(&self) -> [[f32; N]; M] { - let rows = unsafe { self.0.r }.map(|row| -> [f32; N] { - // Rows are stored in WZYX order, so we slice from back to front. - // UNWRAP: N ≤ 4, so slicing to a smaller array should always work - unsafe { row.c[(4 - N)..].try_into() }.unwrap() - }); - - // UNWRAP: M ≤ 4, so slicing to a smaller array should always work - rows[..M].try_into().unwrap() - } + pub fn as_raw(&self) -> &citro3d_sys::C3D_Mtx { + &self.0 } - impl fmt::Debug for Matrix { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let inner = self.as_rows().map(|mut row| { - // Rows are stored in WZYX order which is opposite of how most people - // probably expect, so reverse each row in-place for debug printing - row.reverse(); - row - }); + pub fn as_raw_mut(&mut self) -> &mut citro3d_sys::C3D_Mtx { + &mut self.0 + } - let type_name = std::any::type_name::().split("::").last().unwrap(); - f.debug_tuple(type_name).field(&inner).finish() - } + pub fn into_raw(self) -> citro3d_sys::C3D_Mtx { + self.0 } -} -/// A 3x3 row-major matrix of `f32`s. -pub type Matrix3 = Matrix<3, 3>; -/// A 4x4 row-major matrix of `f32`s. -pub type Matrix4 = Matrix<4, 4>; + /// Get the rows in raw (WZYX) form + pub fn rows_wzyx(self) -> [FVec4; 4] { + unsafe { self.0.r }.map(FVec4::from_raw) + } -impl Matrix { + /// Get the rows in XYZW form + pub fn rows_xyzw(self) -> [[f32; 4]; 4] { + self.rows_wzyx().map(|r| [r.x(), r.y(), r.z(), r.w()]) + } /// Construct the zero matrix. #[doc(alias = "Mtx_Zeros")] pub fn zero() -> Self { @@ -86,17 +62,17 @@ impl Matrix { let mut out = MaybeUninit::uninit(); unsafe { citro3d_sys::Mtx_Zeros(out.as_mut_ptr()); - Self::new(out.assume_init()) + Self::from_raw(out.assume_init()) } } /// Transpose the matrix, swapping rows and columns. #[doc(alias = "Mtx_Transpose")] - pub fn transpose(mut self) -> Matrix { + pub fn transpose(mut self) -> Matrix4 { unsafe { - citro3d_sys::Mtx_Transpose(self.as_mut()); + citro3d_sys::Mtx_Transpose(self.as_raw_mut()); } - Matrix::new(self.into_raw()) + Matrix4::from_raw(self.into_raw()) } // region: Matrix transformations @@ -111,43 +87,39 @@ impl Matrix { /// directions. #[doc(alias = "Mtx_Translate")] pub fn translate(&mut self, x: f32, y: f32, z: f32) { - unsafe { citro3d_sys::Mtx_Translate(self.as_mut(), x, y, z, false) } + unsafe { citro3d_sys::Mtx_Translate(self.as_raw_mut(), x, y, z, false) } } /// Scale a transformation matrix by the given amounts in the X, Y, and Z directions. #[doc(alias = "Mtx_Scale")] pub fn scale(&mut self, x: f32, y: f32, z: f32) { - unsafe { citro3d_sys::Mtx_Scale(self.as_mut(), x, y, z) } + unsafe { citro3d_sys::Mtx_Scale(self.as_raw_mut(), x, y, z) } } /// Rotate a transformation matrix by the given angle around the given axis. #[doc(alias = "Mtx_Rotate")] pub fn rotate(&mut self, axis: FVec3, angle: f32) { - unsafe { citro3d_sys::Mtx_Rotate(self.as_mut(), axis.0, angle, false) } + unsafe { citro3d_sys::Mtx_Rotate(self.as_raw_mut(), axis.0, angle, false) } } /// Rotate a transformation matrix by the given angle around the X axis. #[doc(alias = "Mtx_RotateX")] pub fn rotate_x(&mut self, angle: f32) { - unsafe { citro3d_sys::Mtx_RotateX(self.as_mut(), angle, false) } + unsafe { citro3d_sys::Mtx_RotateX(self.as_raw_mut(), angle, false) } } /// Rotate a transformation matrix by the given angle around the Y axis. #[doc(alias = "Mtx_RotateY")] pub fn rotate_y(&mut self, angle: f32) { - unsafe { citro3d_sys::Mtx_RotateY(self.as_mut(), angle, false) } + unsafe { citro3d_sys::Mtx_RotateY(self.as_raw_mut(), angle, false) } } /// Rotate a transformation matrix by the given angle around the Z axis. #[doc(alias = "Mtx_RotateZ")] pub fn rotate_z(&mut self, angle: f32) { - unsafe { citro3d_sys::Mtx_RotateZ(self.as_mut(), angle, false) } + unsafe { citro3d_sys::Mtx_RotateZ(self.as_raw_mut(), angle, false) } } - // endregion -} - -impl Matrix { /// Find the inverse of the matrix. /// /// # Errors @@ -155,7 +127,7 @@ impl Matrix { /// If the matrix has no inverse, it will be returned unchanged as an [`Err`]. #[doc(alias = "Mtx_Inverse")] pub fn inverse(mut self) -> Result { - let determinant = unsafe { citro3d_sys::Mtx_Inverse(self.as_mut()) }; + let determinant = unsafe { citro3d_sys::Mtx_Inverse(self.as_raw_mut()) }; if determinant == 0.0 { Err(self) } else { @@ -169,31 +141,17 @@ impl Matrix { let mut out = MaybeUninit::uninit(); unsafe { citro3d_sys::Mtx_Identity(out.as_mut_ptr()); - Self::new(out.assume_init()) - } - } -} - -impl Matrix3 { - /// Construct a 3x3 matrix with the given values on the diagonal. - #[doc(alias = "Mtx_Diagonal")] - pub fn diagonal(x: f32, y: f32, z: f32) -> Self { - let mut out = MaybeUninit::uninit(); - unsafe { - citro3d_sys::Mtx_Diagonal(out.as_mut_ptr(), x, y, z, 0.0); - Self::new(out.assume_init()) + Self::from_raw(out.assume_init()) } } -} -impl Matrix4 { /// Construct a 4x4 matrix with the given values on the diagonal. #[doc(alias = "Mtx_Diagonal")] pub fn diagonal(x: f32, y: f32, z: f32, w: f32) -> Self { let mut out = MaybeUninit::uninit(); unsafe { citro3d_sys::Mtx_Diagonal(out.as_mut_ptr(), x, y, z, w); - Self::new(out.assume_init()) + Self::from_raw(out.assume_init()) } } @@ -215,7 +173,27 @@ impl Matrix4 { camera_up.0, coordinates.is_left_handed(), ); - Self::new(out.assume_init()) + Self::from_raw(out.assume_init()) } } } + +impl core::fmt::Debug for Matrix4 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Matrix4").field(&self.rows_wzyx()).finish() + } +} + +#[cfg(feature = "glam")] +impl From for Matrix4 { + fn from(mat: glam::Mat4) -> Self { + Matrix4::from_rows(core::array::from_fn(|i| mat.row(i).into())) + } +} + +#[cfg(feature = "glam")] +impl From for glam::Mat4 { + fn from(mat: Matrix4) -> Self { + glam::Mat4::from_cols_array_2d(&mat.rows_xyzw()).transpose() + } +} diff --git a/citro3d/src/math/ops.rs b/citro3d/src/math/ops.rs index 3d487ea..229210e 100644 --- a/citro3d/src/math/ops.rs +++ b/citro3d/src/math/ops.rs @@ -1,11 +1,10 @@ -use std::borrow::Borrow; use std::mem::MaybeUninit; -use std::ops::{Add, Deref, Div, Mul, Neg, Sub}; +use std::ops::{Add, Div, Mul, Neg, Sub}; #[cfg(feature = "approx")] use approx::AbsDiffEq; -use super::{FVec, FVec3, FVec4, Matrix, Matrix3, Matrix4}; +use super::{FVec, FVec3, FVec4, Matrix4}; // region: FVec4 math operators @@ -126,59 +125,50 @@ impl AbsDiffEq for FVec { // region: Matrix math operators -impl, const M: usize, const N: usize> Add for &Matrix { - type Output = ::Target; +impl Add for Matrix4 { + type Output = Matrix4; #[doc(alias = "Mtx_Add")] - fn add(self, rhs: Rhs) -> Self::Output { + fn add(self, rhs: Matrix4) -> Self::Output { let mut out = MaybeUninit::uninit(); unsafe { - citro3d_sys::Mtx_Add(out.as_mut_ptr(), self.as_raw(), rhs.borrow().as_raw()); - Matrix::new(out.assume_init()) + citro3d_sys::Mtx_Add(out.as_mut_ptr(), self.as_raw(), rhs.as_raw()); + Matrix4::from_raw(out.assume_init()) } } } -impl, const M: usize, const N: usize> Sub for &Matrix { - type Output = ::Target; +impl Sub for Matrix4 { + type Output = Matrix4; #[doc(alias = "Mtx_Subtract")] - fn sub(self, rhs: Rhs) -> Self::Output { + fn sub(self, rhs: Matrix4) -> Self::Output { let mut out = MaybeUninit::uninit(); unsafe { - citro3d_sys::Mtx_Subtract(out.as_mut_ptr(), self.as_raw(), rhs.borrow().as_raw()); - Matrix::new(out.assume_init()) + citro3d_sys::Mtx_Subtract(out.as_mut_ptr(), self.as_raw(), rhs.as_raw()); + Matrix4::from_raw(out.assume_init()) } } } -impl Mul<&Matrix> for &Matrix { - type Output = Matrix; +impl Mul for Matrix4 { + type Output = Matrix4; #[doc(alias = "Mtx_Multiply")] - fn mul(self, rhs: &Matrix) -> Self::Output { + fn mul(self, rhs: Matrix4) -> Self::Output { let mut out = MaybeUninit::uninit(); unsafe { citro3d_sys::Mtx_Multiply(out.as_mut_ptr(), self.as_raw(), rhs.as_raw()); - Matrix::new(out.assume_init()) + Matrix4::from_raw(out.assume_init()) } } } -impl Mul> for &Matrix { - type Output = Matrix; +impl Mul for &Matrix4 { + type Output = Matrix4; - fn mul(self, rhs: Matrix) -> Self::Output { - self * &rhs - } -} - -impl Mul for &Matrix3 { - type Output = FVec3; - - #[doc(alias = "Mtx_MultiplyFVec3")] - fn mul(self, rhs: FVec3) -> Self::Output { - FVec(unsafe { citro3d_sys::Mtx_MultiplyFVec3(self.as_raw(), rhs.0) }) + fn mul(self, rhs: Matrix4) -> Self::Output { + *self * rhs } } @@ -191,7 +181,7 @@ impl Mul for &Matrix4 { } } -impl Mul for &Matrix<4, 3> { +impl Mul for &Matrix4 { type Output = FVec4; #[doc(alias = "Mtx_MultiplyFVecH")] @@ -200,19 +190,17 @@ impl Mul for &Matrix<4, 3> { } } -// endregion - -impl, const M: usize, const N: usize> PartialEq for Matrix { - fn eq(&self, other: &Rhs) -> bool { - self.as_rows() == other.borrow().as_rows() +impl PartialEq for Matrix4 { + fn eq(&self, other: &Matrix4) -> bool { + self.rows_wzyx() == other.rows_wzyx() } } -impl Eq for Matrix {} +// endregion #[cfg(feature = "approx")] #[doc(cfg(feature = "approx"))] -impl AbsDiffEq for Matrix { +impl AbsDiffEq for Matrix4 { type Epsilon = f32; fn default_epsilon() -> Self::Epsilon { @@ -222,18 +210,10 @@ impl AbsDiffEq for Matrix { } fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { - let lhs = self.as_rows(); - let rhs = other.as_rows(); - - for row in 0..M { - for col in 0..N { - if !lhs[row][col].abs_diff_eq(&rhs[row][col], epsilon) { - return false; - } - } - } - - true + self.rows_wzyx() + .into_iter() + .zip(other.rows_wzyx().into_iter()) + .all(|(l, r)| l.abs_diff_eq(&r, epsilon)) } } @@ -267,25 +247,13 @@ mod tests { assert_abs_diff_eq!(l / 2.0, FVec4::splat(0.5)); } - #[test] - fn matrix3() { - let l = Matrix3::diagonal(1.0, 2.0, 3.0); - let r = Matrix3::identity(); - let (l, r) = (&l, &r); - - assert_abs_diff_eq!(&(l * r), l); - assert_abs_diff_eq!(&(l + r), &Matrix3::diagonal(2.0, 3.0, 4.0)); - assert_abs_diff_eq!(&(l - r), &Matrix3::diagonal(0.0, 1.0, 2.0)); - } - #[test] fn matrix4() { let l = Matrix4::diagonal(1.0, 2.0, 3.0, 4.0); let r = Matrix4::identity(); - let (l, r) = (&l, &r); - assert_abs_diff_eq!(&(l * r), l); - assert_abs_diff_eq!(&(l + r), &Matrix4::diagonal(2.0, 3.0, 4.0, 5.0)); - assert_abs_diff_eq!(&(l - r), &Matrix4::diagonal(0.0, 1.0, 2.0, 3.0)); + assert_abs_diff_eq!(l * r, l); + assert_abs_diff_eq!(l + r, Matrix4::diagonal(2.0, 3.0, 4.0, 5.0)); + assert_abs_diff_eq!(l - r, Matrix4::diagonal(0.0, 1.0, 2.0, 3.0)); } } diff --git a/citro3d/src/math/projection.rs b/citro3d/src/math/projection.rs index f694ad6..b0c1da4 100644 --- a/citro3d/src/math/projection.rs +++ b/citro3d/src/math/projection.rs @@ -209,7 +209,7 @@ impl From> for Matrix4 { } } - unsafe { Self::new(result.assume_init()) } + unsafe { Self::from_raw(result.assume_init()) } } } @@ -285,7 +285,7 @@ impl From> for Matrix4 { clip_planes_z.far, projection.coordinates.is_left_handed(), ); - Self::new(out.assume_init()) + Self::from_raw(out.assume_init()) } } } diff --git a/citro3d/src/shader.rs b/citro3d/src/shader.rs index 9f1de0b..512022b 100644 --- a/citro3d/src/shader.rs +++ b/citro3d/src/shader.rs @@ -96,7 +96,7 @@ impl Program { if idx < 0 { Err(crate::Error::NotFound) } else { - Ok(idx.into()) + Ok((idx as u8).into()) } } @@ -116,6 +116,7 @@ impl Drop for Program { /// The type of a shader. #[repr(u32)] +#[derive(Clone, Copy)] pub enum Type { /// A vertex shader. Vertex = ctru_sys::GPU_VERTEX_SHADER, diff --git a/citro3d/src/uniform.rs b/citro3d/src/uniform.rs index 22c7092..c05157b 100644 --- a/citro3d/src/uniform.rs +++ b/citro3d/src/uniform.rs @@ -1,15 +1,17 @@ //! Common definitions for binding uniforms to shaders. This is primarily //! done by implementing the [`Uniform`] trait for a given type. -use crate::math::Matrix; +use std::ops::Range; + +use crate::math::{FVec4, IVec, Matrix4}; use crate::{shader, Instance}; /// The index of a uniform within a [`shader::Program`]. -#[derive(Copy, Clone, Debug)] -pub struct Index(i8); +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Index(u8); -impl From for Index { - fn from(value: i8) -> Self { +impl From for Index { + fn from(value: u8) -> Self { Self(value) } } @@ -20,38 +22,154 @@ impl From for i32 { } } -mod private { - use crate::math::Matrix; +/// A uniform which may be bound as input to a shader program +#[non_exhaustive] +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum Uniform { + /// Single float uniform (`.fvec name`) + #[doc(alias = "C3D_FVUnifSet")] + Float(FVec4), + /// Two element float uniform (`.fvec name[2]`) + #[doc(alias = "C3D_FVUnifMtx2x4")] + Float2([FVec4; 2]), + /// Three element float uniform (`.fvec name [3]`) + #[doc(alias = "C3D_FVUnifMtx3x4")] + Float3([FVec4; 3]), + /// Matrix/4 element float uniform (`.fvec name[4]`) + #[doc(alias = "C3D_FVUnifMtx4x4")] + Float4(Matrix4), + /// Bool uniform (`.bool name`) + #[doc(alias = "C3D_BoolUnifSet")] + Bool(bool), + /// Integer uniform (`.ivec name`) + #[doc(alias = "C3D_IVUnifSet")] + Int(IVec), +} +impl Uniform { + /// Get range of valid indexes for this uniform to bind to + pub fn index_range(&self) -> Range { + // these indexes are from the uniform table in the shader see: https://www.3dbrew.org/wiki/SHBIN#Uniform_Table_Entry + // the input registers then are excluded by libctru, see: https://github.com/devkitPro/libctru/blob/0da8705527f03b4b08ff7fee4dd1b7f28df37905/libctru/source/gpu/shbin.c#L93 + match self { + Self::Float(_) | Self::Float2(_) | Self::Float3(_) | Self::Float4(_) => { + Index(0)..Index(0x60) + } + Self::Int(_) => Index(0x60)..Index(0x64), + // this gap is intentional + Self::Bool(_) => Index(0x68)..Index(0x79), + } + } + /// Get length of uniform, i.e. how many registers it will write to + #[allow(clippy::len_without_is_empty)] // is_empty doesn't make sense here + pub fn len(&self) -> usize { + match self { + Self::Float(_) => 1, + Self::Float2(_) => 2, + Self::Float3(_) => 3, + Self::Float4(_) => 4, + Self::Bool(_) | Uniform::Int(_) => 1, + } + } - pub trait Sealed {} + /// Bind a uniform + /// + /// Note: `_instance` is here to ensure unique access to the global uniform buffers + /// otherwise we could race and/or violate aliasing + pub(crate) fn bind(self, _instance: &mut Instance, ty: shader::Type, index: Index) { + assert!( + self.index_range().contains(&index), + "tried to bind uniform to an invalid index (index: {:?}, valid range: {:?})", + index, + self.index_range(), + ); + assert!(self.index_range().end.0 as usize >= self.len() + index.0 as usize, "tried to bind a uniform that would overflow the uniform buffer. index was {:?}, size was {} max is {:?}", index, self.len(), self.index_range().end); + let set_fvs = |fs: &[FVec4]| { + for (off, f) in fs.iter().enumerate() { + unsafe { + citro3d_sys::C3D_FVUnifSet( + ty.into(), + (index.0 as usize + off) as i32, + f.x(), + f.y(), + f.z(), + f.w(), + ); + } + } + }; + match self { + Self::Bool(b) => unsafe { + citro3d_sys::C3D_BoolUnifSet(ty.into(), index.into(), b); + }, + Self::Int(i) => unsafe { + citro3d_sys::C3D_IVUnifSet( + ty.into(), + index.into(), + i.x() as i32, + i.y() as i32, + i.z() as i32, + i.w() as i32, + ); + }, + Self::Float(f) => set_fvs(&[f]), + Self::Float2(fs) => { + set_fvs(&fs); + } + Self::Float3(fs) => set_fvs(&fs), + Self::Float4(m) => { + set_fvs(&m.rows_wzyx()); + } + } + } +} - impl Sealed for &Matrix {} +impl From for Uniform { + fn from(value: Matrix4) -> Self { + Self::Float4(value) + } +} +impl From<[FVec4; 3]> for Uniform { + fn from(value: [FVec4; 3]) -> Self { + Self::Float3(value) + } } -/// A shader uniform. This trait is implemented for types that can be bound to -/// shaders to be used as a uniform input to the shader. -pub trait Uniform: private::Sealed { - /// Bind the uniform to the given shader index for the given shader type. - /// An [`Instance`] is required to prevent concurrent binding of different - /// uniforms to the same index. - fn bind(self, instance: &mut Instance, shader_type: shader::Type, index: Index); +impl From<[FVec4; 2]> for Uniform { + fn from(value: [FVec4; 2]) -> Self { + Self::Float2(value) + } +} +impl From for Uniform { + fn from(value: FVec4) -> Self { + Self::Float(value) + } +} +impl From for Uniform { + fn from(value: IVec) -> Self { + Self::Int(value) + } +} +impl From for Uniform { + fn from(value: bool) -> Self { + Self::Bool(value) + } +} +impl From<&Matrix4> for Uniform { + fn from(value: &Matrix4) -> Self { + (*value).into() + } } -impl Uniform for &Matrix { - #[doc(alias = "C34_FVUnifMtxNx4")] - #[doc(alias = "C34_FVUnifMtx4x4")] - #[doc(alias = "C34_FVUnifMtx3x4")] - #[doc(alias = "C34_FVUnifMtx2x4")] - fn bind(self, _instance: &mut Instance, type_: shader::Type, index: Index) { - unsafe { - citro3d_sys::C3D_FVUnifMtxNx4( - type_.into(), - index.into(), - self.as_raw(), - // UNWRAP: it should be impossible for end users to construct - // a matrix with M > i32::MAX - M.try_into().unwrap(), - ); - } +#[cfg(feature = "glam")] +impl From for Uniform { + fn from(value: glam::Vec4) -> Self { + Self::Float(value.into()) + } +} + +#[cfg(feature = "glam")] +impl From for Uniform { + fn from(value: glam::Mat4) -> Self { + Self::Float4(value.into()) } }