diff --git a/citro3d/src/math/fvec.rs b/citro3d/src/math/fvec.rs index 8f97f19..205fe11 100644 --- a/citro3d/src/math/fvec.rs +++ b/citro3d/src/math/fvec.rs @@ -149,6 +149,12 @@ impl FVec4 { } } +impl From for [f32; 4] { + fn from(value: FVec4) -> Self { + [value.x(), value.y(), value.z(), value.w()] + } +} + impl FVec3 { /// Create a new [`FVec3`] from its components. /// diff --git a/citro3d/src/render.rs b/citro3d/src/render.rs index 2a5343b..cd50e3a 100644 --- a/citro3d/src/render.rs +++ b/citro3d/src/render.rs @@ -454,7 +454,11 @@ impl<'instance> Frame<'instance> { /// let mtx = Matrix4::identity(); /// instance.bind_vertex_uniform(idx, &mtx); /// ``` - pub fn bind_vertex_uniform(&mut self, index: uniform::Index, uniform: impl Into) { + pub fn bind_vertex_uniform<'a>( + &mut self, + index: uniform::Index, + uniform: impl Into>, + ) { if !self.is_program_bound { panic!("tried to bind vertex uniform when no shader program is bound"); } @@ -481,7 +485,11 @@ impl<'instance> Frame<'instance> { /// let mtx = Matrix4::identity(); /// instance.bind_geometry_uniform(idx, &mtx); /// ``` - pub fn bind_geometry_uniform(&mut self, index: uniform::Index, uniform: impl Into) { + pub fn bind_geometry_uniform<'a>( + &mut self, + index: uniform::Index, + uniform: impl Into>, + ) { if !self.is_program_bound { panic!("tried to bind geometry uniform when no shader program is bound"); } @@ -518,7 +526,7 @@ impl<'instance> Frame<'instance> { } } - /// Bind the given [`Texture`] to the specified [`texture::Unit`], which should + /// Bind the given [`texture::Texture`] to the specified [`texture::Unit`], which should /// correspond to a source or destination texture configured in the [`TexEnv`]. pub fn bind_texture(&mut self, index: texture::Index, texture: &'instance texture::Texture) { unsafe { texture.bind(index) }; diff --git a/citro3d/src/uniform.rs b/citro3d/src/uniform.rs index 8b3aba2..3ac9717 100644 --- a/citro3d/src/uniform.rs +++ b/citro3d/src/uniform.rs @@ -10,6 +10,12 @@ use crate::{Frame, shader}; #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Index(u8); +impl Index { + pub fn inner(&self) -> u8 { + self.0 + } +} + impl From for Index { fn from(value: u8) -> Self { Self(value) @@ -25,38 +31,53 @@ impl From for i32 { /// A uniform which may be bound as input to a shader program #[non_exhaustive] #[derive(Debug, PartialEq, Clone, Copy)] -pub enum Uniform { +pub enum Uniform<'a> { /// 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]`) + /// 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), + /// Uniform of an arbitrary number of floats (`.fvec name[X]`) + FloatArray(&'a [FVec4]), + /// Uniform of an arbitrary number of Matrices/4x floats (`.fvec name[X]`) + Float4Array(&'a [Matrix4]), /// Bool uniform (`.bool name`) #[doc(alias = "C3D_BoolUnifSet")] Bool(bool), /// Integer uniform (`.ivec name`) #[doc(alias = "C3D_IVUnifSet")] Int(IVec), + + #[cfg(feature = "glam")] + GlamFloatArray(&'a [glam::Vec4]), + #[cfg(feature = "glam")] + GlamMatrixArray(&'a [glam::Mat4]), } -impl Uniform { +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::Float(_) + | Self::Float2(_) + | Self::Float3(_) + | Self::Float4(_) + | Self::FloatArray(_) + | Self::Float4Array(_) => Index(0)..Index(0x60), Self::Int(_) => Index(0x60)..Index(0x64), // this gap is intentional Self::Bool(_) => Index(0x68)..Index(0x79), + + #[cfg(feature = "glam")] + Self::GlamFloatArray(_) | Self::GlamMatrixArray(_) => Index(0)..Index(0x60), } } /// Get length of uniform, i.e. how many registers it will write to @@ -67,7 +88,14 @@ impl Uniform { Self::Float2(_) => 2, Self::Float3(_) => 3, Self::Float4(_) => 4, + Self::FloatArray(arr) => arr.len(), + Self::Float4Array(arr) => 4 * arr.len(), Self::Bool(_) | Uniform::Int(_) => 1, + + #[cfg(feature = "glam")] + Self::GlamFloatArray(arr) => arr.len(), + #[cfg(feature = "glam")] + Self::GlamMatrixArray(arr) => 4 * arr.len(), } } @@ -90,94 +118,154 @@ impl Uniform { 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( + unsafe { + match self { + Self::Bool(b) => citro3d_sys::C3D_BoolUnifSet(ty.into(), index.into(), b), + Self::Int(i) => 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()); + ), + Self::Float(f) => set_float_uniforms([f], self.len(), ty, index), + Self::Float2(fs) => { + set_float_uniforms(fs, self.len(), ty, index); + } + Self::Float3(fs) => set_float_uniforms(fs, self.len(), ty, index), + Self::Float4(m) => { + set_float_uniforms(m.rows_wzyx(), self.len(), ty, index); + } + Self::FloatArray(arr) => { + set_float_uniforms(arr.iter().copied(), self.len(), ty, index); + } + Self::Float4Array(arr) => { + set_float_uniforms( + arr.iter().flat_map(|m| m.rows_wzyx()), + self.len(), + ty, + index, + ); + } + #[cfg(feature = "glam")] + Self::GlamFloatArray(arr) => { + set_float_uniforms(arr.iter().copied(), self.len(), ty, index); + } + #[cfg(feature = "glam")] + Self::GlamMatrixArray(arr) => { + set_float_uniforms( + arr.iter() + .copied() + .flat_map(|m| [m.row(0), m.row(1), m.row(2), m.row(3)]), + self.len(), + ty, + index, + ); + } } } } } -impl From for Uniform { +/// SAFETY: +/// 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. +unsafe fn set_float_uniforms>>( + fs: I, + max_len: usize, + ty: shader::Type, + index: Index, +) { + let vecs = + unsafe { citro3d_sys::C3D_FVUnifWritePtr(ty.into(), index.0 as i32, max_len as i32) }; + let vecs = unsafe { core::slice::from_raw_parts_mut(vecs, max_len) }; + + for (i, f) in fs.into_iter().enumerate() { + let f: [f32; 4] = f.into(); + vecs[i].__bindgen_anon_1.x = f[0]; + vecs[i].__bindgen_anon_1.y = f[1]; + vecs[i].__bindgen_anon_1.z = f[2]; + vecs[i].__bindgen_anon_1.w = f[3]; + } +} + +impl From for Uniform<'static> { fn from(value: Matrix4) -> Self { Self::Float4(value) } } -impl From<[FVec4; 3]> for Uniform { +impl<'a> From<&'a [Matrix4]> for Uniform<'a> { + fn from(value: &'a [Matrix4]) -> Self { + Self::Float4Array(value) + } +} +impl From<[FVec4; 3]> for Uniform<'static> { fn from(value: [FVec4; 3]) -> Self { Self::Float3(value) } } - -impl From<[FVec4; 2]> for Uniform { +impl From<[FVec4; 2]> for Uniform<'static> { fn from(value: [FVec4; 2]) -> Self { Self::Float2(value) } } -impl From for Uniform { +impl<'a> From<&'a [FVec4]> for Uniform<'a> { + fn from(value: &'a [FVec4]) -> Self { + Self::FloatArray(value) + } +} +impl From for Uniform<'static> { fn from(value: FVec4) -> Self { Self::Float(value) } } -impl From for Uniform { +impl From for Uniform<'static> { fn from(value: IVec) -> Self { Self::Int(value) } } -impl From for Uniform { +impl From for Uniform<'static> { fn from(value: bool) -> Self { Self::Bool(value) } } -impl From<&Matrix4> for Uniform { +impl From<&Matrix4> for Uniform<'static> { fn from(value: &Matrix4) -> Self { (*value).into() } } #[cfg(feature = "glam")] -impl From for Uniform { +impl From for Uniform<'static> { fn from(value: glam::Vec4) -> Self { Self::Float(value.into()) } } #[cfg(feature = "glam")] -impl From for Uniform { +impl<'a> From<&'a [glam::Vec4]> for Uniform<'a> { + fn from(value: &'a [glam::Vec4]) -> Self { + Self::GlamFloatArray(value) + } +} + +#[cfg(feature = "glam")] +impl<'a> From<&'a Vec> for Uniform<'a> { + fn from(value: &'a Vec) -> Self { + Self::GlamFloatArray(value.as_slice()) + } +} + +#[cfg(feature = "glam")] +impl From for Uniform<'static> { fn from(value: glam::Mat4) -> Self { Self::Float4(value.into()) } } + +#[cfg(feature = "glam")] +impl<'a> From<&'a [glam::Mat4]> for Uniform<'a> { + fn from(value: &'a [glam::Mat4]) -> Self { + Self::GlamMatrixArray(value) + } +}