From b239fb8d9f9074efd2bdb6660cd7ab63a151c996 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Fri, 14 Apr 2023 13:34:10 -0400 Subject: [PATCH] Update doc comments and bitmap example Clarify the behavior of `swap_buffers` for single buffering mode and update bitmap example to prove that this is actually how it works. --- .../{graphics-bitmap.rs => gfx-bitmap.rs} | 35 ++++++++++---- ctru-rs/src/services/gfx.rs | 47 +++++++++++++------ 2 files changed, 59 insertions(+), 23 deletions(-) rename ctru-rs/examples/{graphics-bitmap.rs => gfx-bitmap.rs} (58%) diff --git a/ctru-rs/examples/graphics-bitmap.rs b/ctru-rs/examples/gfx-bitmap.rs similarity index 58% rename from ctru-rs/examples/graphics-bitmap.rs rename to ctru-rs/examples/gfx-bitmap.rs index df9ce11..f680ff2 100644 --- a/ctru-rs/examples/graphics-bitmap.rs +++ b/ctru-rs/examples/gfx-bitmap.rs @@ -22,21 +22,20 @@ fn main() { let apt = Apt::new().expect("Couldn't obtain APT controller"); let _console = Console::new(gfx.top_screen.borrow_mut()); - println!("\x1b[21;16HPress Start to exit."); + 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(); - // We assume the image is the correct size already, so we drop width + height. - let frame_buffer = bottom_screen.raw_framebuffer(); + // 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(); - // Copy the image into the frame buffer - unsafe { - frame_buffer.ptr.copy_from(IMAGE.as_ptr(), IMAGE.len()); - } + let mut image_bytes = IMAGE; // Main loop while apt.main_loop() { @@ -47,9 +46,27 @@ fn main() { break; } - // Flush and swap framebuffers + // 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(); - bottom_screen.swap_buffers(); //Wait for VBlank gfx.wait_for_vblank(); diff --git a/ctru-rs/src/services/gfx.rs b/ctru-rs/src/services/gfx.rs index 59cf829..3a5ba37 100644 --- a/ctru-rs/src/services/gfx.rs +++ b/ctru-rs/src/services/gfx.rs @@ -50,18 +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) } } - /// 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()) } } @@ -76,14 +79,25 @@ 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. /// - /// This should be used even if double buffering is disabled. + /// 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); } @@ -111,6 +125,8 @@ impl Swap for BottomScreen { } } +/// 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. @@ -242,21 +258,19 @@ 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 _) - }) + 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) { 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); } @@ -282,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); @@ -294,9 +311,11 @@ 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 { - ctru_sys::GFX_TOP + self.left.as_raw() } fn side(&self) -> Side {