Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic texenv APIs and fully safe triangle example (!) #33

Merged
merged 6 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions citro3d-sys/bindgen.sh

This file was deleted.

61 changes: 22 additions & 39 deletions citro3d/examples/triangle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use citro3d::macros::include_shader;
use citro3d::math::{AspectRatio, ClipPlanes, Matrix4, Projection, StereoDisplacement};
use citro3d::render::ClearFlags;
use citro3d::texenv;
use citro3d::{attrib, buffer, render, shader};
use ctru::prelude::*;
use ctru::services::gfx::{RawFrameBuffer, Screen, TopScreen3D};
Expand Down Expand Up @@ -33,20 +34,21 @@ struct Vertex {

static VERTICES: &[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),
},
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),
},
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),
},
];

static SHADER_BYTES: &[u8] = include_shader!("assets/vshader.pica");
const CLEAR_COLOR: u32 = 0x68_B0_D8_FF;

fn main() {
let mut soc = Soc::new().expect("failed to get SOC");
Expand Down Expand Up @@ -79,15 +81,22 @@ fn main() {
let shader = shader::Library::from_bytes(SHADER_BYTES).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);
vbo_data.extend_from_slice(VERTICES);

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();

Expand All @@ -100,18 +109,17 @@ fn main() {

instance.render_frame_with(|instance| {
let mut render_to = |target: &mut render::Target, projection| {
target.clear(ClearFlags::ALL, CLEAR_COLOR, 0);

instance
.select_render_target(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.set_attr_info(&attr_info);

instance.draw_arrays(buffer::Primitive::Triangles, vbo_idx);
instance.draw_arrays(buffer::Primitive::Triangles, vbo_data);
};

let Projections {
Expand All @@ -127,15 +135,10 @@ fn main() {
}
}

// sheeeesh, this sucks to type:
fn prepare_vbos<'buf, 'info, 'vbo>(
buf_info: &'info mut buffer::Info,
vbo_data: &'vbo [Vertex],
) -> (attrib::Info, buffer::Slice<'buf>)
where
'info: 'buf,
'vbo: 'buf,
{
fn prepare_vbos<'a>(
buf_info: &'a mut buffer::Info,
vbo_data: &'a [Vertex],
) -> (attrib::Info, buffer::Slice<'a>) {
// Configure attributes for use with the vertex shader
let mut attr_info = attrib::Info::new();

Expand Down Expand Up @@ -190,23 +193,3 @@ fn calculate_projections() -> Projections {
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 changes: 61 additions & 8 deletions citro3d/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@ pub mod error;
pub mod math;
pub mod render;
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 {
Expand All @@ -30,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::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.
Expand All @@ -51,7 +63,17 @@ impl Instance {
#[doc(alias = "C3D_Init")]
pub fn with_cmdbuf_size(size: usize) -> Result<Self> {
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)
}
Expand All @@ -73,7 +95,8 @@ impl 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_FrameEnd")]
pub fn render_frame_with(&mut self, f: impl FnOnce(&mut Self)) {
Expand Down Expand Up @@ -125,20 +148,29 @@ impl Instance {

/// Render primitives from the current vertex array buffer.
#[doc(alias = "C3D_DrawArrays")]
pub fn draw_arrays(&mut self, primitive: buffer::Primitive, index: buffer::Slice) {
self.set_buffer_info(index.info());
pub fn draw_arrays(&mut self, primitive: buffer::Primitive, vbo_data: buffer::Slice) {
self.set_buffer_info(vbo_data.info());

// TODO: should we also require the attrib info directly here?

unsafe {
citro3d_sys::C3D_DrawArrays(
primitive as ctru_sys::GPU_Primitive_t,
index.index(),
index.len(),
vbo_data.index(),
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.
///
/// # Example
Expand Down Expand Up @@ -174,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 {
Expand Down
32 changes: 30 additions & 2 deletions citro3d/src/math/projection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,42 @@ impl<Kind> Projection<Kind> {

/// Set the coordinate system's orientation for the projection.
/// 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
}

/// Set the screen rotation for the projection.
/// 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
}
Expand Down
8 changes: 2 additions & 6 deletions citro3d/src/shader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::uniform;
///
/// The PICA200 does not support user-programmable fragment shaders.
#[doc(alias = "shaderProgram_s")]
#[must_use]
pub struct Program {
program: ctru_sys::shaderProgram_s,
}
Expand Down Expand Up @@ -102,18 +103,13 @@ impl Program {
pub(crate) fn as_raw(&self) -> *const ctru_sys::shaderProgram_s {
&self.program
}

// TODO: pub(crate)
pub fn as_raw_mut(&mut self) -> *mut ctru_sys::shaderProgram_s {
&mut self.program
}
}

impl Drop for Program {
#[doc(alias = "shaderProgramFree")]
fn drop(&mut self) {
unsafe {
let _ = ctru_sys::shaderProgramFree(self.as_raw_mut());
let _ = ctru_sys::shaderProgramFree(self.as_raw().cast_mut());
}
}
}
Expand Down
Loading