diff --git a/citro3d/src/lib.rs b/citro3d/src/lib.rs index 544ed89..34d3160 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -192,8 +192,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. @@ -210,8 +210,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 fcc04c3..35a188e 100644 --- a/citro3d/src/math.rs +++ b/citro3d/src/math.rs @@ -9,7 +9,7 @@ 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, diff --git a/citro3d/src/math/fvec.rs b/citro3d/src/math/fvec.rs index 37e66c7..3fa888e 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. diff --git a/citro3d/src/math/matrix.rs b/citro3d/src/math/matrix.rs index e07f777..e3f958e 100644 --- a/citro3d/src/math/matrix.rs +++ b/citro3d/src/math/matrix.rs @@ -1,84 +1,55 @@ 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 { + /// 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] { + // Safety: FVec4 is repr(C) to allow transmute from C3D_Vec + unsafe { core::mem::transmute::<[citro3d_sys::C3D_FVec; 4], [FVec4; 4]>(self.0.r) } + } -impl Matrix { + /// Get the rows in normal XYZW form + pub fn rows_xyzw(self) -> [[f32; 4]; 4] { + let mut rows = self.rows_wzyx(); + for r in &mut rows { + unsafe { + r.0.c.reverse(); + } + } + // Safety: FVec has same layout as citro3d_sys version which is a union with [f32; 4] as one variant + unsafe { std::mem::transmute::<_, [[f32; 4]; 4]>(rows) } + } /// Construct the zero matrix. #[doc(alias = "Mtx_Zeros")] pub fn zero() -> Self { @@ -86,17 +57,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 +82,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 +122,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 +136,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 +168,19 @@ 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() + } +} +impl PartialEq for Matrix4 { + fn eq(&self, other: &Matrix4) -> bool { + self.rows_wzyx() == other.rows_wzyx() + } +} +impl Eq for Matrix4 {} diff --git a/citro3d/src/math/ops.rs b/citro3d/src/math/ops.rs index 3d487ea..92823ed 100644 --- a/citro3d/src/math/ops.rs +++ b/citro3d/src/math/ops.rs @@ -5,7 +5,7 @@ use std::ops::{Add, Deref, 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 +126,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 +182,7 @@ impl Mul for &Matrix4 { } } -impl Mul for &Matrix<4, 3> { +impl Mul for &Matrix4 { type Output = FVec4; #[doc(alias = "Mtx_MultiplyFVecH")] @@ -202,17 +193,9 @@ 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 Eq for Matrix {} - #[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 +205,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)) } } 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..e35fc2c 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,125 @@ impl From for i32 { } } -mod private { - use crate::math::Matrix; +/// 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 enum Uniform { + Float(FVec4), + Float2([FVec4; 2]), + Float3([FVec4; 3]), + Float4(Matrix4), + Bool(bool), + Int(IVec), +} +impl Uniform { + 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 { + Uniform::Float(_) | Uniform::Float2(_) | Uniform::Float3(_) | Uniform::Float4(_) => { + Index(0)..Index(0x60) + } + Uniform::Int(_) => Index(0x60)..Index(0x64), + // this gap is intentional + Uniform::Bool(_) => Index(0x68)..Index(0x79), + } + } + pub fn len(&self) -> usize { + match self { + Uniform::Float(_) => 1, + Uniform::Float2(_) => 2, + Uniform::Float3(_) => 3, + Uniform::Float4(_) => 4, + Uniform::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.0, + self.index_range().start.0, + self.index_range().end.0 + ); + 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.0, self.len(), self.index_range().end.0); + 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 { + Uniform::Bool(b) => unsafe { + citro3d_sys::C3D_BoolUnifSet(ty.into(), index.into(), b); + }, + Uniform::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, + ); + }, + Uniform::Float(f) => set_fvs(&[f]), + Uniform::Float2(fs) => { + set_fvs(&fs); + } + Uniform::Float3(fs) => set_fvs(&fs), + Uniform::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 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(), - ); - } +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() } }