diff --git a/citro3d/examples/triangle.rs b/citro3d/examples/triangle.rs index 8f3522f..e8ca2c2 100644 --- a/citro3d/examples/triangle.rs +++ b/citro3d/examples/triangle.rs @@ -3,13 +3,10 @@ #![feature(allocator_api)] -use std::ffi::CStr; -use std::mem::MaybeUninit; - use citro3d::macros::include_shader; +use citro3d::math::{CoordinateSystem, Matrix}; use citro3d::render::{self, ClearFlags}; -use citro3d::{attrib, buffer, shader}; -use citro3d_sys::C3D_Mtx; +use citro3d::{attrib, buffer, shader, AspectRatio}; use ctru::prelude::*; use ctru::services::gfx::{RawFrameBuffer, Screen, TopScreen3D}; @@ -92,7 +89,10 @@ fn main() { let mut buf_info = buffer::Info::new(); let (attr_info, vbo_idx) = prepare_vbos(&mut buf_info, &vbo_data); - let projection_uniform_idx = scene_init(&mut program); + scene_init(&mut program); + + let projection_uniform_idx = program.get_uniform_location("projection").unwrap(); + while apt.main_loop() { hid.scan_input(); @@ -109,14 +109,7 @@ fn main() { let clear_color: u32 = 0x7F_7F_7F_FF; target.clear(ClearFlags::ALL, clear_color, 0); - unsafe { - // Update the uniforms - citro3d_sys::C3D_FVUnifMtx4x4( - ctru_sys::GPU_VERTEX_SHADER, - projection_uniform_idx.into(), - projection, - ); - } + instance.update_vertex_uniform_mat4x4(projection_uniform_idx, projection); instance.set_attr_info(&attr_info); @@ -165,16 +158,12 @@ where } struct Projections { - left: C3D_Mtx, - right: C3D_Mtx, - center: C3D_Mtx, + left: Matrix, + right: Matrix, + center: Matrix, } fn calculate_projections() -> Projections { - let mut left_eye = MaybeUninit::uninit(); - let mut right_eye = MaybeUninit::uninit(); - let mut center = MaybeUninit::uninit(); - // TODO: it would be cool to allow playing around with these parameters on // the fly with D-pad, etc. let slider_val = unsafe { ctru_sys::osGet3DSliderState() }; @@ -182,53 +171,48 @@ fn calculate_projections() -> Projections { let near = 0.01; let far = 100.0; - let fovy = 40.0_f32.to_radians(); + let fov_y = 40.0_f32.to_radians(); let screen = 2.0; - unsafe { - citro3d_sys::Mtx_PerspStereoTilt( - left_eye.as_mut_ptr(), - fovy, - citro3d_sys::C3D_AspectRatioTop as f32, - near, - far, - -iod, - screen, - true, - ); - - citro3d_sys::Mtx_PerspStereoTilt( - right_eye.as_mut_ptr(), - fovy, - citro3d_sys::C3D_AspectRatioTop as f32, - near, - far, - iod, - screen, - true, - ); - - citro3d_sys::Mtx_PerspTilt( - center.as_mut_ptr(), - fovy, - citro3d_sys::C3D_AspectRatioBot as f32, - near, - far, - true, - ); - - Projections { - left: left_eye.assume_init(), - right: right_eye.assume_init(), - center: center.assume_init(), - } + let left_eye = Matrix::perspective_stereo_tilt( + fov_y, + AspectRatio::TopScreen, + near, + far, + -iod, + screen, + CoordinateSystem::LeftHanded, + ); + + let right_eye = Matrix::perspective_stereo_tilt( + fov_y, + AspectRatio::TopScreen, + near, + far, + iod, + screen, + CoordinateSystem::LeftHanded, + ); + + let center = Matrix::perspective_tilt( + fov_y, + AspectRatio::BottomScreen, + near, + far, + CoordinateSystem::LeftHanded, + ); + + Projections { + left: left_eye, + right: right_eye, + center, } } -fn scene_init(program: &mut shader::Program) -> i8 { +fn scene_init(program: &mut shader::Program) { // Load the vertex shader, create a shader program and bind it unsafe { - citro3d_sys::C3D_BindProgram(program.as_raw()); + citro3d_sys::C3D_BindProgram(program.as_raw_mut()); // Configure the first fragment shading substage to just pass through the vertex color // See https://www.opengl.org/sdk/docs/man2/xhtml/glTexEnv.xml for more insight @@ -242,13 +226,5 @@ fn scene_init(program: &mut shader::Program) -> i8 { 0, ); citro3d_sys::C3D_TexEnvFunc(env, citro3d_sys::C3D_Both, ctru_sys::GPU_REPLACE); - - // Get the location of the uniforms - let projection_name = CStr::from_bytes_with_nul(b"projection\0").unwrap(); - - ctru_sys::shaderInstanceGetUniformLocation( - (*program.as_raw()).vertexShader, - projection_name.as_ptr(), - ) } } diff --git a/citro3d/src/lib.rs b/citro3d/src/lib.rs index c7dbe0e..68a25ae 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -5,11 +5,12 @@ pub mod attrib; pub mod buffer; pub mod error; +pub mod math; pub mod render; pub mod shader; -use citro3d_sys::C3D_FrameDrawOn; pub use error::{Error, Result}; +pub use math::Matrix; pub mod macros { //! Helper macros for working with shaders. @@ -53,7 +54,7 @@ impl Instance { /// Fails if the given target cannot be used for drawing. pub fn select_render_target(&mut self, target: &render::Target<'_>) -> Result<()> { let _ = self; - if unsafe { C3D_FrameDrawOn(target.as_raw()) } { + if unsafe { citro3d_sys::C3D_FrameDrawOn(target.as_raw()) } { Ok(()) } else { Err(Error::InvalidRenderTarget) @@ -121,6 +122,19 @@ impl Instance { ); } } + + // TODO: need separate versions for vertex/geometry and different dimensions? + // Maybe we could do something nicer with const generics, or something, although + // it will probably be tricker + pub fn update_vertex_uniform_mat4x4(&mut self, index: i8, matrix: &Matrix) { + unsafe { + citro3d_sys::C3D_FVUnifMtx4x4( + ctru_sys::GPU_VERTEX_SHADER, + index.into(), + matrix.as_raw(), + ) + } + } } impl Drop for Instance { @@ -130,3 +144,21 @@ impl Drop for Instance { } } } + +#[derive(Clone, Copy, Debug)] +#[non_exhaustive] +pub enum AspectRatio { + TopScreen, + BottomScreen, + Other(f32), +} + +impl From for f32 { + fn from(ratio: AspectRatio) -> Self { + match ratio { + AspectRatio::TopScreen => citro3d_sys::C3D_AspectRatioTop as f32, + AspectRatio::BottomScreen => citro3d_sys::C3D_AspectRatioBot as f32, + AspectRatio::Other(ratio) => ratio, + } + } +} diff --git a/citro3d/src/math.rs b/citro3d/src/math.rs new file mode 100644 index 0000000..0ee26f1 --- /dev/null +++ b/citro3d/src/math.rs @@ -0,0 +1,87 @@ +//! Safe wrappers for working with matrix and vector types provided by `citro3d`. + +use std::mem::MaybeUninit; + +use crate::AspectRatio; + +/// A 4-vector of [`u8`]s. +pub struct IntVec(citro3d_sys::C3D_IVec); + +/// A 4-vector of [`f32`]s. +pub struct FloatVec(citro3d_sys::C3D_FVec); + +/// A quaternion, internally represented the same way as [`FVec`]. +pub struct Quaternion(citro3d_sys::C3D_FQuat); + +/// A 4x4 row-major matrix of [`f32`]s. +pub struct Matrix(citro3d_sys::C3D_Mtx); + +/// Whether to use left-handed or right-handed coordinates for calculations. +#[derive(Clone, Copy, Debug)] +#[non_exhaustive] // This probably is exhaustive, but just in case +pub enum CoordinateSystem { + LeftHanded, + RightHanded, +} + +impl Matrix { + // TODO: this could probably be generalized with something like builder or options + // pattern. Should look and see what the different citro3d implementations look like + pub fn perspective_stereo_tilt( + fov_y: f32, + aspect_ratio: AspectRatio, + near: f32, + far: f32, + interocular_distance: f32, + /* better name ?? */ screen_depth: f32, + coordinates: CoordinateSystem, + ) -> Self { + let mut result = MaybeUninit::uninit(); + + let inner = unsafe { + citro3d_sys::Mtx_PerspStereoTilt( + result.as_mut_ptr(), + fov_y, + aspect_ratio.into(), + near, + far, + interocular_distance, + screen_depth, + matches!(coordinates, CoordinateSystem::LeftHanded), + ); + + result.assume_init() + }; + + Self(inner) + } + + pub fn perspective_tilt( + fov_y: f32, + aspect_ratio: AspectRatio, + near: f32, + far: f32, + coordinates: CoordinateSystem, + ) -> Self { + let mut result = MaybeUninit::uninit(); + + let inner = unsafe { + citro3d_sys::Mtx_PerspTilt( + result.as_mut_ptr(), + fov_y, + aspect_ratio.into(), + near, + far, + matches!(coordinates, CoordinateSystem::LeftHanded), + ); + + result.assume_init() + }; + + Self(inner) + } + + pub(crate) fn as_raw(&self) -> *const citro3d_sys::C3D_Mtx { + &self.0 + } +} diff --git a/citro3d/src/shader.rs b/citro3d/src/shader.rs index d3c0060..8500e2d 100644 --- a/citro3d/src/shader.rs +++ b/citro3d/src/shader.rs @@ -5,6 +5,7 @@ //! documentation for . use std::error::Error; +use std::ffi::CString; use std::mem::MaybeUninit; /// A PICA200 shader program. It may have one or both of: @@ -66,8 +67,30 @@ impl Program { } } + // TODO: newtype for index? + pub fn get_uniform_location(&self, name: &str) -> crate::Result { + let vertex_instance = unsafe { (*self.as_raw()).vertexShader }; + if vertex_instance.is_null() { + return Err(todo!()); + } + + let name = CString::new(name).map_err(|e| -> crate::Error { todo!() })?; + + let idx = + unsafe { ctru_sys::shaderInstanceGetUniformLocation(vertex_instance, name.as_ptr()) }; + + if idx < 0 { + Err(todo!()) + } else { + Ok(idx) + } + } // TODO: pub(crate) - pub fn as_raw(&mut self) -> *mut ctru_sys::shaderProgram_s { + pub fn as_raw(&self) -> *const ctru_sys::shaderProgram_s { + &self.program + } + + pub fn as_raw_mut(&mut self) -> *mut ctru_sys::shaderProgram_s { &mut self.program } } @@ -75,7 +98,7 @@ impl Program { impl Drop for Program { fn drop(&mut self) { unsafe { - let _ = ctru_sys::shaderProgramFree(self.as_raw()); + let _ = ctru_sys::shaderProgramFree(self.as_raw_mut()); } } }