diff --git a/ctru-rs/examples/camera-image.rs b/ctru-rs/examples/camera-image.rs index de97712..7eb6252 100644 --- a/ctru-rs/examples/camera-image.rs +++ b/ctru-rs/examples/camera-image.rs @@ -1,5 +1,5 @@ use ctru::console::Console; -use ctru::gfx::{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}; @@ -88,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/file-explorer.rs b/ctru-rs/examples/file-explorer.rs index 1cff988..6594f16 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/gfx-3d-mode.rs b/ctru-rs/examples/gfx-3d-mode.rs new file mode 100644 index 0000000..df0593f --- /dev/null +++ b/ctru-rs/examples/gfx-3d-mode.rs @@ -0,0 +1,70 @@ +use ctru::gfx::{Screen, Side, 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"); +static 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 sides (be sure to have 3D mode enabled)."); + + gfx.top_screen.borrow_mut().set_double_buffering(true); + + let top_screen = TopScreen3D::from(&gfx.top_screen); + let (mut left, mut right) = top_screen.split_mut(); + + let mut current_side = Side::Left; + + // 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; + } + + 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) { + // 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()); + } + + // Flush and swap framebuffers + gfx.flush_buffers(); + gfx.swap_buffers(); + + //Wait for VBlank + gfx.wait_for_vblank(); + } +} diff --git a/ctru-rs/src/gfx.rs b/ctru-rs/src/gfx.rs index 6400d97..2e83832 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,45 @@ use crate::error::Result; use crate::services::gspgpu::{self, FramebufferFormat}; use crate::services::ServiceReference; -/// Trait implemented by TopScreen and BottomScreen for common methods -pub trait Screen { - /// Returns the libctru value for the Screen kind +mod private { + 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 {} +} + +/// 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: private::Sealed { + /// 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 { + 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. /// /// Note that even when double buffering is disabled, one should still use the `swap_buffers` @@ -33,10 +67,26 @@ pub trait Screen { } } -#[non_exhaustive] -pub struct TopScreen; +/// 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 { + 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> { + screen: &'top_screen RefCell, +} + +struct TopScreenLeft; + +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 @@ -88,7 +138,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 { @@ -100,14 +150,15 @@ 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, }) } - /// 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) } @@ -139,80 +190,106 @@ impl Gfx { } } -impl TopScreen { - /// Enable or disable the 3D stereoscopic effect - pub fn set_3d_enabled(&mut self, enabled: bool) { +impl TopScreen3D<'_> { + /// 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 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 _) + }) + } +} + +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 { screen: top_screen } + } +} + +impl Drop for TopScreen3D<'_> { + fn drop(&mut self) { unsafe { - ctru_sys::gfxSet3D(enabled); + ctru_sys::gfxSet3D(false); + } + } +} + +impl TopScreen { + fn new() -> Self { + Self { + left: TopScreenLeft, + right: TopScreenRight, } } - /// 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) { + /// Enable or disable wide mode on the top screen. + pub fn set_wide_mode(&mut self, enable: bool) { unsafe { - ctru_sys::gfxSetWide(enabled); + ctru_sys::gfxSetWide(enable); } } - /// Get the status of wide screen mode. + /// Returns whether or not wide mode is enabled on the top screen. 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 Screen for TopScreen { + fn as_raw(&self) -> ctru_sys::gfxScreen_t { + self.left.as_raw() } -} -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) + fn side(&self) -> Side { + self.left.side() } } -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 Screen for TopScreenLeft { + fn as_raw(&self) -> ctru_sys::gfxScreen_t { + ctru_sys::GFX_TOP + } + + fn side(&self) -> Side { + Side::Left } } -impl Screen for TopScreen { +impl Screen for TopScreenRight { fn as_raw(&self) -> ctru_sys::gfxScreen_t { ctru_sys::GFX_TOP } + + fn side(&self) -> Side { + Side::Right + } } impl Screen for BottomScreen { fn as_raw(&self) -> ctru_sys::gfxScreen_t { ctru_sys::GFX_BOTTOM } + + fn side(&self) -> Side { + Side::Left + } } 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, } } } @@ -225,6 +302,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))); } }