From ec91f7d2fe20878cfc5700f5c1721a4a3986d815 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sat, 4 Mar 2023 11:58:17 -0500 Subject: [PATCH] Use the lifetime of the input VBO data We can return a "handle" that stores the index + size of the VBO data, as well as keeping a phantom borrow on the original data. By requiring this handle as input during the call to draw arrays, we ensure the VBO data lives long enough for the draw call. --- citro3d/examples/triangle.rs | 104 +++++++++++++++-------------------- citro3d/src/buffers.rs | 50 +++++++++++++++-- citro3d/src/render.rs | 12 +++- 3 files changed, 101 insertions(+), 65 deletions(-) diff --git a/citro3d/examples/triangle.rs b/citro3d/examples/triangle.rs index 1b3a9a0..6309927 100644 --- a/citro3d/examples/triangle.rs +++ b/citro3d/examples/triangle.rs @@ -1,7 +1,7 @@ #![feature(allocator_api)] use citro3d::attrib::{self, AttrInfo}; -use citro3d::buffers::BufInfo; +use citro3d::buffers::{self, BufInfo}; use citro3d::render::{ClearFlags, Target}; use citro3d::{include_aligned_bytes, shader}; use citro3d_sys::C3D_Mtx; @@ -81,8 +81,9 @@ fn main() { let mut vbo_data = Vec::with_capacity_in(VERTICES.len(), ctru::linear::LinearAllocator); vbo_data.extend_from_slice(VERTICES); + let vbo_idx = prepare_vbos(&vbo_data); - let (uloc_projection, projection) = scene_init(&mut program, &vbo_data); + let (uloc_projection, projection) = scene_init(&mut program); while apt.main_loop() { hid.scan_input(); @@ -99,7 +100,17 @@ fn main() { let clear_color: u32 = 0x7F_7F_7F_FF; target.clear(ClearFlags::ALL, clear_color, 0); - scene_render(uloc_projection.into(), &projection); + + unsafe { + // Update the uniforms + citro3d_sys::C3D_FVUnifMtx4x4( + ctru_sys::GPU_VERTEX_SHADER, + uloc_projection.into(), + &projection, + ); + } + + target.draw_arrays(buffers::Primitive::Triangles, vbo_idx); }); }; @@ -108,50 +119,46 @@ fn main() { } } -fn scene_init(program: &mut shader::Program, vbo_data: &[Vertex]) -> (i8, C3D_Mtx) { - // Load the vertex shader, create a shader program and bind it - unsafe { - citro3d_sys::C3D_BindProgram(program.as_raw()); +fn prepare_vbos(vbo_data: &[Vertex]) -> buffers::Index { + // Configure attributes for use with the vertex shader + let mut attr_info = AttrInfo::get_mut().expect("failed to get global attr info"); - // Get the location of the uniforms - let projection_name = CStr::from_bytes_with_nul(b"projection\0").unwrap(); - let uloc_projection = ctru_sys::shaderInstanceGetUniformLocation( - (*program.as_raw()).vertexShader, - projection_name.as_ptr(), - ); + let reg0 = attrib::Register::new(0).unwrap(); + let reg1 = attrib::Register::new(1).unwrap(); - // Configure attributes for use with the vertex shader - let mut attr_info = AttrInfo::get_mut().expect("failed to get global attr info"); + // The default permutation would actually already be what we want if we + // inserted position, then color, but just show that it's customizable + // by swapping the order then using `set_permutation`. - let reg0 = attrib::Register::new(0).unwrap(); - let reg1 = attrib::Register::new(1).unwrap(); + let color_attr = attr_info + .add_loader(reg0, attrib::Format::Float, 3) + .unwrap(); - // The default permutation would actually already be what we want if we - // inserted position, then color, but just show that it's customizable - // by swapping the order then using `set_permutation`. + let position_attr = attr_info + .add_loader(reg1, attrib::Format::Float, 3) + .unwrap(); - let color_attr = attr_info - .add_loader(reg0, attrib::Format::Float, 3) - .unwrap(); + attr_info + .set_permutation(&[position_attr, color_attr]) + .unwrap(); - let position_attr = attr_info - .add_loader(reg1, attrib::Format::Float, 3) - .unwrap(); + // Configure buffers + let mut buf_info = BufInfo::get_mut().unwrap(); + let buf_idx = buf_info.add(vbo_data, &attr_info).unwrap(); - eprintln!( - "count {} permutation {:#x}", - attr_info.count(), - attr_info.permutation() - ); + buf_idx +} - attr_info - .set_permutation(&[position_attr, color_attr]) - .unwrap(); +fn scene_init(program: &mut shader::Program) -> (i8, C3D_Mtx) { + // Load the vertex shader, create a shader program and bind it + unsafe { + citro3d_sys::C3D_BindProgram(program.as_raw()); - eprintln!( - "count {} permutation {:#x}", - attr_info.count(), - attr_info.permutation() + // Get the location of the uniforms + let projection_name = CStr::from_bytes_with_nul(b"projection\0").unwrap(); + let uloc_projection = ctru_sys::shaderInstanceGetUniformLocation( + (*program.as_raw()).vertexShader, + projection_name.as_ptr(), ); // Compute the projection matrix @@ -171,10 +178,6 @@ fn scene_init(program: &mut shader::Program, vbo_data: &[Vertex]) -> (i8, C3D_Mt projection.assume_init() }; - // Configure buffers - let mut buf_info = BufInfo::get_mut().unwrap(); - 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 let env = citro3d_sys::C3D_GetTexEnv(0); @@ -191,20 +194,3 @@ fn scene_init(program: &mut shader::Program, vbo_data: &[Vertex]) -> (i8, C3D_Mt (uloc_projection, projection) } } - -fn scene_render(uloc_projection: i32, projection: &C3D_Mtx) { - unsafe { - // Update the uniforms - citro3d_sys::C3D_FVUnifMtx4x4(ctru_sys::GPU_VERTEX_SHADER, uloc_projection, projection); - - // Draw the VBO - citro3d_sys::C3D_DrawArrays( - ctru_sys::GPU_TRIANGLES, - 0, - VERTICES - .len() - .try_into() - .expect("VERTICES.len() fits in i32"), - ); - } -} diff --git a/citro3d/src/buffers.rs b/citro3d/src/buffers.rs index 6ad09f8..9191d3f 100644 --- a/citro3d/src/buffers.rs +++ b/citro3d/src/buffers.rs @@ -1,3 +1,4 @@ +use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::sync::{LazyLock, RwLock}; @@ -22,6 +23,33 @@ pub struct BufInfo { unsafe impl Sync for BufInfo {} unsafe impl Send for BufInfo {} +// TODO: is this a good name? It's more like a "handle" to the VBO data, or a slice. +#[derive(Debug, Clone, Copy)] +pub struct Index<'vbo> { + index: libc::c_int, + size: libc::c_int, + _data: PhantomData<&'vbo ()>, +} + +impl Index<'_> { + pub fn as_raw(&self) -> libc::c_int { + self.index + } + + pub fn size(&self) -> libc::c_int { + self.size + } +} + +#[repr(u32)] +#[derive(Debug, Clone, Copy)] +pub enum Primitive { + Triangles = ctru_sys::GPU_TRIANGLES, + TriangleStrip = ctru_sys::GPU_TRIANGLE_STRIP, + TriangleFan = ctru_sys::GPU_TRIANGLE_FAN, + GeometryPrim = ctru_sys::GPU_GEOMETRY_PRIM, +} + impl BufInfo { /// Get a reference to the global buffer info. pub fn get() -> crate::Result> { @@ -33,12 +61,16 @@ impl BufInfo { Ok(BUF_INFO.try_write()?) } - pub fn add(&mut self, vbo_data: &[T], attrib_info: &attrib::AttrInfo) -> crate::Result<()> { + pub fn add<'vbo, T>( + &mut self, + vbo_data: &'vbo [T], + attrib_info: &attrib::AttrInfo, + ) -> crate::Result> { let stride = std::mem::size_of::().try_into()?; let attrib_count = attrib_info.count(); let permutation = attrib_info.permutation(); - unsafe { + let res = unsafe { citro3d_sys::BufInfo_Add( self.raw, // TODO: figure out how the hell to encode the lifetime of this @@ -47,9 +79,17 @@ impl BufInfo { stride, attrib_count, permutation, - ); - } + ) + }; - Ok(()) + if res < 0 { + Err(crate::Error::System(res)) + } else { + Ok(Index { + index: res, + size: vbo_data.len().try_into()?, + _data: PhantomData, + }) + } } } diff --git a/citro3d/src/render.rs b/citro3d/src/render.rs index 0a7a563..08a232f 100644 --- a/citro3d/src/render.rs +++ b/citro3d/src/render.rs @@ -10,7 +10,7 @@ use ctru::gfx::Screen; use ctru::services::gspgpu::FramebufferFormat; use ctru_sys::{GPU_COLORBUF, GPU_DEPTHBUF}; -use crate::{Error, Result}; +use crate::{buffers, Error, Result}; mod transfer; @@ -91,6 +91,16 @@ impl<'screen> Target<'screen> { pub(crate) fn as_raw(&self) -> *mut C3D_RenderTarget { self.raw } + + pub fn draw_arrays(&mut self, primitive: buffers::Primitive, index: buffers::Index) { + unsafe { + citro3d_sys::C3D_DrawArrays( + primitive as ctru_sys::GPU_Primitive_t, + index.as_raw(), + index.size(), + ); + } + } } bitflags::bitflags! {