Browse Source

Use an Rc to keep render queue alive

pull/38/head
Ian Chamberlain 12 months ago
parent
commit
700646e217
No known key found for this signature in database
GPG Key ID: AE5484D09405AA60
  1. 11
      citro3d/examples/triangle.rs
  2. 59
      citro3d/src/lib.rs
  3. 19
      citro3d/src/render.rs

11
citro3d/examples/triangle.rs

@ -65,17 +65,20 @@ fn main() {
let (mut top_left, mut top_right) = top_screen.split_mut(); let (mut top_left, mut top_right) = top_screen.split_mut();
let RawFrameBuffer { width, height, .. } = top_left.raw_framebuffer(); let RawFrameBuffer { width, height, .. } = top_left.raw_framebuffer();
let mut top_left_target = let mut top_left_target = instance
render::Target::new(width, height, top_left, None).expect("failed to create render target"); .render_target(width, height, top_left, None)
.expect("failed to create render target");
let RawFrameBuffer { width, height, .. } = top_right.raw_framebuffer(); 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"); .expect("failed to create render target");
let mut bottom_screen = gfx.bottom_screen.borrow_mut(); let mut bottom_screen = gfx.bottom_screen.borrow_mut();
let RawFrameBuffer { width, height, .. } = bottom_screen.raw_framebuffer(); 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"); .expect("failed to create bottom screen render target");
let shader = shader::Library::from_bytes(SHADER_BYTES).unwrap(); let shader = shader::Library::from_bytes(SHADER_BYTES).unwrap();

59
citro3d/src/lib.rs

@ -18,9 +18,11 @@ pub mod shader;
pub mod texenv; pub mod texenv;
pub mod uniform; pub mod uniform;
use std::cell::OnceCell; use std::cell::{OnceCell, RefMut};
use std::fmt; use std::fmt;
use std::rc::Rc;
use ctru::services::gfx::Screen;
pub use error::{Error, Result}; pub use error::{Error, Result};
use self::texenv::TexEnv; use self::texenv::TexEnv;
@ -37,8 +39,15 @@ pub mod macros {
#[must_use] #[must_use]
pub struct Instance { pub struct Instance {
texenvs: [OnceCell<TexEnv>; texenv::TEXENV_COUNT], texenvs: [OnceCell<TexEnv>; texenv::TEXENV_COUNT],
queue: Rc<RenderQueue>,
} }
/// Representation of `citro3d`'s internal render queue. This is something that
/// lives in the global context, but it keeps references to resources that are
/// used for rendering, so it's useful for us to have something to represent its
/// lifetime.
struct RenderQueue;
impl fmt::Debug for Instance { impl fmt::Debug for Instance {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Instance").finish_non_exhaustive() f.debug_struct("Instance").finish_non_exhaustive()
@ -73,12 +82,31 @@ impl Instance {
OnceCell::new(), OnceCell::new(),
OnceCell::new(), OnceCell::new(),
], ],
queue: Rc::new(RenderQueue),
}) })
} else { } else {
Err(Error::FailedToInitialize) Err(Error::FailedToInitialize)
} }
} }
/// Create a new render target with the specified size, color format,
/// and depth format.
///
/// # Errors
///
/// Fails if the target could not be created with the given parameters.
#[doc(alias = "C3D_RenderTargetCreate")]
#[doc(alias = "C3D_RenderTargetSetOutput")]
pub fn render_target<'screen>(
&self,
width: usize,
height: usize,
screen: RefMut<'screen, dyn Screen>,
depth_format: Option<render::DepthFormat>,
) -> Result<render::Target<'screen>> {
render::Target::new(width, height, screen, depth_format, Rc::clone(&self.queue))
}
/// Select the given render target for drawing the frame. /// Select the given render target for drawing the frame.
/// ///
/// # Errors /// # Errors
@ -229,11 +257,40 @@ impl Instance {
} }
} }
// This only exists to be an alias, which admittedly is kinda silly. The default
// impl should be equivalent though, since RenderQueue has a drop impl too.
impl Drop for Instance { impl Drop for Instance {
#[doc(alias = "C3D_Fini")] #[doc(alias = "C3D_Fini")]
fn drop(&mut self) {}
}
impl Drop for RenderQueue {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
citro3d_sys::C3D_Fini(); citro3d_sys::C3D_Fini();
} }
} }
} }
#[cfg(test)]
mod tests {
use ctru::services::gfx::Gfx;
use super::*;
#[test]
fn select_render_target() {
let gfx = Gfx::new().unwrap();
let screen = gfx.top_screen.borrow_mut();
let mut instance = Instance::new().unwrap();
let target = instance.render_target(10, 10, screen, None).unwrap();
instance.select_render_target(&target).unwrap();
// Check that we don't get a double-free or use-after-free by dropping
// the global instance before dropping the target.
drop(instance);
drop(target);
}
}

19
citro3d/src/render.rs

@ -2,6 +2,7 @@
//! of data to the GPU, including the format of color and depth data to be rendered. //! of data to the GPU, including the format of color and depth data to be rendered.
use std::cell::RefMut; use std::cell::RefMut;
use std::rc::Rc;
use citro3d_sys::{ use citro3d_sys::{
C3D_RenderTarget, C3D_RenderTargetCreate, C3D_RenderTargetDelete, C3D_DEPTHTYPE, C3D_RenderTarget, C3D_RenderTargetCreate, C3D_RenderTargetDelete, C3D_DEPTHTYPE,
@ -10,7 +11,7 @@ use ctru::services::gfx::Screen;
use ctru::services::gspgpu::FramebufferFormat; use ctru::services::gspgpu::FramebufferFormat;
use ctru_sys::{GPU_COLORBUF, GPU_DEPTHBUF}; use ctru_sys::{GPU_COLORBUF, GPU_DEPTHBUF};
use crate::{Error, Result}; use crate::{Error, RenderQueue, Result};
mod transfer; mod transfer;
@ -22,6 +23,7 @@ pub struct Target<'screen> {
// This is unused after construction, but ensures unique access to the // This is unused after construction, but ensures unique access to the
// screen this target writes to during rendering // screen this target writes to during rendering
_screen: RefMut<'screen, dyn Screen>, _screen: RefMut<'screen, dyn Screen>,
_queue: Rc<RenderQueue>,
} }
impl Drop for Target<'_> { impl Drop for Target<'_> {
@ -34,19 +36,15 @@ impl Drop for Target<'_> {
} }
impl<'screen> Target<'screen> { impl<'screen> Target<'screen> {
/// Create a new render target with the specified size, color format, /// Create a new render target with the given parameters. This takes a
/// and depth format. /// [`RenderQueue`] parameter to make sure this [`Target`] doesn't outlive
/// /// the render queue.
/// # Errors pub(crate) fn new(
///
/// Fails if the target could not be created.
#[doc(alias = "C3D_RenderTargetCreate")]
#[doc(alias = "C3D_RenderTargetSetOutput")]
pub fn new(
width: usize, width: usize,
height: usize, height: usize,
screen: RefMut<'screen, dyn Screen>, screen: RefMut<'screen, dyn Screen>,
depth_format: Option<DepthFormat>, depth_format: Option<DepthFormat>,
queue: Rc<RenderQueue>,
) -> Result<Self> { ) -> Result<Self> {
let color_format: ColorFormat = screen.framebuffer_format().into(); let color_format: ColorFormat = screen.framebuffer_format().into();
@ -80,6 +78,7 @@ impl<'screen> Target<'screen> {
Ok(Self { Ok(Self {
raw, raw,
_screen: screen, _screen: screen,
_queue: queue,
}) })
} }

Loading…
Cancel
Save