From 9e045f3fa9a8c04a7afdb56802d7bf60c7c1135c Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Thu, 1 Feb 2024 12:54:07 +0000 Subject: [PATCH 1/3] feat: add indexed drawing --- citro3d/examples/cube.rs | 246 +++++++++++++++++++++++++++++++++++++++ citro3d/src/lib.rs | 51 +++++++- 2 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 citro3d/examples/cube.rs diff --git a/citro3d/examples/cube.rs b/citro3d/examples/cube.rs new file mode 100644 index 0000000..73c074c --- /dev/null +++ b/citro3d/examples/cube.rs @@ -0,0 +1,246 @@ +//! This example demonstrates the most basic usage of `citro3d`: rendering a simple +//! RGB triangle (sometimes called a "Hello triangle") to the 3DS screen. + +#![feature(allocator_api)] + +use citro3d::macros::include_shader; +use citro3d::math::{ + AspectRatio, ClipPlanes, CoordinateOrientation, FVec3, Matrix4, Projection, StereoDisplacement, +}; +use citro3d::render::ClearFlags; +use citro3d::{attrib, buffer, render, shader}; +use citro3d::{texenv, DrawingIndices}; +use ctru::prelude::*; +use ctru::services::gfx::{RawFrameBuffer, Screen, TopScreen3D}; + +#[repr(C)] +#[derive(Copy, Clone)] +struct Vec3 { + x: f32, + y: f32, + z: f32, +} + +impl Vec3 { + const fn new(x: f32, y: f32, z: f32) -> Self { + Self { x, y, z } + } +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct Vertex { + pos: Vec3, + color: Vec3, +} + +// borrowed from https://bevyengine.org/examples/3D%20Rendering/generate-custom-mesh/ +const VERTS: &[[f32; 3]] = &[ + // top (facing towards +y) + [-0.5, 0.5, -0.5], // vertex with index 0 + [0.5, 0.5, -0.5], // vertex with index 1 + [0.5, 0.5, 0.5], // etc. until 23 + [-0.5, 0.5, 0.5], + // bottom (-y) + [-0.5, -0.5, -0.5], + [0.5, -0.5, -0.5], + [0.5, -0.5, 0.5], + [-0.5, -0.5, 0.5], + // right (+x) + [0.5, -0.5, -0.5], + [0.5, -0.5, 0.5], + [0.5, 0.5, 0.5], // This vertex is at the same position as vertex with index 2, but they'll have different UV and normal + [0.5, 0.5, -0.5], + // left (-x) + [-0.5, -0.5, -0.5], + [-0.5, -0.5, 0.5], + [-0.5, 0.5, 0.5], + [-0.5, 0.5, -0.5], + // back (+z) + [-0.5, -0.5, 0.5], + [-0.5, 0.5, 0.5], + [0.5, 0.5, 0.5], + [0.5, -0.5, 0.5], + // forward (-z) + [-0.5, -0.5, -0.5], + [-0.5, 0.5, -0.5], + [0.5, 0.5, -0.5], + [0.5, -0.5, -0.5], +]; + +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"); + drop(soc.redirect_to_3dslink(true, true)); + + let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); + let mut hid = Hid::new().expect("Couldn't obtain HID controller"); + let apt = Apt::new().expect("Couldn't obtain APT controller"); + + let mut instance = citro3d::Instance::new().expect("failed to initialize Citro3D"); + + let top_screen = TopScreen3D::from(&gfx.top_screen); + + let (mut top_left, mut top_right) = top_screen.split_mut(); + + let RawFrameBuffer { width, height, .. } = top_left.raw_framebuffer(); + let mut top_left_target = + render::Target::new(width, height, top_left, None).expect("failed to create render target"); + + let RawFrameBuffer { width, height, .. } = top_right.raw_framebuffer(); + let mut top_right_target = render::Target::new(width, height, top_right, None) + .expect("failed to create render target"); + + let mut bottom_screen = gfx.bottom_screen.borrow_mut(); + let RawFrameBuffer { width, height, .. } = bottom_screen.raw_framebuffer(); + + let mut bottom_target = render::Target::new(width, height, bottom_screen, None) + .expect("failed to create bottom screen render target"); + + let shader = shader::Library::from_bytes(SHADER_BYTES).unwrap(); + let vertex_shader = shader.get(0).unwrap(); + + let program = shader::Program::new(vertex_shader).unwrap(); + instance.bind_program(&program); + let mut vbo_data = Vec::with_capacity_in(VERTS.len(), ctru::linear::LinearAllocator); + for vert in VERTS.iter().enumerate().map(|(i, v)| Vertex { + pos: Vec3 { + x: v[0], + y: v[1], + z: v[2], + }, + color: Vec3::new(i as f32 % 1., i as f32 % 1., i as f32 % 1.), + }) { + vbo_data.push(vert); + } + + let mut buf_info = buffer::Info::new(); + let (attr_info, vbo_data) = prepare_vbos(&mut buf_info, &vbo_data); + + // 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 camera_transform = Matrix4::looking_at( + FVec3::new(1.8, 1.8, 1.8), + FVec3::new(0.0, 0.0, 0.0), + FVec3::new(0.0, 1.0, 0.0), + CoordinateOrientation::RightHanded, + ); + + while apt.main_loop() { + hid.scan_input(); + + if hid.keys_down().contains(KeyPad::START) { + break; + } + + 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"); + + instance.bind_vertex_uniform( + projection_uniform_idx, + &(projection * camera_transform.clone()), + ); + + instance.set_attr_info(&attr_info); + unsafe { + instance.draw_elements( + buffer::Primitive::Triangles, + DrawingIndices::U16(&[ + 0, 3, 1, 1, 3, 2, // triangles making up the top (+y) facing side. + 4, 5, 7, 5, 6, 7, // bottom (-y) + 8, 11, 9, 9, 11, 10, // right (+x) + 12, 13, 15, 13, 14, 15, // left (-x) + 16, 19, 17, 17, 19, 18, // back (+z) + 20, 21, 23, 21, 22, 23, // forward (-z) + ]), + ); + } + + instance.draw_arrays(buffer::Primitive::Triangles, vbo_data); + }; + + let Projections { + left_eye, + right_eye, + center, + } = calculate_projections(); + + render_to(&mut top_left_target, &left_eye); + render_to(&mut top_right_target, &right_eye); + render_to(&mut bottom_target, ¢er); + }); + } +} + +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(); + + let reg0 = attrib::Register::new(0).unwrap(); + let reg1 = attrib::Register::new(1).unwrap(); + + attr_info + .add_loader(reg0, attrib::Format::Float, 3) + .unwrap(); + + attr_info + .add_loader(reg1, attrib::Format::Float, 3) + .unwrap(); + + let buf_idx = buf_info.add(vbo_data, &attr_info).unwrap(); + + (attr_info, buf_idx) +} + +struct Projections { + left_eye: Matrix4, + right_eye: Matrix4, + center: Matrix4, +} + +fn calculate_projections() -> Projections { + // TODO: it would be cool to allow playing around with these parameters on + // the fly with D-pad, etc. + let slider_val = ctru::os::current_3d_slider_state(); + let interocular_distance = slider_val / 2.0; + + let vertical_fov = 40.0_f32.to_radians(); + let screen_depth = 2.0; + + let clip_planes = ClipPlanes { + near: 0.01, + far: 100.0, + }; + + let (left, right) = StereoDisplacement::new(interocular_distance, screen_depth); + + let (left_eye, right_eye) = + Projection::perspective(vertical_fov, AspectRatio::TopScreen, clip_planes) + .stereo_matrices(left, right); + + let center = + Projection::perspective(vertical_fov, AspectRatio::BottomScreen, clip_planes).into(); + + Projections { + left_eye, + right_eye, + center, + } +} diff --git a/citro3d/src/lib.rs b/citro3d/src/lib.rs index c18e6fc..133f369 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -152,7 +152,6 @@ impl Instance { 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, @@ -161,6 +160,31 @@ impl Instance { ); } } + /// Indexed drawing + /// + /// # Safety + /// If `indices` goes out of scope before the current frame ends it will cause a use-after-free (possibly by the GPU) + #[doc(alias = "C3D_DrawElements")] + pub unsafe fn draw_elements<'a>( + &mut self, + primitive: buffer::Primitive, + indices: impl Into>, + ) { + let indices: DrawingIndices<'a> = indices.into(); + citro3d_sys::C3D_DrawElements( + primitive as ctru_sys::GPU_Primitive_t, + indices.len() as i32, + // flag bit for short or byte + match indices { + DrawingIndices::U16(_) => 1, + DrawingIndices::U8(_) => 0, + }, + match indices { + DrawingIndices::U16(v) => v.as_ptr() as *const _, + DrawingIndices::U8(v) => v.as_ptr() as *const _, + }, + ); + } /// Use the given [`shader::Program`] for subsequent draw calls. pub fn bind_program(&mut self, program: &shader::Program) { @@ -237,3 +261,28 @@ impl Drop for Instance { } } } + +pub enum DrawingIndices<'a> { + U16(&'a [u16]), + U8(&'a [u8]), +} +impl DrawingIndices<'_> { + fn len(&self) -> usize { + match self { + DrawingIndices::U16(a) => a.len(), + DrawingIndices::U8(a) => a.len(), + } + } +} + +impl<'a> From<&'a [u8]> for DrawingIndices<'a> { + fn from(v: &'a [u8]) -> Self { + Self::U8(v) + } +} + +impl<'a> From<&'a [u16]> for DrawingIndices<'a> { + fn from(v: &'a [u16]) -> Self { + Self::U16(v) + } +} From d4d5c6ff9534d155e4472e40fb3266ae8d495a37 Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Thu, 1 Feb 2024 15:05:40 +0000 Subject: [PATCH 2/3] fix: crashing citra --- citro3d/examples/cube.rs | 24 ++++++++++++++---------- citro3d/src/lib.rs | 29 ++++++++++++++++++++++------- citro3d/src/util.rs | 6 ++++++ 3 files changed, 42 insertions(+), 17 deletions(-) create mode 100644 citro3d/src/util.rs diff --git a/citro3d/examples/cube.rs b/citro3d/examples/cube.rs index 73c074c..b8ff063 100644 --- a/citro3d/examples/cube.rs +++ b/citro3d/examples/cube.rs @@ -111,7 +111,7 @@ fn main() { y: v[1], z: v[2], }, - color: Vec3::new(i as f32 % 1., i as f32 % 1., i as f32 % 1.), + color: Vec3::new(1.0, 0.7, 0.5), }) { vbo_data.push(vert); } @@ -134,6 +134,16 @@ fn main() { FVec3::new(0.0, 1.0, 0.0), CoordinateOrientation::RightHanded, ); + let indecies_a = [ + 0, 3, 1, 1, 3, 2, // triangles making up the top (+y) facing side. + 4, 5, 7, 5, 6, 7, // bottom (-y) + 8, 11, 9, 9, 11, 10, // right (+x) + 12, 13, 15, 13, 14, 15, // left (-x) + 16, 19, 17, 17, 19, 18, // back (+z) + 20, 21, 23, 21, 22, 23, // forward (-z) + ]; + let mut indecies = Vec::with_capacity_in(indecies_a.len(), ctru::linear::LinearAllocator); + indecies.extend_from_slice(&indecies_a); while apt.main_loop() { hid.scan_input(); @@ -159,18 +169,12 @@ fn main() { unsafe { instance.draw_elements( buffer::Primitive::Triangles, - DrawingIndices::U16(&[ - 0, 3, 1, 1, 3, 2, // triangles making up the top (+y) facing side. - 4, 5, 7, 5, 6, 7, // bottom (-y) - 8, 11, 9, 9, 11, 10, // right (+x) - 12, 13, 15, 13, 14, 15, // left (-x) - 16, 19, 17, 17, 19, 18, // back (+z) - 20, 21, 23, 21, 22, 23, // forward (-z) - ]), + &buf_info, + DrawingIndices::U16(&indecies), ); } - instance.draw_arrays(buffer::Primitive::Triangles, vbo_data); + //instance.draw_arrays(buffer::Primitive::Triangles, vbo_data); }; let Projections { diff --git a/citro3d/src/lib.rs b/citro3d/src/lib.rs index 133f369..88636ee 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -17,11 +17,13 @@ pub mod render; pub mod shader; pub mod texenv; pub mod uniform; +mod util; use std::cell::OnceCell; use std::fmt; pub use error::{Error, Result}; +use util::is_linear_ptr; use self::texenv::TexEnv; use self::uniform::Uniform; @@ -162,27 +164,40 @@ impl Instance { } /// Indexed drawing /// + /// Draws the vertices in `buf` indexed by `indices`. `indices` must be linearly allocated + /// /// # Safety /// If `indices` goes out of scope before the current frame ends it will cause a use-after-free (possibly by the GPU) + /// If `buf` does not contain all the vertices references by `indices` it will cause an invalid access by the GPU (this crashes citra) + /// + /// # Panics + /// If `indices` is not allocated in linear memory #[doc(alias = "C3D_DrawElements")] pub unsafe fn draw_elements<'a>( &mut self, primitive: buffer::Primitive, + buf: &buffer::Info, indices: impl Into>, ) { + self.set_buffer_info(buf); let indices: DrawingIndices<'a> = indices.into(); + let elements = match indices { + DrawingIndices::U16(v) => v.as_ptr() as *const _, + DrawingIndices::U8(v) => v.as_ptr() as *const _, + }; + assert!( + is_linear_ptr(elements), + "draw_elements requires linear allocated indices buffer" + ); citro3d_sys::C3D_DrawElements( primitive as ctru_sys::GPU_Primitive_t, indices.len() as i32, // flag bit for short or byte match indices { - DrawingIndices::U16(_) => 1, - DrawingIndices::U8(_) => 0, - }, - match indices { - DrawingIndices::U16(v) => v.as_ptr() as *const _, - DrawingIndices::U8(v) => v.as_ptr() as *const _, - }, + DrawingIndices::U16(_) => citro3d_sys::C3D_UNSIGNED_SHORT, + DrawingIndices::U8(_) => citro3d_sys::C3D_UNSIGNED_BYTE, + } as i32, + elements, ); } diff --git a/citro3d/src/util.rs b/citro3d/src/util.rs new file mode 100644 index 0000000..727e8a2 --- /dev/null +++ b/citro3d/src/util.rs @@ -0,0 +1,6 @@ +/// Check if pointer is in linear memory +pub fn is_linear_ptr

