Browse Source

Refactor matrix API and add doctests etc

Also add some doc aliases for citro3d functions. We could probably stand
to add more aliases to other wrappers too.
pull/27/head
Ian Chamberlain 1 year ago
parent
commit
2b1e1db28b
No known key found for this signature in database
GPG Key ID: AE5484D09405AA60
  1. 2
      citro3d-macros/src/lib.rs
  2. 44
      citro3d/examples/triangle.rs
  3. 1
      citro3d/src/buffer.rs
  4. 48
      citro3d/src/lib.rs
  5. 334
      citro3d/src/math.rs
  6. 2
      citro3d/src/shader.rs
  7. 18
      citro3d/src/uniform.rs

2
citro3d-macros/src/lib.rs

@ -1,3 +1,5 @@
//! Procedural macro helpers for `citro3d`.
// we're already nightly-only so might as well use unstable proc macro APIs. // we're already nightly-only so might as well use unstable proc macro APIs.
#![feature(proc_macro_span)] #![feature(proc_macro_span)]

44
citro3d/examples/triangle.rs

@ -4,9 +4,11 @@
#![feature(allocator_api)] #![feature(allocator_api)]
use citro3d::macros::include_shader; use citro3d::macros::include_shader;
use citro3d::math::{ClipPlane, CoordinateSystem, Matrix, Orientation, Stereoscopic}; use citro3d::math::{
use citro3d::render::{self, ClearFlags}; AspectRatio, ClipPlanes, CoordinateOrientation, Matrix, ScreenOrientation, StereoDisplacement,
use citro3d::{attrib, buffer, shader, AspectRatio}; };
use citro3d::render::ClearFlags;
use citro3d::{attrib, buffer, render, shader};
use ctru::prelude::*; use ctru::prelude::*;
use ctru::services::gfx::{RawFrameBuffer, Screen, TopScreen3D}; use ctru::services::gfx::{RawFrameBuffer, Screen, TopScreen3D};
@ -167,46 +169,34 @@ fn calculate_projections() -> Projections {
// TODO: it would be cool to allow playing around with these parameters on // TODO: it would be cool to allow playing around with these parameters on
// the fly with D-pad, etc. // the fly with D-pad, etc.
let slider_val = unsafe { ctru_sys::osGet3DSliderState() }; let slider_val = unsafe { ctru_sys::osGet3DSliderState() };
let interocular_distance = slider_val / 4.0; let interocular_distance = slider_val / 2.0;
let vertical_fov = 40.0_f32.to_radians(); let vertical_fov = 40.0_f32.to_radians();
let screen_depth = 2.0; let screen_depth = 2.0;
let clip_plane = ClipPlane { let clip_planes = ClipPlanes {
near: 0.01, near: 0.01,
far: 100.0, far: 100.0,
}; };
let stereoscopic = Stereoscopic::Stereo { let stereo = StereoDisplacement::new(interocular_distance, screen_depth);
interocular_distance,
screen_depth,
};
let left_eye = Matrix::perspective_projection(
vertical_fov,
AspectRatio::TopScreen,
Orientation::Natural,
clip_plane,
stereoscopic,
CoordinateSystem::LeftHanded,
);
let right_eye = Matrix::perspective_projection( let (left_eye, right_eye) = Matrix::stereo_projections(
vertical_fov, vertical_fov,
AspectRatio::TopScreen, AspectRatio::TopScreen,
Orientation::Natural, ScreenOrientation::Rotated,
clip_plane, clip_planes,
stereoscopic.invert(), CoordinateOrientation::LeftHanded,
CoordinateSystem::LeftHanded, stereo,
); );
let center = Matrix::perspective_projection( let center = Matrix::perspective_projection(
vertical_fov, vertical_fov,
AspectRatio::BottomScreen, AspectRatio::BottomScreen,
Orientation::Natural, ScreenOrientation::Rotated,
clip_plane, clip_planes,
Stereoscopic::Mono, CoordinateOrientation::LeftHanded,
CoordinateSystem::LeftHanded, None,
); );
Projections { Projections {

1
citro3d/src/buffer.rs

@ -100,7 +100,6 @@ impl Info {
/// ///
/// * if `vbo_data` is not allocated with the [`ctru::linear`] allocator /// * if `vbo_data` is not allocated with the [`ctru::linear`] allocator
/// * if the maximum number (12) of VBOs are already registered /// * if the maximum number (12) of VBOs are already registered
///
pub fn add<'this, 'vbo, 'idx, T>( pub fn add<'this, 'vbo, 'idx, T>(
&'this mut self, &'this mut self,
vbo_data: &'vbo [T], vbo_data: &'vbo [T],

48
citro3d/src/lib.rs

@ -126,11 +126,37 @@ impl Instance {
} }
} }
pub fn bind_vertex_uniform(&mut self, index: uniform::Index, uniform: &impl Uniform) { /// Bind a uniform to the given `index` in the vertex shader for the next draw call.
///
/// # Example
///
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use citro3d::{uniform, Matrix};
/// #
/// # let mut instance = citro3d::Instance::new().unwrap();
/// let idx = uniform::Index::from(0);
/// 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); uniform.bind(self, shader::Type::Vertex, index);
} }
pub fn bind_geometry_uniform(&mut self, index: uniform::Index, uniform: &impl Uniform) { /// Bind a uniform to the given `index` in the geometry shader for the next draw call.
///
/// # Example
///
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use citro3d::{uniform, Matrix};
/// #
/// # let mut instance = citro3d::Instance::new().unwrap();
/// let idx = uniform::Index::from(0);
/// 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); uniform.bind(self, shader::Type::Geometry, index);
} }
} }
@ -142,21 +168,3 @@ impl Drop for Instance {
} }
} }
} }
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub enum AspectRatio {
TopScreen,
BottomScreen,
Other(f32),
}
impl From<AspectRatio> 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,
}
}
}

