From 73b454f7a0ac8286964c996fb0d494653ef8f8ff Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Thu, 1 Feb 2024 12:54:07 +0000 Subject: [PATCH 1/5] 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 452b89d..702efdd 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -190,7 +190,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, @@ -199,6 +198,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) { @@ -282,6 +306,31 @@ impl Drop for RenderQueue { } } +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) + } +} + #[cfg(test)] mod tests { use ctru::services::gfx::Gfx; From bfff51d0a4a355a509b9a91d9dd7bd1b4a1e62f0 Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Thu, 1 Feb 2024 15:05:40 +0000 Subject: [PATCH 2/5] 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 702efdd..db15e6f 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -24,6 +24,7 @@ pub mod render; pub mod shader; pub mod texenv; pub mod uniform; +mod util; use std::cell::{OnceCell, RefMut}; use std::fmt; @@ -31,6 +32,7 @@ use std::rc::Rc; use ctru::services::gfx::Screen; pub use error::{Error, Result}; +use util::is_linear_ptr; use self::texenv::TexEnv; use self::uniform::Uniform; @@ -200,27 +202,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 910f39d87c5d86bd9158fb175ca8dd0352bf4fb3 Mon Sep 17 00:00:00 2001 From: Natasha England-Elbro Date: Thu, 1 Feb 2024 15:06:07 +0000 Subject: [PATCH 3/5] 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 db15e6f..b17a73c 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -215,13 +215,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), @@ -232,8 +232,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, ); @@ -321,26 +321,26 @@ impl Drop for RenderQueue { } } -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) } From 8f9d70a9e8f9b317109483d0de2f152abcd603c1 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Wed, 19 Jun 2024 14:45:30 -0400 Subject: [PATCH 4/5] Use a checked index API and do a bit of cleanup --- citro3d/examples/cube.rs | 40 ++++++++++------------ citro3d/src/buffer.rs | 59 ++++++++++++++++++++++++++++++++ citro3d/src/error.rs | 8 +++++ citro3d/src/lib.rs | 72 ++++++++++++++-------------------------- citro3d/src/util.rs | 6 ---- 5 files changed, 110 insertions(+), 75 deletions(-) delete mode 100644 citro3d/src/util.rs diff --git a/citro3d/examples/cube.rs b/citro3d/examples/cube.rs index 69d9f80..980f8da 100644 --- a/citro3d/examples/cube.rs +++ b/citro3d/examples/cube.rs @@ -8,8 +8,7 @@ use citro3d::math::{ AspectRatio, ClipPlanes, CoordinateOrientation, FVec3, Matrix4, Projection, StereoDisplacement, }; use citro3d::render::ClearFlags; -use citro3d::{attrib, buffer, render, shader}; -use citro3d::{texenv, IndexType}; +use citro3d::{attrib, buffer, render, shader, texenv}; use ctru::prelude::*; use ctru::services::gfx::{RawFrameBuffer, Screen, TopScreen3D}; @@ -86,17 +85,20 @@ fn main() { 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 mut top_left_target = instance + .render_target(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) + let mut top_right_target = instance + .render_target(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) + let mut bottom_target = instance + .render_target(width, height, bottom_screen, None) .expect("failed to create bottom screen render target"); let shader = shader::Library::from_bytes(SHADER_BYTES).unwrap(); @@ -111,13 +113,17 @@ fn main() { y: v[1], z: v[2], }, - color: Vec3::new(1.0, 0.7, 0.5), + color: { + // Give each vertex a slightly different color just to highlight edges/corners + let value = i as f32 / VERTS.len() as f32; + Vec3::new(1.0, 0.7 * value, 0.5) + }, }) { vbo_data.push(vert); } let mut buf_info = buffer::Info::new(); - let (attr_info, vbo_data) = prepare_vbos(&mut buf_info, &vbo_data); + let (attr_info, vbo_slice) = 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 @@ -134,7 +140,7 @@ fn main() { FVec3::new(0.0, 1.0, 0.0), CoordinateOrientation::RightHanded, ); - let indecies_a = [ + let indices: &[u8] = &[ 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) @@ -142,8 +148,7 @@ fn main() { 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); + let indices = vbo_slice.index_buffer(indices).unwrap(); while apt.main_loop() { hid.scan_input(); @@ -160,21 +165,12 @@ fn main() { .select_render_target(target) .expect("failed to set render target"); - instance.bind_vertex_uniform( - projection_uniform_idx, - &(projection * camera_transform.clone()), - ); + instance.bind_vertex_uniform(projection_uniform_idx, projection * camera_transform); instance.set_attr_info(&attr_info); unsafe { - instance.draw_elements( - buffer::Primitive::Triangles, - &buf_info, - IndexType::U16(&indecies), - ); + instance.draw_elements(buffer::Primitive::Triangles, &buf_info, &indices); } - - //instance.draw_arrays(buffer::Primitive::Triangles, vbo_data); }; let Projections { diff --git a/citro3d/src/buffer.rs b/citro3d/src/buffer.rs index 0a19ff3..0785237 100644 --- a/citro3d/src/buffer.rs +++ b/citro3d/src/buffer.rs @@ -5,7 +5,10 @@ use std::mem::MaybeUninit; +use ctru::linear::LinearAllocator; + use crate::attrib; +use crate::Error; /// Vertex buffer info. This struct is used to describe the shape of the buffer /// data to be sent to the GPU for rendering. @@ -46,6 +49,62 @@ impl Slice<'_> { pub fn info(&self) -> &Info { self.buf_info } + + /// Get an index buffer for this slice using the given indices. + /// + /// # Errors + /// + /// Returns an error if: + /// - any of the given indices are out of bounds. + /// - the given slice is too long for its length to fit in a `libc::c_int`. + pub fn index_buffer(&self, indices: &[I]) -> Result, Error> + where + I: Index + Copy + Into, + { + if libc::c_int::try_from(indices.len()).is_err() { + return Err(Error::InvalidSize); + } + + for &idx in indices { + let idx = idx.into(); + let len = self.len(); + if idx >= len { + return Err(Error::IndexOutOfBounds { idx, len }); + } + } + + Ok(unsafe { self.index_buffer_unchecked(indices) }) + } + + /// Get an index buffer for this slice using the given indices without + /// bounds checking. + /// + /// # Safety + /// + /// If any indices are outside this buffer it can cause an invalid access by the GPU + /// (this crashes citra). + pub unsafe fn index_buffer_unchecked( + &self, + indices: &[I], + ) -> Vec { + let mut buf = Vec::with_capacity_in(indices.len(), LinearAllocator); + buf.extend_from_slice(indices); + buf + } +} + +/// A type that can be used as an index for indexed drawing. +pub trait Index: crate::private::Sealed { + /// The data type of the index, as used by [`citro3d_sys::C3D_DrawElements`]'s `type_` parameter. + const TYPE: libc::c_int; +} + +impl Index for u8 { + const TYPE: libc::c_int = citro3d_sys::C3D_UNSIGNED_BYTE as _; +} + +impl Index for u16 { + const TYPE: libc::c_int = citro3d_sys::C3D_UNSIGNED_SHORT as _; } /// The geometric primitive to draw (i.e. what shapes the buffer data describes). diff --git a/citro3d/src/error.rs b/citro3d/src/error.rs index 9a99089..bcb69bb 100644 --- a/citro3d/src/error.rs +++ b/citro3d/src/error.rs @@ -1,5 +1,6 @@ //! General-purpose error and result types returned by public APIs of this crate. +use core::fmt; use std::ffi::NulError; use std::num::TryFromIntError; use std::sync::TryLockError; @@ -36,6 +37,13 @@ pub enum Error { InvalidName, /// The requested resource could not be found. NotFound, + /// Attempted to use an index that was out of bounds. + IndexOutOfBounds { + /// The index used. + idx: libc::c_int, + /// The length of the collection. + len: libc::c_int, + }, } impl From for Error { diff --git a/citro3d/src/lib.rs b/citro3d/src/lib.rs index b17a73c..c20155e 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -1,5 +1,6 @@ #![feature(custom_test_frameworks)] #![test_runner(test_runner::run_gdb)] +#![feature(allocator_api)] #![feature(doc_cfg)] #![feature(doc_auto_cfg)] #![doc(html_root_url = "https://rust3ds.github.io/citro3d-rs/crates")] @@ -24,15 +25,14 @@ pub mod render; pub mod shader; pub mod texenv; pub mod uniform; -mod util; use std::cell::{OnceCell, RefMut}; use std::fmt; use std::rc::Rc; +use ctru::linear::LinearAllocation; use ctru::services::gfx::Screen; pub use error::{Error, Result}; -use util::is_linear_ptr; use self::texenv::TexEnv; use self::uniform::Uniform; @@ -42,6 +42,12 @@ pub mod macros { pub use citro3d_macros::*; } +mod private { + pub trait Sealed {} + impl Sealed for u8 {} + impl Sealed for u16 {} +} + /// The single instance for using `citro3d`. This is the base type that an application /// should instantiate to use this library. #[non_exhaustive] @@ -205,36 +211,33 @@ impl Instance { /// 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) + // TODO: #41 might be able to solve this: + /// If `indices` goes out of scope before the current frame ends it will cause a + /// use-after-free (possibly by the GPU). /// /// # Panics - /// If `indices` is not allocated in linear memory + /// + /// If the given index buffer is too long to have its length converted to `i32`. #[doc(alias = "C3D_DrawElements")] - pub unsafe fn draw_elements<'a>( + pub unsafe fn draw_elements( &mut self, primitive: buffer::Primitive, buf: &buffer::Info, - indices: impl Into>, - ) { + indices: &Indices, + ) where + I: buffer::Index, + Indices: AsRef<[I]> + LinearAllocation, + { self.set_buffer_info(buf); - let indices: IndexType<'a> = indices.into(); - let elements = match indices { - IndexType::U16(v) => v.as_ptr() as *const _, - IndexType::U8(v) => v.as_ptr() as *const _, - }; - assert!( - is_linear_ptr(elements), - "draw_elements requires linear allocated indices buffer" - ); + + let indices = indices.as_ref(); + let elements = indices.as_ptr().cast(); + citro3d_sys::C3D_DrawElements( primitive as ctru_sys::GPU_Primitive_t, - indices.len() as i32, + indices.len().try_into().unwrap(), // flag bit for short or byte - match indices { - IndexType::U16(_) => citro3d_sys::C3D_UNSIGNED_SHORT, - IndexType::U8(_) => citro3d_sys::C3D_UNSIGNED_BYTE, - } as i32, + I::TYPE, elements, ); } @@ -321,31 +324,6 @@ impl Drop for RenderQueue { } } -pub enum IndexType<'a> { - U16(&'a [u16]), - U8(&'a [u8]), -} -impl IndexType<'_> { - fn len(&self) -> usize { - match self { - IndexType::U16(a) => a.len(), - IndexType::U8(a) => a.len(), - } - } -} - -impl<'a> From<&'a [u8]> for IndexType<'a> { - fn from(v: &'a [u8]) -> Self { - Self::U8(v) - } -} - -impl<'a> From<&'a [u16]> for IndexType<'a> { - fn from(v: &'a [u16]) -> Self { - Self::U16(v) - } -} - #[cfg(test)] mod tests { use ctru::services::gfx::Gfx; diff --git a/citro3d/src/util.rs b/citro3d/src/util.rs deleted file mode 100644 index 727e8a2..0000000 --- a/citro3d/src/util.rs +++ /dev/null @@ -1,6 +0,0 @@ -/// 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 8e9205deb0499c2b9acd7ebea4b4f700c0646c16 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Wed, 19 Jun 2024 20:16:29 -0400 Subject: [PATCH 5/5] Use a vertex buffer slice in draw_elements args --- citro3d/examples/cube.rs | 17 +++++++---------- citro3d/src/buffer.rs | 22 ++++++++++++++-------- citro3d/src/error.rs | 1 - citro3d/src/lib.rs | 19 ++++++++----------- citro3d/src/math/ops.rs | 2 +- 5 files changed, 30 insertions(+), 31 deletions(-) diff --git a/citro3d/examples/cube.rs b/citro3d/examples/cube.rs index 980f8da..ba58afb 100644 --- a/citro3d/examples/cube.rs +++ b/citro3d/examples/cube.rs @@ -122,8 +122,10 @@ fn main() { vbo_data.push(vert); } + let attr_info = build_attrib_info(); + let mut buf_info = buffer::Info::new(); - let (attr_info, vbo_slice) = prepare_vbos(&mut buf_info, &vbo_data); + let vbo_slice = buf_info.add(&vbo_data, &attr_info).unwrap(); // 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 @@ -148,7 +150,7 @@ fn main() { 16, 19, 17, 17, 19, 18, // back (+z) 20, 21, 23, 21, 22, 23, // forward (-z) ]; - let indices = vbo_slice.index_buffer(indices).unwrap(); + let index_buffer = vbo_slice.index_buffer(indices).unwrap(); while apt.main_loop() { hid.scan_input(); @@ -169,7 +171,7 @@ fn main() { instance.set_attr_info(&attr_info); unsafe { - instance.draw_elements(buffer::Primitive::Triangles, &buf_info, &indices); + instance.draw_elements(buffer::Primitive::Triangles, vbo_slice, &index_buffer); } }; @@ -186,10 +188,7 @@ fn main() { } } -fn prepare_vbos<'a>( - buf_info: &'a mut buffer::Info, - vbo_data: &'a [Vertex], -) -> (attrib::Info, buffer::Slice<'a>) { +fn build_attrib_info() -> attrib::Info { // Configure attributes for use with the vertex shader let mut attr_info = attrib::Info::new(); @@ -204,9 +203,7 @@ fn prepare_vbos<'a>( .add_loader(reg1, attrib::Format::Float, 3) .unwrap(); - let buf_idx = buf_info.add(vbo_data, &attr_info).unwrap(); - - (attr_info, buf_idx) + attr_info } struct Projections { diff --git a/citro3d/src/buffer.rs b/citro3d/src/buffer.rs index 0785237..afaa426 100644 --- a/citro3d/src/buffer.rs +++ b/citro3d/src/buffer.rs @@ -57,7 +57,7 @@ impl Slice<'_> { /// Returns an error if: /// - any of the given indices are out of bounds. /// - the given slice is too long for its length to fit in a `libc::c_int`. - pub fn index_buffer(&self, indices: &[I]) -> Result, Error> + pub fn index_buffer(&self, indices: &[I]) -> Result, Error> where I: Index + Copy + Into, { @@ -83,16 +83,22 @@ impl Slice<'_> { /// /// If any indices are outside this buffer it can cause an invalid access by the GPU /// (this crashes citra). - pub unsafe fn index_buffer_unchecked( - &self, - indices: &[I], - ) -> Vec { - let mut buf = Vec::with_capacity_in(indices.len(), LinearAllocator); - buf.extend_from_slice(indices); - buf + pub unsafe fn index_buffer_unchecked(&self, indices: &[I]) -> Indices { + let mut buffer = Vec::with_capacity_in(indices.len(), LinearAllocator); + buffer.extend_from_slice(indices); + Indices { + buffer, + _slice: *self, + } } } +/// An index buffer for indexed drawing. See [`Slice::index_buffer`] to obtain one. +pub struct Indices<'buf, I> { + pub(crate) buffer: Vec, + _slice: Slice<'buf>, +} + /// A type that can be used as an index for indexed drawing. pub trait Index: crate::private::Sealed { /// The data type of the index, as used by [`citro3d_sys::C3D_DrawElements`]'s `type_` parameter. diff --git a/citro3d/src/error.rs b/citro3d/src/error.rs index bcb69bb..65ca430 100644 --- a/citro3d/src/error.rs +++ b/citro3d/src/error.rs @@ -1,6 +1,5 @@ //! General-purpose error and result types returned by public APIs of this crate. -use core::fmt; use std::ffi::NulError; use std::num::TryFromIntError; use std::sync::TryLockError; diff --git a/citro3d/src/lib.rs b/citro3d/src/lib.rs index c20155e..b8748c6 100644 --- a/citro3d/src/lib.rs +++ b/citro3d/src/lib.rs @@ -30,10 +30,10 @@ use std::cell::{OnceCell, RefMut}; use std::fmt; use std::rc::Rc; -use ctru::linear::LinearAllocation; use ctru::services::gfx::Screen; pub use error::{Error, Result}; +use self::buffer::{Index, Indices}; use self::texenv::TexEnv; use self::uniform::Uniform; @@ -219,18 +219,15 @@ impl Instance { /// /// If the given index buffer is too long to have its length converted to `i32`. #[doc(alias = "C3D_DrawElements")] - pub unsafe fn draw_elements( + pub unsafe fn draw_elements( &mut self, primitive: buffer::Primitive, - buf: &buffer::Info, - indices: &Indices, - ) where - I: buffer::Index, - Indices: AsRef<[I]> + LinearAllocation, - { - self.set_buffer_info(buf); - - let indices = indices.as_ref(); + vbo_data: buffer::Slice, + indices: &Indices, + ) { + self.set_buffer_info(vbo_data.info()); + + let indices = &indices.buffer; let elements = indices.as_ptr().cast(); citro3d_sys::C3D_DrawElements( diff --git a/citro3d/src/math/ops.rs b/citro3d/src/math/ops.rs index 229210e..7949189 100644 --- a/citro3d/src/math/ops.rs +++ b/citro3d/src/math/ops.rs @@ -212,7 +212,7 @@ impl AbsDiffEq for Matrix4 { fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { self.rows_wzyx() .into_iter() - .zip(other.rows_wzyx().into_iter()) + .zip(other.rows_wzyx()) .all(|(l, r)| l.abs_diff_eq(&r, epsilon)) } }