From 6c7a74f5f611b7c9a41385efb00deb1174c50575 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Mon, 21 Nov 2022 20:22:32 -0500 Subject: [PATCH] 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 }