diff --git a/ctru-rs/Cargo.toml b/ctru-rs/Cargo.toml index a7a160c..a79e7ac 100644 --- a/ctru-rs/Cargo.toml +++ b/ctru-rs/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = [ "Rust3DS Org", "Ronald Kinard " ] +authors = ["Rust3DS Org", "Ronald Kinard "] description = "A safe wrapper around smealum's ctrulib." license = "Zlib" name = "ctru-rs" diff --git a/ctru-rs/examples/camera-image.rs b/ctru-rs/examples/camera-image.rs index dcc92ff..0042599 100644 --- a/ctru-rs/examples/camera-image.rs +++ b/ctru-rs/examples/camera-image.rs @@ -1,6 +1,6 @@ use ctru::prelude::*; use ctru::services::cam::{Cam, Camera, OutputFormat, ShutterSound, ViewSize}; -use ctru::services::gfx::Screen; +use ctru::services::gfx::{Flush, Screen, Swap}; use ctru::services::gspgpu::FramebufferFormat; use std::time::Duration; @@ -88,7 +88,7 @@ fn main() { rotate_image_to_screen(&buf, top_screen.raw_framebuffer().ptr, WIDTH, HEIGHT); // We will only flush the "camera" screen, since the other screen is handled by `Console` - top_screen.flush_buffer(); + top_screen.flush_buffers(); top_screen.swap_buffers(); gfx.wait_for_vblank(); diff --git a/ctru-rs/examples/gfx-3d-mode.rs b/ctru-rs/examples/gfx-3d-mode.rs index 5bd982f..582994e 100644 --- a/ctru-rs/examples/gfx-3d-mode.rs +++ b/ctru-rs/examples/gfx-3d-mode.rs @@ -1,5 +1,5 @@ use ctru::prelude::*; -use ctru::services::gfx::{Screen, Side, TopScreen3D}; +use ctru::services::gfx::{Flush, Screen, Side, Swap, TopScreen3D}; /// See `graphics-bitmap.rs` for details on how the image is generated. /// @@ -21,8 +21,7 @@ fn main() { 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 top_screen = TopScreen3D::from(&gfx.top_screen); let mut current_side = Side::Left; @@ -35,6 +34,8 @@ fn main() { break; } + let (mut left, mut right) = top_screen.split_mut(); + let left_buf = left.raw_framebuffer(); let right_buf = right.raw_framebuffer(); @@ -61,8 +62,10 @@ fn main() { buf.copy_from(IMAGE.as_ptr(), IMAGE.len()); } - left.flush_buffer(); - left.swap_buffers(); + drop((left, right)); + + top_screen.flush_buffers(); + top_screen.swap_buffers(); //Wait for VBlank gfx.wait_for_vblank(); diff --git a/ctru-rs/examples/gfx-bitmap.rs b/ctru-rs/examples/gfx-bitmap.rs new file mode 100644 index 0000000..f680ff2 --- /dev/null +++ b/ctru-rs/examples/gfx-bitmap.rs @@ -0,0 +1,74 @@ +use ctru::prelude::*; +use ctru::services::gfx::{Flush, Screen, Swap}; + +/// Ferris image taken from and scaled down to 320x240px. +/// To regenerate the data, you will need to install `imagemagick` and run this +/// command from the `examples` directory: +/// +/// ```sh +/// magick assets/ferris.png -channel-fx "red<=>blue" -rotate 90 assets/ferris.rgb +/// ``` +/// +/// This creates an image appropriate for the default frame buffer format of +/// [`Bgr8`](ctru::services::gspgpu::FramebufferFormat::Bgr8) +/// and rotates the image 90° to account for the portrait mode screen. +static IMAGE: &[u8] = include_bytes!("assets/ferris.rgb"); + +fn main() { + ctru::use_panic_handler(); + + let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); + let mut hid = Hid::new().expect("Couldn't obtain HID controller"); + let apt = Apt::new().expect("Couldn't obtain APT controller"); + let _console = Console::new(gfx.top_screen.borrow_mut()); + + println!("\x1b[21;4HPress Start to exit, or A to flip the image."); + + let mut bottom_screen = gfx.bottom_screen.borrow_mut(); + + // We don't need double buffering in this example. + // In this way we can draw our image only once on screen. + bottom_screen.set_double_buffering(false); + // Swapping buffers commits the change from the line above. + bottom_screen.swap_buffers(); + + // 3 bytes per pixel, we just want to reverse the pixels but not individual bytes + let flipped_image: Vec<_> = IMAGE.chunks(3).rev().flatten().copied().collect(); + + let mut image_bytes = IMAGE; + + // 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::START) { + break; + } + + // We assume the image is the correct size already, so we drop width + height. + let frame_buffer = bottom_screen.raw_framebuffer(); + + if hid.keys_down().contains(KeyPad::A) { + image_bytes = if std::ptr::eq(image_bytes, IMAGE) { + &flipped_image[..] + } else { + IMAGE + }; + } + + // this copies more than necessary (once per frame) but it's fine... + unsafe { + frame_buffer + .ptr + .copy_from(image_bytes.as_ptr(), image_bytes.len()); + } + + // Flush framebuffers. Since we're not using double buffering, + // this will render the pixels immediately + bottom_screen.flush_buffers(); + + //Wait for VBlank + gfx.wait_for_vblank(); + } +} diff --git a/ctru-rs/examples/graphics-bitmap.rs b/ctru-rs/examples/graphics-bitmap.rs index 4beacea..df9ce11 100644 --- a/ctru-rs/examples/graphics-bitmap.rs +++ b/ctru-rs/examples/graphics-bitmap.rs @@ -1,5 +1,5 @@ use ctru::prelude::*; -use ctru::services::gfx::Screen; +use ctru::services::gfx::{Flush, Screen, Swap}; /// Ferris image taken from and scaled down to 320x240px. /// To regenerate the data, you will need to install `imagemagick` and run this @@ -48,7 +48,7 @@ fn main() { } // Flush and swap framebuffers - bottom_screen.flush_buffer(); + bottom_screen.flush_buffers(); bottom_screen.swap_buffers(); //Wait for VBlank diff --git a/ctru-rs/src/services/gfx.rs b/ctru-rs/src/services/gfx.rs index 9ac0a34..3a5ba37 100644 --- a/ctru-rs/src/services/gfx.rs +++ b/ctru-rs/src/services/gfx.rs @@ -9,11 +9,12 @@ use crate::services::gspgpu::{self, FramebufferFormat}; use crate::services::ServiceReference; mod private { - use super::{BottomScreen, TopScreen, TopScreenLeft, TopScreenRight}; + use super::{BottomScreen, TopScreen, TopScreen3D, TopScreenLeft, TopScreenRight}; pub trait Sealed {} impl Sealed for TopScreen {} + impl Sealed for TopScreen3D<'_> {} impl Sealed for TopScreenLeft {} impl Sealed for TopScreenRight {} impl Sealed for BottomScreen {} @@ -49,38 +50,21 @@ pub trait Screen: private::Sealed { /// Sets whether to use double buffering. Enabled by default. /// - /// Note that even when double buffering is disabled, one should still use the `swap_buffers` - /// method on each frame to keep the gsp configuration up to date + /// [`Swap::swap_buffers`] must be called after this function for the configuration + /// change to take effect. fn set_double_buffering(&mut self, enabled: bool) { unsafe { ctru_sys::gfxSetDoubleBuffering(self.as_raw(), enabled) } } - /// Flushes the video buffer for this screen. - fn flush_buffer(&mut self) { - let framebuffer = self.raw_framebuffer(); - - // Flush the data array. `self.raw_framebuffer` should get the correct parameters for all kinds of screens - unsafe { - ctru_sys::GSPGPU_FlushDataCache( - framebuffer.ptr.cast(), - (framebuffer.height * framebuffer.width) as u32, - ) - }; - } - - /// Swaps the video buffers. - /// - /// This should be used even if double buffering is disabled. - fn swap_buffers(&mut self) { - unsafe { ctru_sys::gfxScreenSwapBuffers(self.side().into(), true) }; - } - - /// Gets the framebuffer format + /// Gets the framebuffer format. fn framebuffer_format(&self) -> FramebufferFormat { unsafe { ctru_sys::gfxGetScreenFormat(self.as_raw()) }.into() } - /// Change the framebuffer format + /// Change the framebuffer format. + /// + /// [`Swap::swap_buffers`] must be called after this method for the configuration + /// change to take effect. fn set_framebuffer_format(&mut self, fmt: FramebufferFormat) { unsafe { ctru_sys::gfxSetScreenFormat(self.as_raw(), fmt.into()) } } @@ -95,17 +79,98 @@ pub struct TopScreen { /// 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, +pub struct TopScreen3D<'screen> { + screen: &'screen RefCell, +} + +/// A screen that can have its frame buffers swapped, if double buffering is enabled. +/// +/// This trait applies to all [`Screen`]s that have swappable frame buffers. +pub trait Swap: private::Sealed { + /// Swaps the video buffers. + /// + /// If double buffering is disabled, "swapping" the buffers has the side effect + /// of committing any configuration changes to the buffers (e.g. [`set_wide_mode`], + /// [`set_framebuffer_format`], [`set_double_buffering`]). + /// + /// This should be called once per frame at most. + /// + /// [`set_wide_mode`]: TopScreen::set_wide_mode + /// [`set_framebuffer_format`]: Screen::set_framebuffer_format + /// [`set_double_buffering`]: Screen::set_double_buffering + fn swap_buffers(&mut self); +} + +impl Swap for TopScreen3D<'_> { + fn swap_buffers(&mut self) { + unsafe { + ctru_sys::gfxScreenSwapBuffers(ctru_sys::GFX_TOP, true); + } + } +} + +impl Swap for TopScreen { + fn swap_buffers(&mut self) { + unsafe { + ctru_sys::gfxScreenSwapBuffers(ctru_sys::GFX_TOP, false); + } + } } -struct TopScreenLeft; +impl Swap for BottomScreen { + fn swap_buffers(&mut self) { + unsafe { + ctru_sys::gfxScreenSwapBuffers(ctru_sys::GFX_BOTTOM, false); + } + } +} -struct TopScreenRight; +/// A screen with buffers that can be flushed. This trait applies to any [`Screen`] +/// that has data written to its frame buffer. +pub trait Flush: private::Sealed { + /// Flushes the video buffer(s) for this screen. Note that you must still call + /// [`Swap::swap_buffers`] after this method for the buffer contents to be displayed. + fn flush_buffers(&mut self); +} +impl Flush for S { + fn flush_buffers(&mut self) { + let framebuffer = self.raw_framebuffer(); + + // Flush the data array. `self.raw_framebuffer` should get the correct parameters for all kinds of screens + unsafe { + ctru_sys::GSPGPU_FlushDataCache( + framebuffer.ptr.cast(), + (framebuffer.height * framebuffer.width) as u32, + ) + }; + } +} + +impl Flush for TopScreen3D<'_> { + /// Unlike most other implementations of [`Flush`], this flushes the buffers for both + /// the left and right sides of the top screen. + fn flush_buffers(&mut self) { + let (mut left, mut right) = self.split_mut(); + left.flush_buffers(); + right.flush_buffers(); + } +} + +/// The left side of the top screen, when using 3D mode. +#[derive(Debug)] #[non_exhaustive] +pub struct TopScreenLeft; + +/// The right side of the top screen, when using 3D mode. +#[derive(Debug)] +#[non_exhaustive] +pub struct TopScreenRight; + /// The bottom screen. Mutable access to this struct is required to write to the /// bottom screen's frame buffer. +#[derive(Debug)] +#[non_exhaustive] pub struct BottomScreen; /// Representation of a framebuffer for one [`Side`] of the top screen, or the @@ -192,22 +257,20 @@ impl Gfx { 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 _) - }) + pub fn split(&self) -> (Ref, Ref) { + Ref::map_split(self.screen.borrow(), |screen| (&screen.left, &screen.right)) } /// Mutably borrow the two sides of the screen as `(left, right)`. - pub fn split_mut(&self) -> (RefMut, RefMut) { + pub fn split_mut(&self) -> (RefMut, RefMut) { RefMut::map_split(self.screen.borrow_mut(), |screen| { - (&mut screen.left as _, &mut screen.right as _) + (&mut screen.left, &mut screen.right) }) } } -impl<'top_screen> From<&'top_screen RefCell> for TopScreen3D<'top_screen> { - fn from(top_screen: &'top_screen RefCell) -> Self { +impl<'screen> From<&'screen RefCell> for TopScreen3D<'screen> { + fn from(top_screen: &'screen RefCell) -> Self { unsafe { ctru_sys::gfxSet3D(true); } @@ -233,6 +296,9 @@ impl TopScreen { } /// Enable or disable wide mode on the top screen. + /// + /// [`Swap::swap_buffers`] must be called after this method for the configuration + /// to take effect. pub fn set_wide_mode(&mut self, enable: bool) { unsafe { ctru_sys::gfxSetWide(enable); @@ -245,6 +311,8 @@ impl TopScreen { } } +// When 3D mode is disabled, only the left side is used, so this Screen impl +// just forwards everything to the TopScreenLeft. impl Screen for TopScreen { fn as_raw(&self) -> ctru_sys::gfxScreen_t { self.left.as_raw()