334
citro3d/src/math.rs

@ -2,133 +2,303 @@
use std::mem::MaybeUninit; use std::mem::MaybeUninit;
use crate::AspectRatio;
/// A 4-vector of `u8`s. /// A 4-vector of `u8`s.
pub struct IntVec(citro3d_sys::C3D_IVec); #[doc(alias = "C3D_IVec")]
pub struct IVec(citro3d_sys::C3D_IVec);
/// A 4-vector of `f32`s. /// A 4-vector of `f32`s.
pub struct FloatVec(citro3d_sys::C3D_FVec); #[doc(alias = "C3D_FVec")]
pub struct FVec(citro3d_sys::C3D_FVec);
/// A quaternion, internally represented the same way as [`FloatVec`]. /// A quaternion, internally represented the same way as [`FVec`].
pub struct Quaternion(citro3d_sys::C3D_FQuat); #[doc(alias = "C3D_FQuat")]
pub struct FQuat(citro3d_sys::C3D_FQuat);
/// A 4x4 row-major matrix of `f32`s. /// A 4x4 row-major matrix of `f32`s.
#[doc(alias = "C3D_Mtx")]
pub struct Matrix(citro3d_sys::C3D_Mtx); pub struct Matrix(citro3d_sys::C3D_Mtx);
impl Matrix { impl Matrix {
// TODO: does it make sense to have a helper that builds both left and right /// Construct the identity matrix.
// eyes for stereoscopic at the same time? #[doc(alias = "Mtx_Identity")]
pub fn identity() -> Self {
let mut out = MaybeUninit::uninit();
unsafe {
citro3d_sys::Mtx_Identity(out.as_mut_ptr());
Self(out.assume_init())
}
}
/// Construct a projection matrix suitable for projecting 3D world space onto /// Construct a projection matrix suitable for projecting 3D world space onto
/// the 3DS screens. /// the 3DS screens.
///
/// # Parameters
///
/// * `vertical_fov_radians`: the vertical field of view, measured in radians
/// * `aspect_ratio`: The aspect ratio of the projection
/// * `orientation`: the orientation of the projection with respect to the screen
/// * `coordinates`: the handedness of the coordinate system to use
/// * `stereo`: if specified, the offset to displace the projection by
/// for stereoscopic rendering.
///
/// # Examples
///
/// ```
/// # use citro3d::math::*;
/// # use std::f32::consts::PI;
/// #
/// # let _runner = test_runner::GdbRunner::default();
///
/// let clip_planes = ClipPlanes {
/// near: 0.01,
/// far: 100.0,
/// };
///
/// let center = Matrix::perspective_projection(
/// PI / 4.0,
/// AspectRatio::BottomScreen,
/// ScreenOrientation::Rotated,
/// clip_planes,
/// CoordinateOrientation::LeftHanded,
/// None,
/// );
///
/// let right_eye = Matrix::perspective_projection(
/// PI / 4.0,
/// AspectRatio::BottomScreen,
/// ScreenOrientation::Rotated,
/// clip_planes,
/// CoordinateOrientation::LeftHanded,
/// Some(StereoDisplacement {
/// displacement: 1.0,
/// screen_depth: 2.0,
/// }),
/// );
/// ```
#[doc(alias = "Mtx_Persp")]
#[doc(alias = "Mtx_PerspTilt")]
pub fn perspective_projection( pub fn perspective_projection(
vertical_fov: f32, vertical_fov_radians: f32,
aspect_ratio: AspectRatio, aspect_ratio: AspectRatio,
orientation: Orientation, orientation: ScreenOrientation,
clip_plane: ClipPlane, clip_plane: ClipPlanes,
stereo: Stereoscopic, coordinates: CoordinateOrientation,
coordinates: CoordinateSystem, stereo: Option<StereoDisplacement>,
) -> Self { ) -> Self {
let (make_mtx_persp, make_mtx_stereo); let mut result = MaybeUninit::uninit();
let initialize_mtx: &dyn Fn(_, _, _, _, _, _) -> _ = match stereo { let left_handed = matches!(coordinates, CoordinateOrientation::LeftHanded);
Stereoscopic::Mono => {
let make_mtx = match orientation {
Orientation::Natural => citro3d_sys::Mtx_PerspTilt,
Orientation::HardwareDefault => citro3d_sys::Mtx_Persp,
};
make_mtx_persp = move |a, b, c, d, e, f| unsafe { make_mtx(a, b, c, d, e, f) }; if let Some(stereo) = stereo {
&make_mtx_persp let initialize_mtx = orientation.perpsective_stereo_builder();
unsafe {
initialize_mtx(
result.as_mut_ptr(),
vertical_fov_radians,
aspect_ratio.into(),
clip_plane.near,
clip_plane.far,
stereo.displacement,
stereo.screen_depth,
left_handed,
);
} }
Stereoscopic::Stereo { } else {
interocular_distance, let initialize_mtx = orientation.perspective_mono_builder();
screen_depth, unsafe {
} => { initialize_mtx(
let make_mtx = match orientation { result.as_mut_ptr(),
Orientation::Natural => citro3d_sys::Mtx_PerspStereoTilt, vertical_fov_radians,
Orientation::HardwareDefault => citro3d_sys::Mtx_PerspStereo, aspect_ratio.into(),
}; clip_plane.near,
clip_plane.far,
make_mtx_stereo = move |a, b, c, d, e, f| unsafe { left_handed,
make_mtx(a, b, c, d, interocular_distance, screen_depth, e, f) );
};
&make_mtx_stereo
} }
}; }
let left_handed = matches!(coordinates, CoordinateSystem::LeftHanded);
let mut result = MaybeUninit::uninit();
initialize_mtx(
result.as_mut_ptr(),
vertical_fov,
aspect_ratio.into(),
clip_plane.near,
clip_plane.far,
left_handed,
);
let inner = unsafe { result.assume_init() }; let inner = unsafe { result.assume_init() };
Self(inner) Self(inner)
} }
/// Helper function to build both eyes' perspective projection matrices
/// at once. See [`perspective_projection`] for a description of each
/// parameter.
///
/// ```
/// # use std::f32::consts::PI;
/// # use citro3d::math::*;
/// #
/// # let _runner = test_runner::GdbRunner::default();
///
/// let (left_eye, right_eye) = Matrix::stereo_projections(
/// PI / 4.0,
/// AspectRatio::TopScreen,
/// ScreenOrientation::Rotated,
/// ClipPlanes {
/// near: 0.01,
/// far: 100.0,
/// },
/// CoordinateOrientation::LeftHanded,
/// StereoDisplacement::new(0.5, 2.0),
/// );
/// ```
///
/// [`perspective_projection`]: Self::perspective_projection
#[doc(alias = "Mtx_PerspStereo")]
#[doc(alias = "Mtx_PerspStereoTilt")]
pub fn stereo_projections(
vertical_fov_radians: f32,
aspect_ratio: AspectRatio,
orientation: ScreenOrientation,
clip_plane: ClipPlanes,
coordinates: CoordinateOrientation,
(left_eye, right_eye): (StereoDisplacement, StereoDisplacement),
) -> (Self, Self) {
let left = Self::perspective_projection(
vertical_fov_radians,
aspect_ratio,
orientation,
clip_plane,
coordinates,
Some(left_eye),
);
let right = Self::perspective_projection(
vertical_fov_radians,
aspect_ratio,
orientation,
clip_plane,
coordinates,
Some(right_eye),
);
(left, right)
}
pub(crate) fn as_raw(&self) -> *const citro3d_sys::C3D_Mtx { pub(crate) fn as_raw(&self) -> *const citro3d_sys::C3D_Mtx {
&self.0 &self.0
} }
} }
/// Whether to use left-handed or right-handed coordinates for calculations. /// The [orientation](https://en.wikipedia.org/wiki/Orientation_(geometry))
/// (or "handedness") of the coordinate system.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum CoordinateSystem { pub enum CoordinateOrientation {
/// A left-handed coordinate system.
LeftHanded, LeftHanded,
/// A right-handed coordinate system.
RightHanded, RightHanded,
} }
/// Whether to rotate a projection to account for the 3DS screen configuration. /// Whether to rotate a projection to account for the 3DS screen orientation.
/// Both screens on the 3DS are oriented such that the "top" of the screen is /// Both screens on the 3DS are oriented such that the "top-left" of the screen
/// on the [left | right] ? side of the device when it's held normally, so /// in framebuffer coordinates is the physical bottom-left of the screen
/// projections must account for this extra rotation to display in the correct /// (i.e. the "width" is smaller than the "height").
/// orientation.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum Orientation { pub enum ScreenOrientation {
/// Rotate the projection 90° to account for the 3DS screen rotation. /// Rotate 90° clockwise to account for the 3DS screen rotation. Most
Natural, /// applications will use this variant.
/// Don't rotate the projection at all. Rotated,
HardwareDefault, /// Do not apply any extra rotation to the projection.
None,
} }
#[derive(Clone, Copy, Debug)] impl ScreenOrientation {
// TODO: better name fn perspective_mono_builder(
pub enum Stereoscopic { self,
Mono, ) -> unsafe extern "C" fn(*mut citro3d_sys::C3D_Mtx, f32, f32, f32, f32, bool) {
Stereo { match self {
interocular_distance: f32, Self::Rotated => citro3d_sys::Mtx_PerspTilt,
// TODO: better name? At least docstring Self::None => citro3d_sys::Mtx_Persp,
screen_depth: f32, }
}, }
}
impl Stereoscopic { fn perpsective_stereo_builder(
/// Flip the stereoscopic projection for the opposite eye. self,
pub fn invert(self) -> Self { ) -> unsafe extern "C" fn(*mut citro3d_sys::C3D_Mtx, f32, f32, f32, f32, f32, f32, bool) {
match self { match self {
Self::Stereo { Self::Rotated => citro3d_sys::Mtx_PerspStereoTilt,
interocular_distance, Self::None => citro3d_sys::Mtx_PerspStereo,
screen_depth,
} => Self::Stereo {
interocular_distance: -interocular_distance,
screen_depth,
},
mono => mono,
} }
} }
// TODO: orthographic projections
fn ortho_builder(
self,
) -> unsafe extern "C" fn(*mut citro3d_sys::C3D_Mtx, f32, f32, f32, f32, f32, f32, bool) {
match self {
Self::Rotated => citro3d_sys::Mtx_OrthoTilt,
Self::None => citro3d_sys::Mtx_Ortho,
}
}
}
/// Configuration for calculating stereoscopic projections.
#[derive(Clone, Copy, Debug)]
pub struct StereoDisplacement {
/// The horizontal offset of the eye from center. Negative values
/// correspond to the left eye, and positive values to the right eye.
pub displacement: f32,
/// The position of the screen, which determines the focal length. Objects
/// closer than this depth will appear to pop out of the screen, and objects
/// further than this will appear inside the screen.
pub screen_depth: f32,
}
impl StereoDisplacement {
/// Construct displacement for the left and right eyes simulataneously.
/// The given `interocular_distance` describes the distance between the two
/// rendered "eyes". A negative value will be treated the same as a positive
/// value of the same magnitude.
///
/// See struct documentation for details about the
/// [`screen_depth`](Self::screen_depth) parameter.
pub fn new(interocular_distance: f32, screen_depth: f32) -> (Self, Self) {
let displacement = interocular_distance.abs() / 2.0;
let left_eye = Self {
displacement: -displacement,
screen_depth,
};
let right_eye = Self {
displacement,
screen_depth,
};
(left_eye, right_eye)
}
} }
/// Configuration for the [frustum](https://en.wikipedia.org/wiki/Viewing_frustum)
/// of a perspective projection.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct ClipPlane { pub struct ClipPlanes {
/// The z-depth of the near clip plane.
pub near: f32, pub near: f32,
/// The z-depth of the far clip plane.
pub far: f32, pub far: f32,
} }
/// The aspect ratio of a projection plane.
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub enum AspectRatio {
/// The aspect ratio of the 3DS' top screen (per-eye).
#[doc(alias = "C3D_AspectRatioTop")]
TopScreen,
/// The aspect ratio of the 3DS' bottom screen.
#[doc(alias = "C3D_AspectRatioBot")]
BottomScreen,
/// A custom aspect ratio (should be calcualted as `width / height`).
Other(f32),
}
impl From<AspectRatio> 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,
}
}
}

