Browse Source

Merge pull request #33 from rust3ds/feature/texenv

pull/39/head
Ian Chamberlain 1 year ago committed by GitHub
parent
commit
b02384fc33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      citro3d-sys/bindgen.sh
  2. 61
      citro3d/examples/triangle.rs
  3. 69
      citro3d/src/lib.rs
  4. 32
      citro3d/src/math/projection.rs
  5. 8
      citro3d/src/shader.rs
  6. 132
      citro3d/src/texenv.rs

5
citro3d-sys/bindgen.sh

@ -1,5 +0,0 @@
#!/usr/bin/env bash
set -euxo pipefail
cargo run --package bindgen-citro3d > src/bindings.rs

61
citro3d/examples/triangle.rs

@ -6,6 +6,7 @@
use citro3d::macros::include_shader; use citro3d::macros::include_shader;
use citro3d::math::{AspectRatio, ClipPlanes, Matrix4, Projection, StereoDisplacement}; use citro3d::math::{AspectRatio, ClipPlanes, Matrix4, Projection, StereoDisplacement};
use citro3d::render::ClearFlags; use citro3d::render::ClearFlags;
use citro3d::texenv;
use citro3d::{attrib, buffer, render, shader}; 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};
@ -33,20 +34,21 @@ struct Vertex {
static VERTICES: &[Vertex] = &[ static VERTICES: &[Vertex] = &[
Vertex { Vertex {
pos: Vec3::new(0.0, 0.5, 3.0), pos: Vec3::new(0.0, 0.5, -3.0),
color: Vec3::new(1.0, 0.0, 0.0), color: Vec3::new(1.0, 0.0, 0.0),
}, },
Vertex { Vertex {
pos: Vec3::new(-0.5, -0.5, 3.0), pos: Vec3::new(-0.5, -0.5, -3.0),
color: Vec3::new(0.0, 1.0, 0.0), color: Vec3::new(0.0, 1.0, 0.0),
}, },
Vertex { Vertex {
pos: Vec3::new(0.5, -0.5, 3.0), pos: Vec3::new(0.5, -0.5, -3.0),
color: Vec3::new(0.0, 0.0, 1.0), color: Vec3::new(0.0, 0.0, 1.0),
}, },
]; ];
static SHADER_BYTES: &[u8] = include_shader!("assets/vshader.pica"); static SHADER_BYTES: &[u8] = include_shader!("assets/vshader.pica");
const CLEAR_COLOR: u32 = 0x68_B0_D8_FF;
fn main() { fn main() {
let mut soc = Soc::new().expect("failed to get SOC"); let mut soc = Soc::new().expect("failed to get SOC");
@ -79,15 +81,22 @@ fn main() {
let shader = shader::Library::from_bytes(SHADER_BYTES).unwrap(); let shader = shader::Library::from_bytes(SHADER_BYTES).unwrap();
let vertex_shader = shader.get(0).unwrap(); let vertex_shader = shader.get(0).unwrap();
let mut program = shader::Program::new(vertex_shader).unwrap(); let program = shader::Program::new(vertex_shader).unwrap();
instance.bind_program(&program);
let mut vbo_data = Vec::with_capacity_in(VERTICES.len(), ctru::linear::LinearAllocator); let mut vbo_data = Vec::with_capacity_in(VERTICES.len(), ctru::linear::LinearAllocator);
vbo_data.extend_from_slice(VERTICES); vbo_data.extend_from_slice(VERTICES);
let mut buf_info = buffer::Info::new(); let mut buf_info = buffer::Info::new();
let (attr_info, vbo_idx) = prepare_vbos(&mut buf_info, &vbo_data); let (attr_info, vbo_data) = prepare_vbos(&mut buf_info, &vbo_data);
scene_init(&mut program); // 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
let stage0 = texenv::Stage::new(0).unwrap();
instance
.texenv(stage0)
.src(texenv::Mode::BOTH, texenv::Source::PrimaryColor, None, None)
.func(texenv::Mode::BOTH, texenv::CombineFunc::Replace);
let projection_uniform_idx = program.get_uniform("projection").unwrap(); let projection_uniform_idx = program.get_uniform("projection").unwrap();
@ -100,18 +109,17 @@ fn main() {
instance.render_frame_with(|instance| { instance.render_frame_with(|instance| {
let mut render_to = |target: &mut render::Target, projection| { let mut render_to = |target: &mut render::Target, projection| {
target.clear(ClearFlags::ALL, CLEAR_COLOR, 0);
instance instance
.select_render_target(target) .select_render_target(target)
.expect("failed to set render target"); .expect("failed to set render target");
let clear_color: u32 = 0x7F_7F_7F_FF;
target.clear(ClearFlags::ALL, clear_color, 0);
instance.bind_vertex_uniform(projection_uniform_idx, projection); instance.bind_vertex_uniform(projection_uniform_idx, projection);
instance.set_attr_info(&attr_info); instance.set_attr_info(&attr_info);
instance.draw_arrays(buffer::Primitive::Triangles, vbo_idx); instance.draw_arrays(buffer::Primitive::Triangles, vbo_data);
}; };
let Projections { let Projections {
@ -127,15 +135,10 @@ fn main() {
} }
} }
// sheeeesh, this sucks to type: fn prepare_vbos<'a>(
fn prepare_vbos<'buf, 'info, 'vbo>( buf_info: &'a mut buffer::Info,
buf_info: &'info mut buffer::Info, vbo_data: &'a [Vertex],
vbo_data: &'vbo [Vertex], ) -> (attrib::Info, buffer::Slice<'a>) {
) -> (attrib::Info, buffer::Slice<'buf>)
where
'info: 'buf,
'vbo: 'buf,
{
// Configure attributes for use with the vertex shader // Configure attributes for use with the vertex shader
let mut attr_info = attrib::Info::new(); let mut attr_info = attrib::Info::new();
@ -190,23 +193,3 @@ fn calculate_projections() -> Projections {
center, center,
} }
} }
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_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
let env = citro3d_sys::C3D_GetTexEnv(0);
citro3d_sys::C3D_TexEnvInit(env);
citro3d_sys::C3D_TexEnvSrc(
env,
citro3d_sys::C3D_Both,
ctru_sys::GPU_PRIMARY_COLOR,
0,
0,
);
citro3d_sys::C3D_TexEnvFunc(env, citro3d_sys::C3D_Both, ctru_sys::GPU_REPLACE);
}
}

69
citro3d/src/lib.rs

@ -15,10 +15,15 @@ pub mod error;
pub mod math; pub mod math;
pub mod render; pub mod render;
pub mod shader; pub mod shader;
pub mod texenv;
pub mod uniform; pub mod uniform;
use std::cell::OnceCell;
use std::fmt;
pub use error::{Error, Result}; pub use error::{Error, Result};
use self::texenv::TexEnv;
use self::uniform::Uniform; use self::uniform::Uniform;
pub mod macros { pub mod macros {
@ -30,8 +35,15 @@ pub mod macros {
/// should instantiate to use this library. /// should instantiate to use this library.
#[non_exhaustive] #[non_exhaustive]
#[must_use] #[must_use]
#[derive(Debug)] pub struct Instance {
pub struct Instance; texenvs: [OnceCell<TexEnv>; 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 { impl Instance {
/// Initialize the default `citro3d` instance. /// Initialize the default `citro3d` instance.
@ -51,7 +63,17 @@ impl Instance {
#[doc(alias = "C3D_Init")] #[doc(alias = "C3D_Init")]
pub fn with_cmdbuf_size(size: usize) -> Result<Self> { pub fn with_cmdbuf_size(size: usize) -> Result<Self> {
if unsafe { citro3d_sys::C3D_Init(size) } { 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 { } else {
Err(Error::FailedToInitialize) Err(Error::FailedToInitialize)
} }
@ -73,7 +95,8 @@ impl Instance {
} }
/// Render a frame. The passed in function/closure can mutate the instance, /// Render a frame. The passed in function/closure can mutate the instance,
/// such as to [select a render target](Self::select_render_target). /// such as to [select a render target](Self::select_render_target)
/// or [bind a new shader program](Self::bind_program).
#[doc(alias = "C3D_FrameBegin")] #[doc(alias = "C3D_FrameBegin")]
#[doc(alias = "C3D_FrameEnd")] #[doc(alias = "C3D_FrameEnd")]
pub fn render_frame_with(&mut self, f: impl FnOnce(&mut Self)) { pub fn render_frame_with(&mut self, f: impl FnOnce(&mut Self)) {
@ -125,20 +148,29 @@ impl Instance {
/// Render primitives from the current vertex array buffer. /// Render primitives from the current vertex array buffer.
#[doc(alias = "C3D_DrawArrays")] #[doc(alias = "C3D_DrawArrays")]
pub fn draw_arrays(&mut self, primitive: buffer::Primitive, index: buffer::Slice) { pub fn draw_arrays(&mut self, primitive: buffer::Primitive, vbo_data: buffer::Slice) {
self.set_buffer_info(index.info()); self.set_buffer_info(vbo_data.info());
// TODO: should we also require the attrib info directly here? // TODO: should we also require the attrib info directly here?
unsafe { unsafe {
citro3d_sys::C3D_DrawArrays( citro3d_sys::C3D_DrawArrays(
primitive as ctru_sys::GPU_Primitive_t, primitive as ctru_sys::GPU_Primitive_t,
index.index(), vbo_data.index(),
index.len(), vbo_data.len(),
); );
} }
} }
/// Use the given [`shader::Program`] for subsequent draw calls.
pub fn bind_program(&mut self, program: &shader::Program) {
// SAFETY: AFAICT C3D_BindProgram just copies pointers from the given program,
// instead of mutating the pointee in any way that would cause UB
unsafe {
citro3d_sys::C3D_BindProgram(program.as_raw().cast_mut());
}
}
/// Bind a uniform to the given `index` in the vertex shader for the next draw call. /// Bind a uniform to the given `index` in the vertex shader for the next draw call.
/// ///
/// # Example /// # Example
@ -174,6 +206,27 @@ impl Instance {
pub fn bind_geometry_uniform(&mut self, index: uniform::Index, uniform: impl Uniform) { 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);
} }
/// 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 { impl Drop for Instance {

32
citro3d/src/math/projection.rs

@ -26,14 +26,42 @@ impl<Kind> Projection<Kind> {
/// Set the coordinate system's orientation for the projection. /// Set the coordinate system's orientation for the projection.
/// See [`CoordinateOrientation`] for more details. /// See [`CoordinateOrientation`] for more details.
pub fn coordinates(&mut self, orientation: CoordinateOrientation) -> &mut Self { ///
/// # Example
///
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use citro3d::math::{Projection, AspectRatio, CoordinateOrientation, Matrix4, ClipPlanes};
/// let clip_planes = ClipPlanes {
/// near: 0.1,
/// far: 100.0,
/// };
/// let mtx: Matrix4 = Projection::perspective(40.0, AspectRatio::TopScreen, clip_planes)
/// .coordinates(CoordinateOrientation::LeftHanded)
/// .into();
/// ```
pub fn coordinates(mut self, orientation: CoordinateOrientation) -> Self {
self.coordinates = orientation; self.coordinates = orientation;
self self
} }
/// Set the screen rotation for the projection. /// Set the screen rotation for the projection.
/// See [`ScreenOrientation`] for more details. /// See [`ScreenOrientation`] for more details.
pub fn screen(&mut self, orientation: ScreenOrientation) -> &mut Self { ///
/// # Example
///
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use citro3d::math::{Projection, AspectRatio, ScreenOrientation, Matrix4, ClipPlanes};
/// let clip_planes = ClipPlanes {
/// near: 0.1,
/// far: 100.0,
/// };
/// let mtx: Matrix4 = Projection::perspective(40.0, AspectRatio::TopScreen, clip_planes)
/// .screen(ScreenOrientation::None)
/// .into();
/// ```
pub fn screen(mut self, orientation: ScreenOrientation) -> Self {
self.rotation = orientation; self.rotation = orientation;
self self
} }

8
citro3d/src/shader.rs

@ -17,6 +17,7 @@ use crate::uniform;
/// ///
/// The PICA200 does not support user-programmable fragment shaders. /// The PICA200 does not support user-programmable fragment shaders.
#[doc(alias = "shaderProgram_s")] #[doc(alias = "shaderProgram_s")]
#[must_use]
pub struct Program { pub struct Program {
program: ctru_sys::shaderProgram_s, program: ctru_sys::shaderProgram_s,
} }
@ -102,18 +103,13 @@ impl Program {
pub(crate) fn as_raw(&self) -> *const ctru_sys::shaderProgram_s { pub(crate) fn as_raw(&self) -> *const ctru_sys::shaderProgram_s {
&self.program &self.program
} }
// TODO: pub(crate)
pub fn as_raw_mut(&mut self) -> *mut ctru_sys::shaderProgram_s {
&mut self.program
}
} }
impl Drop for Program { impl Drop for Program {
#[doc(alias = "shaderProgramFree")] #[doc(alias = "shaderProgramFree")]
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
let _ = ctru_sys::shaderProgramFree(self.as_raw_mut()); let _ = ctru_sys::shaderProgramFree(self.as_raw().cast_mut());
} }
} }
} }

132
citro3d/src/texenv.rs

@ -0,0 +1,132 @@
//! Texture combiner support. See <https://www.khronos.org/opengl/wiki/Texture_Combiners>
//! for more details.
use bitflags::bitflags;
/// 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(*mut citro3d_sys::C3D_TexEnv);
// https://oreo639.github.io/citro3d/texenv_8h.html#a9eda91f8e7252c91f873b1d43e3728b6
pub(crate) const TEXENV_COUNT: usize = 6;
impl TexEnv {
pub(crate) fn new(stage: Stage) -> Self {
let mut result = unsafe { Self(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.0);
}
}
/// 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,
source0: Source,
source1: Option<Source>,
source2: Option<Source>,
) -> &mut Self {
unsafe {
citro3d_sys::C3D_TexEnvSrc(
self.0,
mode.bits(),
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.0, mode.bits(), func as _);
}
self
}
}
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.
#[doc(alias = "GPU_TEVSRC")]
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy)]
#[repr(u32)]
#[non_exhaustive]
pub enum Source {
PrimaryColor = ctru_sys::GPU_PRIMARY_COLOR,
FragmentPrimaryColor = ctru_sys::GPU_FRAGMENT_PRIMARY_COLOR,
FragmentSecondaryColor = ctru_sys::GPU_FRAGMENT_SECONDARY_COLOR,
Texture0 = ctru_sys::GPU_TEXTURE0,
Texture1 = ctru_sys::GPU_TEXTURE1,
Texture2 = ctru_sys::GPU_TEXTURE2,
Texture3 = ctru_sys::GPU_TEXTURE3,
PreviousBuffer = ctru_sys::GPU_PREVIOUS_BUFFER,
Constant = ctru_sys::GPU_CONSTANT,
Previous = ctru_sys::GPU_PREVIOUS,
}
/// The combination function to apply to the [`TexEnv`] operands.
#[doc(alias = "GPU_COMBINEFUNC")]
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy)]
#[repr(u32)]
#[non_exhaustive]
pub enum CombineFunc {
Replace = ctru_sys::GPU_REPLACE,
Modulate = ctru_sys::GPU_MODULATE,
Add = ctru_sys::GPU_ADD,
AddSigned = ctru_sys::GPU_ADD_SIGNED,
Interpolate = ctru_sys::GPU_INTERPOLATE,
Subtract = ctru_sys::GPU_SUBTRACT,
Dot3Rgb = ctru_sys::GPU_DOT3_RGB,
// Added in libcrtu 2.3.0:
// 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 Stage(pub(crate) usize);
impl Stage {
/// Get a stage index. Valid indices range from 0 to 5.
pub fn new(index: usize) -> Option<Self> {
(index < 6).then_some(Self(index))
}
}
Loading…
Cancel
Save