From 223ade454323ba7b902c04ea9756581d638976d7 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sat, 15 Oct 2022 14:16:18 -0400 Subject: [PATCH 1/5] First attempt at splitting top screen types --- ctru-rs/examples/file-explorer.rs | 5 +- ctru-rs/examples/hello-world.rs | 3 +- ctru-rs/src/gfx.rs | 118 +++++++++++++++++++++++------- 3 files changed, 97 insertions(+), 29 deletions(-) diff --git a/ctru-rs/examples/file-explorer.rs b/ctru-rs/examples/file-explorer.rs index ff6296f..5837ec8 100644 --- a/ctru-rs/examples/file-explorer.rs +++ b/ctru-rs/examples/file-explorer.rs @@ -32,8 +32,9 @@ struct FileExplorer<'a> { impl<'a> FileExplorer<'a> { fn init(apt: &'a Apt, hid: &'a Hid, gfx: &'a Gfx) -> Self { - gfx.top_screen.borrow_mut().set_wide_mode(true); - let console = Console::init(gfx.top_screen.borrow_mut()); + let mut top_screen = gfx.top_screen.borrow_mut(); + top_screen.set_wide_mode(true); + let console = Console::init(top_screen); FileExplorer { apt, diff --git a/ctru-rs/examples/hello-world.rs b/ctru-rs/examples/hello-world.rs index 5b7e8ee..53cefcb 100644 --- a/ctru-rs/examples/hello-world.rs +++ b/ctru-rs/examples/hello-world.rs @@ -1,3 +1,4 @@ +use ctru::gfx::Side; use ctru::prelude::*; use std::io::BufWriter; @@ -7,7 +8,7 @@ fn main() { let gfx = Gfx::init().expect("Couldn't obtain GFX controller"); let hid = Hid::init().expect("Couldn't obtain HID controller"); let apt = Apt::init().expect("Couldn't obtain APT controller"); - let _console = Console::init(gfx.top_screen.borrow_mut()); + let _console = Console::init(gfx.top_screen.borrow_side_mut(Side::Left)); let out = b"Hello fellow Rustaceans, I'm on the Nintendo 3DS!"; let width = 24; diff --git a/ctru-rs/src/gfx.rs b/ctru-rs/src/gfx.rs index 6400d97..d9c83d6 100644 --- a/ctru-rs/src/gfx.rs +++ b/ctru-rs/src/gfx.rs @@ -1,7 +1,7 @@ //! LCD screens manipulation helper use once_cell::sync::Lazy; -use std::cell::RefCell; +use std::cell::{Ref, RefCell, RefMut}; use std::marker::PhantomData; use std::sync::Mutex; @@ -9,11 +9,19 @@ use crate::error::Result; use crate::services::gspgpu::{self, FramebufferFormat}; use crate::services::ServiceReference; -/// Trait implemented by TopScreen and BottomScreen for common methods +/// This trait is implemented by the screen structs for working with frame buffers and +/// drawing to the screens. Graphics-related code can be made generic over this +/// trait to work with any of the given screens. pub trait Screen { /// Returns the libctru value for the Screen kind fn as_raw(&self) -> ctru_sys::gfxScreen_t; + /// Returns a [`RawFrameBuffer`] for the screen. + /// + /// Note that the pointer of the framebuffer returned by this function can + /// change after each call to this function if double buffering is enabled. + fn get_raw_framebuffer(&mut self) -> RawFrameBuffer; + /// Sets whether to use double buffering. Enabled by default. /// /// Note that even when double buffering is disabled, one should still use the `swap_buffers` @@ -33,8 +41,23 @@ pub trait Screen { } } +// TODO: it might be nice to do `TopScreen` but it requires +// #![feature(adt_const_params)] which is unstable, and doesn't seem worth it +// just for this. +#[non_exhaustive] +pub struct TopLeftScreen; + +#[non_exhaustive] +pub struct TopRightScreen; + +/// A helper container for both sides of the top screen. +pub struct TopScreenInner { + left: TopLeftScreen, + right: TopRightScreen, +} + #[non_exhaustive] -pub struct TopScreen; +pub struct TopScreen(RefCell); #[non_exhaustive] pub struct BottomScreen; @@ -71,7 +94,7 @@ pub enum Side { /// The service exits when this struct is dropped. #[non_exhaustive] pub struct Gfx { - pub top_screen: RefCell, + pub top_screen: TopScreen, pub bottom_screen: RefCell, _service_handler: ServiceReference, } @@ -100,14 +123,15 @@ impl Gfx { )?; Ok(Self { - top_screen: RefCell::new(TopScreen), + top_screen: TopScreen::new(TopLeftScreen, TopRightScreen), bottom_screen: RefCell::new(BottomScreen), _service_handler, }) } - /// Creates a new Gfx instance with default init values - /// It's the same as calling: `Gfx::with_formats(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false) + /// Creates a new [Gfx] instance with default init values + /// It's the same as calling: + /// `Gfx::with_formats(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false)` pub fn init() -> Result { Gfx::with_formats(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false) } @@ -140,6 +164,36 @@ impl Gfx { } impl TopScreen { + fn new(left: TopLeftScreen, right: TopRightScreen) -> Self { + Self(RefCell::new(TopScreenInner { left, right })) + } + + pub fn borrow(&self) -> Ref<'_, TopScreenInner> { + self.0.borrow() + } + + pub fn borrow_mut(&self) -> RefMut<'_, TopScreenInner> { + self.0.borrow_mut() + } + + pub fn borrow_side(&self, side: Side) -> Ref<'_, dyn Screen> { + let borrow = self.0.borrow(); + match side { + Side::Left => Ref::map(borrow, |top| &top.left), + Side::Right => Ref::map(borrow, |top| &top.right), + } + } + + pub fn borrow_side_mut(&self, side: Side) -> RefMut<'_, dyn Screen> { + let borrow = self.0.borrow_mut(); + match side { + Side::Left => RefMut::map(borrow, |top| &mut top.left), + Side::Right => RefMut::map(borrow, |top| &mut top.right), + } + } +} + +impl TopScreenInner { /// Enable or disable the 3D stereoscopic effect pub fn set_3d_enabled(&mut self, enabled: bool) { unsafe { @@ -159,24 +213,6 @@ impl TopScreen { pub fn get_wide_mode(&self) -> bool { unsafe { ctru_sys::gfxIsWide() } } - - /// Returns a [`RawFrameBuffer`] for the given [`Side`] of the top screen. - /// - /// Note that the pointer of the framebuffer returned by this function can - /// change after each call to this function if double buffering is enabled. - pub fn get_raw_framebuffer(&mut self, side: Side) -> RawFrameBuffer { - RawFrameBuffer::for_screen_side(self, side) - } -} - -impl BottomScreen { - /// Returns a [`RawFrameBuffer`] for the bottom screen. - /// - /// Note that the pointer of the framebuffer returned by this function can - /// change after each call to this function if double buffering is enabled. - pub fn get_raw_framebuffer(&mut self) -> RawFrameBuffer { - RawFrameBuffer::for_screen_side(self, Side::Left) - } } impl<'screen> RawFrameBuffer<'screen> { @@ -195,16 +231,46 @@ impl<'screen> RawFrameBuffer<'screen> { } } -impl Screen for TopScreen { +impl Screen for TopScreenInner { + fn as_raw(&self) -> ctru_sys::gfxScreen_t { + self.left.as_raw() + } + + fn get_raw_framebuffer(&mut self) -> RawFrameBuffer { + // When dealing with the "whole" top screen, the left framebuffer is the + // one used for writing pixel data. + self.left.get_raw_framebuffer() + } +} + +impl Screen for TopLeftScreen { fn as_raw(&self) -> ctru_sys::gfxScreen_t { ctru_sys::GFX_TOP } + + fn get_raw_framebuffer(&mut self) -> RawFrameBuffer { + RawFrameBuffer::for_screen_side(self, Side::Left) + } +} + +impl Screen for TopRightScreen { + fn as_raw(&self) -> ctru_sys::gfxScreen_t { + ctru_sys::GFX_TOP + } + + fn get_raw_framebuffer(&mut self) -> RawFrameBuffer { + RawFrameBuffer::for_screen_side(self, Side::Right) + } } impl Screen for BottomScreen { fn as_raw(&self) -> ctru_sys::gfxScreen_t { ctru_sys::GFX_BOTTOM } + + fn get_raw_framebuffer(&mut self) -> RawFrameBuffer { + RawFrameBuffer::for_screen_side(self, Side::Left) + } } impl From for ctru_sys::gfx3dSide_t { From 6c7a74f5f611b7c9a41385efb00deb1174c50575 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Mon, 21 Nov 2022 20:22:32 -0500 Subject: [PATCH 2/5] Implement a TopScreen3D type for 3d mode This type sets 3d mode on construction, and unsets it on drop, while keeping a copy of the RefCell to the top screen. I don't think this is perfect, but it seems like a decent option that should still prevent any concurrent access to the top screen's buffer. --- ctru-rs/examples/camera-image.rs | 5 +- ctru-rs/examples/gfx-3d-mode.rs | 72 ++++++++++++++++ ctru-rs/examples/hello-world.rs | 3 +- ctru-rs/src/gfx.rs | 140 +++++++++++++++---------------- 4 files changed, 143 insertions(+), 77 deletions(-) create mode 100644 ctru-rs/examples/gfx-3d-mode.rs diff --git a/ctru-rs/examples/camera-image.rs b/ctru-rs/examples/camera-image.rs index 45dcb13..cc4c4ad 100644 --- a/ctru-rs/examples/camera-image.rs +++ b/ctru-rs/examples/camera-image.rs @@ -1,9 +1,8 @@ use ctru::console::Console; -use ctru::gfx::{Screen, Side}; +use ctru::gfx::{Gfx, Screen}; use ctru::services::cam::{Cam, CamOutputFormat, CamShutterSoundType, CamSize, Camera}; use ctru::services::hid::KeyPad; use ctru::services::{Apt, Hid}; -use ctru::Gfx; use std::time::Duration; const WIDTH: usize = 400; @@ -89,7 +88,7 @@ fn main() { unsafe { gfx.top_screen .borrow_mut() - .get_raw_framebuffer(Side::Left) + .get_raw_framebuffer() .ptr .copy_from(img.as_ptr(), img.len()); } diff --git a/ctru-rs/examples/gfx-3d-mode.rs b/ctru-rs/examples/gfx-3d-mode.rs new file mode 100644 index 0000000..e6f82a2 --- /dev/null +++ b/ctru-rs/examples/gfx-3d-mode.rs @@ -0,0 +1,72 @@ +use ctru::gfx::TopScreen3D; +use ctru::prelude::*; + +/// See `graphics-bitmap.rs` for details on how the image is generated. +/// +/// WARNING: this example uses 3D mode in a rather unnatural way, and should +/// probably not be viewed for too long or at all if you are photosensitive. + +const IMAGE: &[u8] = include_bytes!("assets/ferris.rgb"); +const ZERO: &[u8] = &[0; IMAGE.len()]; + +fn main() { + ctru::init(); + let gfx = Gfx::init().expect("Couldn't obtain GFX controller"); + let hid = Hid::init().expect("Couldn't obtain HID controller"); + let apt = Apt::init().expect("Couldn't obtain APT controller"); + let _console = Console::init(gfx.bottom_screen.borrow_mut()); + + println!("Press Start to exit.\nPress A to switch which side is drawn to."); + + let top_screen = TopScreen3D::from(&gfx.top_screen); + + // TODO set double buffering for top screen + + let mut left = top_screen.left_mut(); + let left_buf = left.get_raw_framebuffer(); + let mut right = top_screen.right_mut(); + let right_buf = right.get_raw_framebuffer(); + + // We assume the image is the correct size already, so we ignore width + height. + let mut buf = left_buf.ptr; + + // Copy the image into the left-side frame buffer + unsafe { + buf.copy_from(IMAGE.as_ptr(), IMAGE.len()); + } + + // Main loop + while apt.main_loop() { + //Scan all the inputs. This should be done once for each frame + hid.scan_input(); + + if hid.keys_down().contains(KeyPad::KEY_START) { + break; + } + + if hid.keys_down().contains(KeyPad::KEY_A) { + // Clear the side we just drew to by zeroing it out + unsafe { + buf.copy_from(ZERO.as_ptr(), ZERO.len()); + } + + // flip which buffer we're writing to, and redraw the image + buf = if buf == left_buf.ptr { + right_buf.ptr + } else { + left_buf.ptr + }; + + unsafe { + buf.copy_from(IMAGE.as_ptr(), IMAGE.len()); + } + } + + // Flush and swap framebuffers + gfx.flush_buffers(); + gfx.swap_buffers(); + + //Wait for VBlank + gfx.wait_for_vblank(); + } +} diff --git a/ctru-rs/examples/hello-world.rs b/ctru-rs/examples/hello-world.rs index 53cefcb..5b7e8ee 100644 --- a/ctru-rs/examples/hello-world.rs +++ b/ctru-rs/examples/hello-world.rs @@ -1,4 +1,3 @@ -use ctru::gfx::Side; use ctru::prelude::*; use std::io::BufWriter; @@ -8,7 +7,7 @@ fn main() { let gfx = Gfx::init().expect("Couldn't obtain GFX controller"); let hid = Hid::init().expect("Couldn't obtain HID controller"); let apt = Apt::init().expect("Couldn't obtain APT controller"); - let _console = Console::init(gfx.top_screen.borrow_side_mut(Side::Left)); + let _console = Console::init(gfx.top_screen.borrow_mut()); let out = b"Hello fellow Rustaceans, I'm on the Nintendo 3DS!"; let width = 24; diff --git a/ctru-rs/src/gfx.rs b/ctru-rs/src/gfx.rs index d9c83d6..e19f4bb 100644 --- a/ctru-rs/src/gfx.rs +++ b/ctru-rs/src/gfx.rs @@ -9,10 +9,20 @@ use crate::error::Result; use crate::services::gspgpu::{self, FramebufferFormat}; use crate::services::ServiceReference; +mod private { + use super::{BottomScreen, TopScreen, TopScreenRight}; + + pub trait Sealed {} + + impl Sealed for TopScreen {} + impl Sealed for TopScreenRight {} + impl Sealed for BottomScreen {} +} + /// This trait is implemented by the screen structs for working with frame buffers and /// drawing to the screens. Graphics-related code can be made generic over this /// trait to work with any of the given screens. -pub trait Screen { +pub trait Screen: private::Sealed { /// Returns the libctru value for the Screen kind fn as_raw(&self) -> ctru_sys::gfxScreen_t; @@ -41,23 +51,23 @@ pub trait Screen { } } -// TODO: it might be nice to do `TopScreen` but it requires -// #![feature(adt_const_params)] which is unstable, and doesn't seem worth it -// just for this. #[non_exhaustive] -pub struct TopLeftScreen; - -#[non_exhaustive] -pub struct TopRightScreen; +pub struct TopScreen; /// A helper container for both sides of the top screen. -pub struct TopScreenInner { - left: TopLeftScreen, - right: TopRightScreen, +pub struct TopScreen3D<'a> { + // morally, this should be &mut or RefMut, but if we do + // - &mut: it means gfx can no longer be borrowed immutably while this exists + // - RefMut: we don't have an easy way to obtain Ref for the left side. + // maybe this one isn't as important since the use case is typically RefMut anyway. + // we could just return &dyn Screen instead of Ref ? + left: &'a RefCell, + right: RefCell, } -#[non_exhaustive] -pub struct TopScreen(RefCell); +// TODO: it feels a little weird to have an asymmetric separate type like this, +// but maybe if it's not `pub` it's not as weird... +struct TopScreenRight; #[non_exhaustive] pub struct BottomScreen; @@ -81,7 +91,7 @@ pub struct RawFrameBuffer<'screen> { /// Side of top screen framebuffer /// /// The top screen of the 3DS can have two separate sets of framebuffers to support its 3D functionality -pub enum Side { +enum Side { /// The left framebuffer. This framebuffer is also the one used when 3D is disabled Left, /// The right framebuffer @@ -94,7 +104,7 @@ pub enum Side { /// The service exits when this struct is dropped. #[non_exhaustive] pub struct Gfx { - pub top_screen: TopScreen, + pub top_screen: RefCell, pub bottom_screen: RefCell, _service_handler: ServiceReference, } @@ -123,7 +133,7 @@ impl Gfx { )?; Ok(Self { - top_screen: TopScreen::new(TopLeftScreen, TopRightScreen), + top_screen: RefCell::new(TopScreen), bottom_screen: RefCell::new(BottomScreen), _service_handler, }) @@ -163,87 +173,73 @@ impl Gfx { } } -impl TopScreen { - fn new(left: TopLeftScreen, right: TopRightScreen) -> Self { - Self(RefCell::new(TopScreenInner { left, right })) +impl<'screen> RawFrameBuffer<'screen> { + fn for_screen_side(screen: &'screen mut dyn Screen, side: Side) -> Self { + let mut width = 0; + let mut height = 0; + let ptr = unsafe { + ctru_sys::gfxGetFramebuffer(screen.as_raw(), side.into(), &mut width, &mut height) + }; + Self { + ptr, + width, + height, + screen: PhantomData, + } } +} - pub fn borrow(&self) -> Ref<'_, TopScreenInner> { - self.0.borrow() +impl TopScreen3D<'_> { + pub fn left(&self) -> Ref { + self.left.borrow() } - pub fn borrow_mut(&self) -> RefMut<'_, TopScreenInner> { - self.0.borrow_mut() + pub fn left_mut(&self) -> RefMut { + self.left.borrow_mut() } - pub fn borrow_side(&self, side: Side) -> Ref<'_, dyn Screen> { - let borrow = self.0.borrow(); - match side { - Side::Left => Ref::map(borrow, |top| &top.left), - Side::Right => Ref::map(borrow, |top| &top.right), - } + pub fn right(&self) -> Ref { + self.right.borrow() } - pub fn borrow_side_mut(&self, side: Side) -> RefMut<'_, dyn Screen> { - let borrow = self.0.borrow_mut(); - match side { - Side::Left => RefMut::map(borrow, |top| &mut top.left), - Side::Right => RefMut::map(borrow, |top| &mut top.right), - } + pub fn right_mut(&self) -> RefMut { + self.right.borrow_mut() } } -impl TopScreenInner { - /// Enable or disable the 3D stereoscopic effect - pub fn set_3d_enabled(&mut self, enabled: bool) { +impl<'a> From<&'a RefCell> for TopScreen3D<'a> { + fn from(top_screen: &'a RefCell) -> Self { unsafe { - ctru_sys::gfxSet3D(enabled); + ctru_sys::gfxSet3D(true); } - } - - /// Enable or disable the wide screen mode (top screen). - /// This only works when 3D is disabled. - pub fn set_wide_mode(&mut self, enabled: bool) { - unsafe { - ctru_sys::gfxSetWide(enabled); + TopScreen3D { + left: top_screen, + right: RefCell::new(TopScreenRight), } } - - /// Get the status of wide screen mode. - pub fn get_wide_mode(&self) -> bool { - unsafe { ctru_sys::gfxIsWide() } - } } -impl<'screen> RawFrameBuffer<'screen> { - fn for_screen_side(screen: &'screen mut dyn Screen, side: Side) -> Self { - let mut width = 0; - let mut height = 0; - let ptr = unsafe { - ctru_sys::gfxGetFramebuffer(screen.as_raw(), side.into(), &mut width, &mut height) - }; - Self { - ptr, - width, - height, - screen: PhantomData, +impl Drop for TopScreen3D<'_> { + fn drop(&mut self) { + unsafe { + ctru_sys::gfxSet3D(false); } } } -impl Screen for TopScreenInner { - fn as_raw(&self) -> ctru_sys::gfxScreen_t { - self.left.as_raw() +impl TopScreen { + pub fn set_wide_mode(&mut self, enable: bool) { + unsafe { + ctru_sys::gfxSetWide(enable); + } } - fn get_raw_framebuffer(&mut self) -> RawFrameBuffer { - // When dealing with the "whole" top screen, the left framebuffer is the - // one used for writing pixel data. - self.left.get_raw_framebuffer() + pub fn get_wide_mode(&self) -> bool { + unsafe { ctru_sys::gfxIsWide() } } } -impl Screen for TopLeftScreen { +impl Screen for TopScreen { fn as_raw(&self) -> ctru_sys::gfxScreen_t { ctru_sys::GFX_TOP } @@ -253,7 +249,7 @@ impl Screen for TopLeftScreen { } } -impl Screen for TopRightScreen { +impl Screen for TopScreenRight { fn as_raw(&self) -> ctru_sys::gfxScreen_t { ctru_sys::GFX_TOP } From df14df484ea8d30427ade255b7023a1f614247fe Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Wed, 23 Nov 2022 20:11:20 -0500 Subject: [PATCH 3/5] Add some more docs, re-expose Side in trait method --- ctru-rs/src/gfx.rs | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/ctru-rs/src/gfx.rs b/ctru-rs/src/gfx.rs index e19f4bb..6871daa 100644 --- a/ctru-rs/src/gfx.rs +++ b/ctru-rs/src/gfx.rs @@ -23,14 +23,20 @@ mod private { /// drawing to the screens. Graphics-related code can be made generic over this /// trait to work with any of the given screens. pub trait Screen: private::Sealed { - /// Returns the libctru value for the Screen kind + /// Returns the `libctru` value for the Screen kind. fn as_raw(&self) -> ctru_sys::gfxScreen_t; + /// Returns the Screen side (left or right). + fn side(&self) -> Side; + /// Returns a [`RawFrameBuffer`] for the screen. /// /// Note that the pointer of the framebuffer returned by this function can /// change after each call to this function if double buffering is enabled. - fn get_raw_framebuffer(&mut self) -> RawFrameBuffer; + fn get_raw_framebuffer(&mut self) -> RawFrameBuffer { + let side = self.side(); + RawFrameBuffer::for_screen_side(self, side) + } /// Sets whether to use double buffering. Enabled by default. /// @@ -52,16 +58,19 @@ pub trait Screen: private::Sealed { } #[non_exhaustive] +/// The top screen. Mutable access to this struct is required to write to the top +/// screen's frame buffer. To enable 3D mode, it can be converted into a [`TopScreen3D`]. pub struct TopScreen; -/// A helper container for both sides of the top screen. -pub struct TopScreen3D<'a> { +/// A helper container for both sides of the top screen. Once the [`TopScreen`] is +/// converted into this, 3D mode will be enabled until this struct is dropped. +pub struct TopScreen3D<'top_screen> { // morally, this should be &mut or RefMut, but if we do // - &mut: it means gfx can no longer be borrowed immutably while this exists // - RefMut: we don't have an easy way to obtain Ref for the left side. // maybe this one isn't as important since the use case is typically RefMut anyway. // we could just return &dyn Screen instead of Ref ? - left: &'a RefCell, + left: &'top_screen RefCell, right: RefCell, } @@ -70,6 +79,8 @@ pub struct TopScreen3D<'a> { struct TopScreenRight; #[non_exhaustive] +/// The bottom screen. Mutable access to this struct is required to write to the +/// bottom screen's frame buffer. pub struct BottomScreen; /// Representation of a framebuffer for one [`Side`] of the top screen, or the @@ -91,7 +102,7 @@ pub struct RawFrameBuffer<'screen> { /// Side of top screen framebuffer /// /// The top screen of the 3DS can have two separate sets of framebuffers to support its 3D functionality -enum Side { +pub enum Side { /// The left framebuffer. This framebuffer is also the one used when 3D is disabled Left, /// The right framebuffer @@ -174,7 +185,7 @@ impl Gfx { } impl<'screen> RawFrameBuffer<'screen> { - fn for_screen_side(screen: &'screen mut dyn Screen, side: Side) -> Self { + fn for_screen_side(screen: &'screen mut (impl Screen + ?Sized), side: Side) -> Self { let mut width = 0; let mut height = 0; let ptr = unsafe { @@ -190,18 +201,22 @@ impl<'screen> RawFrameBuffer<'screen> { } impl TopScreen3D<'_> { + /// Immutably borrow the left side of the screen. pub fn left(&self) -> Ref { self.left.borrow() } + /// Mutably borrow the left side of the screen. pub fn left_mut(&self) -> RefMut { self.left.borrow_mut() } + /// Immutably borrow the right side of the screen. pub fn right(&self) -> Ref { self.right.borrow() } + /// Mutably borrow the right side of the screen. pub fn right_mut(&self) -> RefMut { self.right.borrow_mut() } @@ -228,12 +243,14 @@ impl Drop for TopScreen3D<'_> { } impl TopScreen { + /// Enable or disable wide mode on the top screen. pub fn set_wide_mode(&mut self, enable: bool) { unsafe { ctru_sys::gfxSetWide(enable); } } + /// Returns whether or not wide mode is enabled on the top screen. pub fn get_wide_mode(&self) -> bool { unsafe { ctru_sys::gfxIsWide() } } @@ -244,8 +261,8 @@ impl Screen for TopScreen { ctru_sys::GFX_TOP } - fn get_raw_framebuffer(&mut self) -> RawFrameBuffer { - RawFrameBuffer::for_screen_side(self, Side::Left) + fn side(&self) -> Side { + Side::Left } } @@ -254,8 +271,8 @@ impl Screen for TopScreenRight { ctru_sys::GFX_TOP } - fn get_raw_framebuffer(&mut self) -> RawFrameBuffer { - RawFrameBuffer::for_screen_side(self, Side::Right) + fn side(&self) -> Side { + Side::Right } } @@ -264,8 +281,8 @@ impl Screen for BottomScreen { ctru_sys::GFX_BOTTOM } - fn get_raw_framebuffer(&mut self) -> RawFrameBuffer { - RawFrameBuffer::for_screen_side(self, Side::Left) + fn side(&self) -> Side { + Side::Left } } From 7718269b198859779fefde277ca811b6f87cda71 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sun, 27 Nov 2022 19:29:15 -0700 Subject: [PATCH 4/5] Address more comments Use symmetric left/right top screen types, but keep the public API the same. Switch const -> static where feasible in the example. --- ctru-rs/examples/gfx-3d-mode.rs | 2 +- ctru-rs/src/gfx.rs | 68 +++++++++++++++++++-------------- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/ctru-rs/examples/gfx-3d-mode.rs b/ctru-rs/examples/gfx-3d-mode.rs index e6f82a2..bd678a2 100644 --- a/ctru-rs/examples/gfx-3d-mode.rs +++ b/ctru-rs/examples/gfx-3d-mode.rs @@ -7,7 +7,7 @@ use ctru::prelude::*; /// probably not be viewed for too long or at all if you are photosensitive. const IMAGE: &[u8] = include_bytes!("assets/ferris.rgb"); -const ZERO: &[u8] = &[0; IMAGE.len()]; +static ZERO: &[u8] = &[0; IMAGE.len()]; fn main() { ctru::init(); diff --git a/ctru-rs/src/gfx.rs b/ctru-rs/src/gfx.rs index 6871daa..05cb7c9 100644 --- a/ctru-rs/src/gfx.rs +++ b/ctru-rs/src/gfx.rs @@ -10,11 +10,12 @@ use crate::services::gspgpu::{self, FramebufferFormat}; use crate::services::ServiceReference; mod private { - use super::{BottomScreen, TopScreen, TopScreenRight}; + use super::{BottomScreen, TopScreen, TopScreenLeft, TopScreenRight}; pub trait Sealed {} impl Sealed for TopScreen {} + impl Sealed for TopScreenLeft {} impl Sealed for TopScreenRight {} impl Sealed for BottomScreen {} } @@ -60,22 +61,19 @@ pub trait Screen: private::Sealed { #[non_exhaustive] /// The top screen. Mutable access to this struct is required to write to the top /// screen's frame buffer. To enable 3D mode, it can be converted into a [`TopScreen3D`]. -pub struct TopScreen; +pub struct TopScreen { + left: TopScreenLeft, + right: TopScreenRight, +} /// A helper container for both sides of the top screen. Once the [`TopScreen`] is /// converted into this, 3D mode will be enabled until this struct is dropped. pub struct TopScreen3D<'top_screen> { - // morally, this should be &mut or RefMut, but if we do - // - &mut: it means gfx can no longer be borrowed immutably while this exists - // - RefMut: we don't have an easy way to obtain Ref for the left side. - // maybe this one isn't as important since the use case is typically RefMut anyway. - // we could just return &dyn Screen instead of Ref ? - left: &'top_screen RefCell, - right: RefCell, + screen: &'top_screen RefCell, } -// TODO: it feels a little weird to have an asymmetric separate type like this, -// but maybe if it's not `pub` it's not as weird... +struct TopScreenLeft; + struct TopScreenRight; #[non_exhaustive] @@ -132,7 +130,7 @@ impl Gfx { bottom_fb_fmt: FramebufferFormat, use_vram_buffers: bool, ) -> Result { - let _service_handler = ServiceReference::new( + let handler = ServiceReference::new( &GFX_ACTIVE, false, || unsafe { @@ -144,9 +142,9 @@ impl Gfx { )?; Ok(Self { - top_screen: RefCell::new(TopScreen), + top_screen: RefCell::new(TopScreen::new()), bottom_screen: RefCell::new(BottomScreen), - _service_handler, + _service_handler: handler, }) } @@ -203,34 +201,32 @@ impl<'screen> RawFrameBuffer<'screen> { impl TopScreen3D<'_> { /// Immutably borrow the left side of the screen. pub fn left(&self) -> Ref { - self.left.borrow() + Ref::map(self.screen.borrow(), |screen| &screen.left) } /// Mutably borrow the left side of the screen. pub fn left_mut(&self) -> RefMut { - self.left.borrow_mut() + RefMut::map(self.screen.borrow_mut(), |screen| &mut screen.left) } /// Immutably borrow the right side of the screen. pub fn right(&self) -> Ref { - self.right.borrow() + Ref::map(self.screen.borrow(), |screen| &screen.right) } /// Mutably borrow the right side of the screen. pub fn right_mut(&self) -> RefMut { - self.right.borrow_mut() + RefMut::map(self.screen.borrow_mut(), |screen| &mut screen.right) } } -impl<'a> From<&'a RefCell> for TopScreen3D<'a> { - fn from(top_screen: &'a RefCell) -> Self { +impl<'top_screen> From<&'top_screen RefCell> for TopScreen3D<'top_screen> { + fn from(top_screen: &'top_screen RefCell) -> Self { unsafe { ctru_sys::gfxSet3D(true); } - TopScreen3D { - left: top_screen, - right: RefCell::new(TopScreenRight), - } + + TopScreen3D { screen: top_screen } } } @@ -243,6 +239,13 @@ impl Drop for TopScreen3D<'_> { } impl TopScreen { + fn new() -> Self { + Self { + left: TopScreenLeft, + right: TopScreenRight, + } + } + /// Enable or disable wide mode on the top screen. pub fn set_wide_mode(&mut self, enable: bool) { unsafe { @@ -257,6 +260,16 @@ impl TopScreen { } impl Screen for TopScreen { + fn as_raw(&self) -> ctru_sys::gfxScreen_t { + self.left.as_raw() + } + + fn side(&self) -> Side { + self.left.side() + } +} + +impl Screen for TopScreenLeft { fn as_raw(&self) -> ctru_sys::gfxScreen_t { ctru_sys::GFX_TOP } @@ -288,10 +301,9 @@ impl Screen for BottomScreen { impl From for ctru_sys::gfx3dSide_t { fn from(s: Side) -> ctru_sys::gfx3dSide_t { - use self::Side::*; match s { - Left => ctru_sys::GFX_LEFT, - Right => ctru_sys::GFX_RIGHT, + Side::Left => ctru_sys::GFX_LEFT, + Side::Right => ctru_sys::GFX_RIGHT, } } } @@ -304,6 +316,6 @@ mod tests { #[test] fn gfx_duplicate() { // We don't need to build a `Gfx` because the test runner has one already - assert!(matches!(Gfx::init(), Err(Error::ServiceAlreadyActive))) + assert!(matches!(Gfx::init(), Err(Error::ServiceAlreadyActive))); } } From e02f7443b1cbd0cabe0eb6a4f37757e06ccc9298 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Mon, 16 Jan 2023 18:33:17 -0500 Subject: [PATCH 5/5] Address review comments, improve example This fixes some glitchy rendering by redrawing everything every frame, and using double buffering. Also fix double-borrowing issue with split top screen. --- ctru-rs/examples/gfx-3d-mode.rs | 56 ++++++++++++++++----------------- ctru-rs/src/gfx.rs | 56 +++++++++++++-------------------- 2 files changed, 48 insertions(+), 64 deletions(-) diff --git a/ctru-rs/examples/gfx-3d-mode.rs b/ctru-rs/examples/gfx-3d-mode.rs index bd678a2..df0593f 100644 --- a/ctru-rs/examples/gfx-3d-mode.rs +++ b/ctru-rs/examples/gfx-3d-mode.rs @@ -1,4 +1,4 @@ -use ctru::gfx::TopScreen3D; +use ctru::gfx::{Screen, Side, TopScreen3D}; use ctru::prelude::*; /// See `graphics-bitmap.rs` for details on how the image is generated. @@ -16,24 +16,14 @@ fn main() { let apt = Apt::init().expect("Couldn't obtain APT controller"); let _console = Console::init(gfx.bottom_screen.borrow_mut()); - println!("Press Start to exit.\nPress A to switch which side is drawn to."); + println!("Press Start to exit.\nPress A to switch sides (be sure to have 3D mode enabled)."); - let top_screen = TopScreen3D::from(&gfx.top_screen); - - // TODO set double buffering for top screen - - let mut left = top_screen.left_mut(); - let left_buf = left.get_raw_framebuffer(); - let mut right = top_screen.right_mut(); - let right_buf = right.get_raw_framebuffer(); + gfx.top_screen.borrow_mut().set_double_buffering(true); - // We assume the image is the correct size already, so we ignore width + height. - let mut buf = left_buf.ptr; + let top_screen = TopScreen3D::from(&gfx.top_screen); + let (mut left, mut right) = top_screen.split_mut(); - // Copy the image into the left-side frame buffer - unsafe { - buf.copy_from(IMAGE.as_ptr(), IMAGE.len()); - } + let mut current_side = Side::Left; // Main loop while apt.main_loop() { @@ -44,22 +34,30 @@ fn main() { break; } + let left_buf = left.get_raw_framebuffer(); + let right_buf = right.get_raw_framebuffer(); + + // Clear both buffers every time, in case the user switches sides this loop + unsafe { + left_buf.ptr.copy_from(ZERO.as_ptr(), ZERO.len()); + right_buf.ptr.copy_from(ZERO.as_ptr(), ZERO.len()); + } + if hid.keys_down().contains(KeyPad::KEY_A) { - // Clear the side we just drew to by zeroing it out - unsafe { - buf.copy_from(ZERO.as_ptr(), ZERO.len()); - } - - // flip which buffer we're writing to, and redraw the image - buf = if buf == left_buf.ptr { - right_buf.ptr - } else { - left_buf.ptr + // flip which buffer we're writing to + current_side = match current_side { + Side::Left => Side::Right, + Side::Right => Side::Left, }; + } + + let buf = match current_side { + Side::Left => left_buf.ptr, + Side::Right => right_buf.ptr, + }; - unsafe { - buf.copy_from(IMAGE.as_ptr(), IMAGE.len()); - } + unsafe { + buf.copy_from(IMAGE.as_ptr(), IMAGE.len()); } // Flush and swap framebuffers diff --git a/ctru-rs/src/gfx.rs b/ctru-rs/src/gfx.rs index 05cb7c9..2e83832 100644 --- a/ctru-rs/src/gfx.rs +++ b/ctru-rs/src/gfx.rs @@ -35,8 +35,17 @@ pub trait Screen: private::Sealed { /// Note that the pointer of the framebuffer returned by this function can /// change after each call to this function if double buffering is enabled. fn get_raw_framebuffer(&mut self) -> RawFrameBuffer { - let side = self.side(); - RawFrameBuffer::for_screen_side(self, side) + let mut width = 0; + let mut height = 0; + let ptr = unsafe { + ctru_sys::gfxGetFramebuffer(self.as_raw(), self.side().into(), &mut width, &mut height) + }; + RawFrameBuffer { + ptr, + width, + height, + screen: PhantomData, + } } /// Sets whether to use double buffering. Enabled by default. @@ -58,7 +67,6 @@ pub trait Screen: private::Sealed { } } -#[non_exhaustive] /// The top screen. Mutable access to this struct is required to write to the top /// screen's frame buffer. To enable 3D mode, it can be converted into a [`TopScreen3D`]. pub struct TopScreen { @@ -182,41 +190,19 @@ impl Gfx { } } -impl<'screen> RawFrameBuffer<'screen> { - fn for_screen_side(screen: &'screen mut (impl Screen + ?Sized), side: Side) -> Self { - let mut width = 0; - let mut height = 0; - let ptr = unsafe { - ctru_sys::gfxGetFramebuffer(screen.as_raw(), side.into(), &mut width, &mut height) - }; - Self { - ptr, - width, - height, - screen: PhantomData, - } - } -} - impl TopScreen3D<'_> { - /// Immutably borrow the left side of the screen. - pub fn left(&self) -> Ref { - Ref::map(self.screen.borrow(), |screen| &screen.left) - } - - /// Mutably borrow the left side of the screen. - pub fn left_mut(&self) -> RefMut { - RefMut::map(self.screen.borrow_mut(), |screen| &mut screen.left) - } - - /// Immutably borrow the right side of the screen. - pub fn right(&self) -> Ref { - Ref::map(self.screen.borrow(), |screen| &screen.right) + /// Immutably borrow the two sides of the screen as `(left, right)`. + pub fn split(&self) -> (Ref, Ref) { + Ref::map_split(self.screen.borrow(), |screen| { + (&screen.left as _, &screen.right as _) + }) } - /// Mutably borrow the right side of the screen. - pub fn right_mut(&self) -> RefMut { - RefMut::map(self.screen.borrow_mut(), |screen| &mut screen.right) + /// Mutably borrow the two sides of the screen as `(left, right)`. + pub fn split_mut(&self) -> (RefMut, RefMut) { + RefMut::map_split(self.screen.borrow_mut(), |screen| { + (&mut screen.left as _, &mut screen.right as _) + }) } }