2
citro3d/src/shader.rs

@ -115,7 +115,9 @@ impl Drop for Program {
/// The type of a shader. /// The type of a shader.
#[repr(u32)] #[repr(u32)]
pub enum Type { pub enum Type {
/// A vertex shader.
Vertex = ctru_sys::GPU_VERTEX_SHADER, Vertex = ctru_sys::GPU_VERTEX_SHADER,
/// A geometry shader.
Geometry = ctru_sys::GPU_GEOMETRY_SHADER, Geometry = ctru_sys::GPU_GEOMETRY_SHADER,
} }

18
citro3d/src/uniform.rs

@ -1,5 +1,9 @@
//! Common definitions for binding uniforms to shaders. This is primarily
//! done by implementing the [`Uniform`] trait for a given type.
use crate::{shader, Instance, Matrix}; use crate::{shader, Instance, Matrix};
/// The index of a uniform within a [`shader::Program`].
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct Index(i8); pub struct Index(i8);
@ -19,15 +23,21 @@ mod private {
use crate::Matrix; use crate::Matrix;
pub trait Sealed {} pub trait Sealed {}
impl Sealed for Matrix {} impl Sealed for &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 trait Uniform: private::Sealed { pub trait Uniform: private::Sealed {
fn bind(&self, instance: &mut Instance, shader_type: shader::Type, index: Index); /// 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 { impl Uniform for &Matrix {
fn bind(&self, _instance: &mut Instance, type_: shader::Type, index: Index) { #[doc(alias = "C3D_FVUnifMtx4x4")]
fn bind(self, _instance: &mut Instance, type_: shader::Type, index: Index) {
unsafe { citro3d_sys::C3D_FVUnifMtx4x4(type_.into(), index.into(), self.as_raw()) } unsafe { citro3d_sys::C3D_FVUnifMtx4x4(type_.into(), index.into(), self.as_raw()) }
} }
} }

Loading…
Cancel
Save