Browse Source

Merge pull request #43 from 0x00002a/0x2a/feat/improve-matrix-api

main
Ian Chamberlain 10 months ago committed by GitHub
parent
commit
a57fdf14c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      citro3d/Cargo.toml
  2. 8
      citro3d/src/lib.rs
  3. 42
      citro3d/src/math.rs
  4. 38
      citro3d/src/math/fvec.rs
  5. 182
      citro3d/src/math/matrix.rs
  6. 98
      citro3d/src/math/ops.rs
  7. 4
      citro3d/src/math/projection.rs
  8. 3
      citro3d/src/shader.rs
  9. 182
      citro3d/src/uniform.rs

5
citro3d/Cargo.toml

@ -6,6 +6,7 @@ version = "0.1.0" @@ -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" @@ -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" }

8
citro3d/src/lib.rs

@ -223,8 +223,8 @@ impl Instance { @@ -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>) {
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 { @@ -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>) {
uniform.into().bind(self, shader::Type::Geometry, index);
}
/// Retrieve the [`TexEnv`] for the given stage, initializing it first if necessary.

42
citro3d/src/math.rs

@ -9,16 +9,56 @@ mod ops; @@ -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);
}
}

38
citro3d/src/math/fvec.rs

@ -3,8 +3,15 @@ @@ -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<const N: usize>(pub(crate) citro3d_sys::C3D_FVec);
/// A 3-vector of `f32`s.
@ -48,6 +55,11 @@ impl FVec4 { @@ -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 { @@ -243,6 +255,32 @@ impl FVec3 {
}
}
#[cfg(feature = "glam")]
impl From<glam::Vec4> for FVec4 {
fn from(value: glam::Vec4) -> Self {
Self::new(value.x, value.y, value.z, value.w)
}
}
#[cfg(feature = "glam")]
impl From<glam::Vec3> for FVec3 {
fn from(value: glam::Vec3) -> Self {
Self::new(value.x, value.y, value.z)
}
}
#[cfg(feature = "glam")]
impl From<FVec4> for glam::Vec4 {
fn from(value: FVec4) -> Self {
glam::Vec4::new(value.x(), value.y(), value.z(), value.w())
}
}
#[cfg(feature = "glam")]
impl From<FVec3> 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;

182
citro3d/src/math/matrix.rs

@ -1,84 +1,60 @@ @@ -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<const M: usize, const N: usize>(citro3d_sys::C3D_Mtx);
impl<const M: usize, const N: usize> Matrix<M, N> {
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<const M: usize, const N: usize> fmt::Debug for Matrix<M, N> {
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::<Self>().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<const M: usize, const N: usize> Matrix<M, N> {
/// 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<const M: usize, const N: usize> Matrix<M, N> { @@ -86,17 +62,17 @@ impl<const M: usize, const N: usize> Matrix<M, N> {
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<N, M> {
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<const M: usize, const N: usize> Matrix<M, N> { @@ -111,43 +87,39 @@ impl<const M: usize, const N: usize> Matrix<M, N> {
/// 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<const N: usize> Matrix<N, N> {
/// Find the inverse of the matrix.
///
/// # Errors
@ -155,7 +127,7 @@ impl<const N: usize> Matrix<N, N> { @@ -155,7 +127,7 @@ impl<const N: usize> Matrix<N, N> {
/// If the matrix has no inverse, it will be returned unchanged as an [`Err`].
#[doc(alias = "Mtx_Inverse")]
pub fn inverse(mut self) -> Result<Self, Self> {
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<const N: usize> Matrix<N, N> { @@ -169,31 +141,17 @@ impl<const N: usize> Matrix<N, N> {
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 { @@ -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<glam::Mat4> 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<Matrix4> for glam::Mat4 {
fn from(mat: Matrix4) -> Self {
glam::Mat4::from_cols_array_2d(&mat.rows_xyzw()).transpose()
}
}

98
citro3d/src/math/ops.rs

@ -1,11 +1,10 @@ @@ -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<const N: usize> AbsDiffEq for FVec<N> { @@ -126,59 +125,50 @@ impl<const N: usize> AbsDiffEq for FVec<N> {
// region: Matrix math operators
impl<Rhs: Borrow<Self>, const M: usize, const N: usize> Add<Rhs> for &Matrix<M, N> {
type Output = <Self as Deref>::Target;
impl Add<Matrix4> 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<Rhs: Borrow<Self>, const M: usize, const N: usize> Sub<Rhs> for &Matrix<M, N> {
type Output = <Self as Deref>::Target;
impl Sub<Matrix4> 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<const M: usize, const N: usize, const P: usize> Mul<&Matrix<N, P>> for &Matrix<M, N> {
type Output = Matrix<M, P>;
impl Mul<Matrix4> for Matrix4 {
type Output = Matrix4;
#[doc(alias = "Mtx_Multiply")]
fn mul(self, rhs: &Matrix<N, P>) -> 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<const M: usize, const N: usize, const P: usize> Mul<Matrix<N, P>> for &Matrix<M, N> {
type Output = Matrix<M, P>;
impl Mul<Matrix4> for &Matrix4 {
type Output = Matrix4;
fn mul(self, rhs: Matrix<N, P>) -> Self::Output {
self * &rhs
}
}
impl Mul<FVec3> 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<FVec4> for &Matrix4 { @@ -191,7 +181,7 @@ impl Mul<FVec4> for &Matrix4 {
}
}
impl Mul<FVec3> for &Matrix<4, 3> {
impl Mul<FVec3> for &Matrix4 {
type Output = FVec4;
#[doc(alias = "Mtx_MultiplyFVecH")]
@ -200,19 +190,17 @@ impl Mul<FVec3> for &Matrix<4, 3> { @@ -200,19 +190,17 @@ impl Mul<FVec3> for &Matrix<4, 3> {
}
}
// endregion
impl<Rhs: Borrow<Self>, const M: usize, const N: usize> PartialEq<Rhs> for Matrix<M, N> {
fn eq(&self, other: &Rhs) -> bool {
self.as_rows() == other.borrow().as_rows()
impl PartialEq<Matrix4> for Matrix4 {
fn eq(&self, other: &Matrix4) -> bool {
self.rows_wzyx() == other.rows_wzyx()
}
}
impl<const M: usize, const N: usize> Eq for Matrix<M, N> {}
// endregion
#[cfg(feature = "approx")]
#[doc(cfg(feature = "approx"))]
impl<const M: usize, const N: usize> AbsDiffEq for Matrix<M, N> {
impl AbsDiffEq for Matrix4 {
type Epsilon = f32;
fn default_epsilon() -> Self::Epsilon {
@ -222,18 +210,10 @@ impl<const M: usize, const N: usize> AbsDiffEq for Matrix<M, N> { @@ -222,18 +210,10 @@ impl<const M: usize, const N: usize> AbsDiffEq for Matrix<M, N> {
}
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 { @@ -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));
}
}

4
citro3d/src/math/projection.rs

@ -209,7 +209,7 @@ impl From<Projection<Perspective>> for Matrix4 { @@ -209,7 +209,7 @@ impl From<Projection<Perspective>> for Matrix4 {
}
}
unsafe { Self::new(result.assume_init()) }
unsafe { Self::from_raw(result.assume_init()) }
}
}
@ -285,7 +285,7 @@ impl From<Projection<Orthographic>> for Matrix4 { @@ -285,7 +285,7 @@ impl From<Projection<Orthographic>> for Matrix4 {
clip_planes_z.far,
projection.coordinates.is_left_handed(),
);
Self::new(out.assume_init())
Self::from_raw(out.assume_init())
}
}
}

3
citro3d/src/shader.rs

@ -96,7 +96,7 @@ impl Program { @@ -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 { @@ -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,

182
citro3d/src/uniform.rs

@ -1,15 +1,17 @@ @@ -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<i8> for Index {
fn from(value: i8) -> Self {
impl From<u8> for Index {
fn from(value: u8) -> Self {
Self(value)
}
}
@ -20,38 +22,154 @@ impl From<Index> for i32 { @@ -20,38 +22,154 @@ impl From<Index> 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<Index> {
// 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<const M: usize, const N: usize> Sealed for &Matrix<M, N> {}
impl From<Matrix4> 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<FVec4> for Uniform {
fn from(value: FVec4) -> Self {
Self::Float(value)
}
}
impl From<IVec> for Uniform {
fn from(value: IVec) -> Self {
Self::Int(value)
}
}
impl From<bool> for Uniform {
fn from(value: bool) -> Self {
Self::Bool(value)
}
}
impl From<&Matrix4> for Uniform {
fn from(value: &Matrix4) -> Self {
(*value).into()
}
}
impl<const M: usize> Uniform for &Matrix<M, 4> {
#[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<glam::Vec4> for Uniform {
fn from(value: glam::Vec4) -> Self {
Self::Float(value.into())
}
}
#[cfg(feature = "glam")]
impl From<glam::Mat4> for Uniform {
fn from(value: glam::Mat4) -> Self {
Self::Float4(value.into())
}
}

Loading…
Cancel
Save