diff --git a/citro3d/examples/triangle.rs b/citro3d/examples/triangle.rs index ca1358a..4cc50b9 100644 --- a/citro3d/examples/triangle.rs +++ b/citro3d/examples/triangle.rs @@ -6,7 +6,7 @@ use citro3d::macros::include_shader; use citro3d::math::{AspectRatio, ClipPlanes, Matrix4, Projection, StereoDisplacement}; use citro3d::render::ClearFlags; -use citro3d::texenv::{self, CombineFunc, TexEnv}; +use citro3d::texenv; use citro3d::{attrib, buffer, render, shader}; use ctru::prelude::*; use ctru::services::gfx::{RawFrameBuffer, Screen, TopScreen3D}; @@ -91,9 +91,10 @@ fn main() { // 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 - TexEnv::get(&mut instance, texenv::Id(0)) + instance + .texenv(texenv::Stage::new(0).unwrap()) .src(texenv::Mode::BOTH, texenv::Source::PrimaryColor, None, None) - .func(texenv::Mode::BOTH, CombineFunc::Replace); + .func(texenv::Mode::BOTH, texenv::CombineFunc::Replace); let projection_uniform_idx = program.get_uniform("projection").unwrap(); diff --git a/citro3d/src/lib.rs b/citro3d/src/lib.rs index 3cdd983..797d62f 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -18,8 +18,12 @@ pub mod shader; pub mod texenv; pub mod uniform; +use std::cell::OnceCell; +use std::fmt; + pub use error::{Error, Result}; +use self::texenv::TexEnv; use self::uniform::Uniform; pub mod macros { @@ -31,8 +35,15 @@ pub mod macros { /// should instantiate to use this library. #[non_exhaustive] #[must_use] -#[derive(Debug)] -pub struct Instance; +pub struct Instance { + texenvs: [OnceCell; texenv::TEXENV_COUNT], +} + +impl fmt::Debug for Instance { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Instance").finish_non_exhaustive() + } +} impl Instance { /// Initialize the default `citro3d` instance. @@ -52,7 +63,17 @@ impl Instance { #[doc(alias = "C3D_Init")] pub fn with_cmdbuf_size(size: usize) -> Result { if unsafe { citro3d_sys::C3D_Init(size) } { - Ok(Self) + Ok(Self { + texenvs: [ + // thank goodness there's only six of them! + OnceCell::new(), + OnceCell::new(), + OnceCell::new(), + OnceCell::new(), + OnceCell::new(), + OnceCell::new(), + ], + }) } else { Err(Error::FailedToInitialize) } @@ -185,6 +206,27 @@ impl Instance { pub fn bind_geometry_uniform(&mut self, index: uniform::Index, uniform: impl Uniform) { uniform.bind(self, shader::Type::Geometry, index); } + + /// Retrieve the [`TexEnv`] for the given stage, initializing it first if necessary. + /// + /// # Example + /// + /// ``` + /// # use citro3d::texenv; + /// # let _runner = test_runner::GdbRunner::default(); + /// # let mut instance = citro3d::Instance::new().unwrap(); + /// let stage0 = texenv::Stage::new(0).unwrap(); + /// let texenv0 = instance.texenv(stage0); + /// ``` + #[doc(alias = "C3D_GetTexEnv")] + #[doc(alias = "C3D_TexEnvInit")] + pub fn texenv(&mut self, stage: texenv::Stage) -> &mut texenv::TexEnv { + let texenv = &mut self.texenvs[stage.0]; + texenv.get_or_init(|| TexEnv::new(stage)); + // We have to do this weird unwrap to get a mutable reference, + // since there is no `get_mut_or_init` or equivalent + texenv.get_mut().unwrap() + } } impl Drop for Instance { diff --git a/citro3d/src/texenv.rs b/citro3d/src/texenv.rs index fc5798a..222a645 100644 --- a/citro3d/src/texenv.rs +++ b/citro3d/src/texenv.rs @@ -1,52 +1,70 @@ -//! Texture environment support. See `` for more information. +//! Texture combiner support. See +//! for more details. use bitflags::bitflags; -use crate::Instance; - +/// A texture combiner, also called a "texture environment" (hence the struct name). +/// See also [`texenv.h` documentation](https://oreo639.github.io/citro3d/texenv_8h.html). #[doc(alias = "C3D_TexEnv")] -pub struct TexEnv<'a> { +pub struct TexEnv { raw: *mut citro3d_sys::C3D_TexEnv, - _instance: &'a mut Instance, } -impl<'a> TexEnv<'a> { - #[doc(alias = "C3D_TexEnvInit")] - pub fn set(&self, _instance: &mut Instance, id: Id) { - unsafe { - // SAFETY: pointee is only copied from, not modified - citro3d_sys::C3D_SetTexEnv(id.0, self.raw); - } - } +// https://oreo639.github.io/citro3d/texenv_8h.html#a9eda91f8e7252c91f873b1d43e3728b6 +pub(crate) const TEXENV_COUNT: usize = 6; - pub fn get(instance: &'a mut Instance, id: Id) -> Self { - unsafe { +impl TexEnv { + pub(crate) fn new(stage: Stage) -> Self { + let mut result = unsafe { Self { - raw: citro3d_sys::C3D_GetTexEnv(id.0), - _instance: instance, + raw: citro3d_sys::C3D_GetTexEnv(stage.0 as _), } + }; + result.reset(); + result + } + + /// Re-initialize the texture combiner to its default state. + pub fn reset(&mut self) { + unsafe { + citro3d_sys::C3D_TexEnvInit(self.raw); } } + /// Configure the source values of the texture combiner. + /// + /// # Parameters + /// + /// - `mode`: which [`Mode`]\(s) to set the sourc operand(s) for. + /// - `source0`: the first [`Source`] operand to the texture combiner + /// - `source1` and `source2`: optional additional [`Source`] operands to use + #[doc(alias = "C3D_TexEnvSrc")] pub fn src( &mut self, mode: Mode, - s1: Source, - s2: Option, - s3: Option, + source0: Source, + source1: Option, + source2: Option, ) -> &mut Self { unsafe { citro3d_sys::C3D_TexEnvSrc( self.raw, mode.bits(), - s1 as _, - s2.unwrap_or(Source::PrimaryColor) as _, - s3.unwrap_or(Source::PrimaryColor) as _, - ) + source0 as _, + source1.unwrap_or(Source::PrimaryColor) as _, + source2.unwrap_or(Source::PrimaryColor) as _, + ); } self } + /// Configure the texture combination function. + /// + /// # Parameters + /// + /// - `mode`: the [`Mode`]\(s) the combination function will apply to. + /// - `func`: the [`CombineFunc`] used to combine textures. + #[doc(alias = "C3D_TexEnvFunc")] pub fn func(&mut self, mode: Mode, func: CombineFunc) -> &mut Self { unsafe { citro3d_sys::C3D_TexEnvFunc(self.raw, mode.bits(), func as _); @@ -57,17 +75,23 @@ impl<'a> TexEnv<'a> { } bitflags! { + /// Whether to operate on colors, alpha values, or both. #[doc(alias = "C3D_TexEnvMode")] pub struct Mode: citro3d_sys::C3D_TexEnvMode { + #[allow(missing_docs)] const RGB = citro3d_sys::C3D_RGB; + #[allow(missing_docs)] const ALPHA = citro3d_sys::C3D_Alpha; + #[allow(missing_docs)] const BOTH = citro3d_sys::C3D_Both; } } +/// A source operand of a [`TexEnv`]'s texture combination. #[derive(Debug, Clone, Copy)] #[repr(u32)] #[doc(alias = "GPU_TEVSRC")] +#[allow(missing_docs)] pub enum Source { PrimaryColor = ctru_sys::GPU_PRIMARY_COLOR, FragmentPrimaryColor = ctru_sys::GPU_FRAGMENT_PRIMARY_COLOR, @@ -81,9 +105,11 @@ pub enum Source { Previous = ctru_sys::GPU_PREVIOUS, } +/// The combination function to apply to the [`TexEnv`] operands. #[derive(Debug, Clone, Copy)] #[repr(u32)] #[doc(alias = "GPU_COMBINEFUNC")] +#[allow(missing_docs)] pub enum CombineFunc { Replace = ctru_sys::GPU_REPLACE, Modulate = ctru_sys::GPU_MODULATE, @@ -95,5 +121,15 @@ pub enum CombineFunc { Dot3Rgba = ctru_sys::GPU_DOT3_RGBA, } +/// A texture combination stage identifier. This index doubles as the order +/// in which texture combinations will be applied. +// (I think?) #[derive(Copy, Clone, Debug)] -pub struct Id(/* TODO maybe non pub? idk */ pub libc::c_int); +pub struct Stage(pub(crate) usize); + +impl Stage { + /// Get a stage index. Valid indices range from 0 to 5. + pub fn new(index: usize) -> Option { + (index < 6).then_some(Self(index)) + } +}