(p: *const P) -> bool { + let addr = p as usize; + addr >= ctru_sys::OS_FCRAM_VADDR as usize + && addr < (ctru_sys::OS_FCRAM_VADDR as usize + ctru_sys::OS_FCRAM_SIZE as usize) +} From e74d426beb68ba4aae1e9877f2e659b7c5ee0c72 Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Thu, 1 Feb 2024 15:06:07 +0000 Subject: [PATCH 3/3] chore: rename drawingindexecies -> indextype --- citro3d/examples/cube.rs | 4 ++-- citro3d/src/lib.rs | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/citro3d/examples/cube.rs b/citro3d/examples/cube.rs index b8ff063..69d9f80 100644 --- a/citro3d/examples/cube.rs +++ b/citro3d/examples/cube.rs @@ -9,7 +9,7 @@ use citro3d::math::{ }; use citro3d::render::ClearFlags; use citro3d::{attrib, buffer, render, shader}; -use citro3d::{texenv, DrawingIndices}; +use citro3d::{texenv, IndexType}; use ctru::prelude::*; use ctru::services::gfx::{RawFrameBuffer, Screen, TopScreen3D}; @@ -170,7 +170,7 @@ fn main() { instance.draw_elements( buffer::Primitive::Triangles, &buf_info, - DrawingIndices::U16(&indecies), + IndexType::U16(&indecies), ); } diff --git a/citro3d/src/lib.rs b/citro3d/src/lib.rs index 88636ee..7117a85 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -177,13 +177,13 @@ impl Instance { &mut self, primitive: buffer::Primitive, buf: &buffer::Info, - indices: impl Into>, + indices: impl Into>, ) { self.set_buffer_info(buf); - let indices: DrawingIndices<'a> = indices.into(); + let indices: IndexType<'a> = indices.into(); let elements = match indices { - DrawingIndices::U16(v) => v.as_ptr() as *const _, - DrawingIndices::U8(v) => v.as_ptr() as *const _, + IndexType::U16(v) => v.as_ptr() as *const _, + IndexType::U8(v) => v.as_ptr() as *const _, }; assert!( is_linear_ptr(elements), @@ -194,8 +194,8 @@ impl Instance { indices.len() as i32, // flag bit for short or byte match indices { - DrawingIndices::U16(_) => citro3d_sys::C3D_UNSIGNED_SHORT, - DrawingIndices::U8(_) => citro3d_sys::C3D_UNSIGNED_BYTE, + IndexType::U16(_) => citro3d_sys::C3D_UNSIGNED_SHORT, + IndexType::U8(_) => citro3d_sys::C3D_UNSIGNED_BYTE, } as i32, elements, ); @@ -277,26 +277,26 @@ impl Drop for Instance { } } -pub enum DrawingIndices<'a> { +pub enum IndexType<'a> { U16(&'a [u16]), U8(&'a [u8]), } -impl DrawingIndices<'_> { +impl IndexType<'_> { fn len(&self) -> usize { match self { - DrawingIndices::U16(a) => a.len(), - DrawingIndices::U8(a) => a.len(), + IndexType::U16(a) => a.len(), + IndexType::U8(a) => a.len(), } } } -impl<'a> From<&'a [u8]> for DrawingIndices<'a> { +impl<'a> From<&'a [u8]> for IndexType<'a> { fn from(v: &'a [u8]) -> Self { Self::U8(v) } } -impl<'a> From<&'a [u16]> for DrawingIndices<'a> { +impl<'a> From<&'a [u16]> for IndexType<'a> { fn from(v: &'a [u16]) -> Self { Self::U16(v) }