Browse Source

Merge pull request #118 from rust3ds/feat/flush-swap-screen-traits

Create some traits for swapping/flushing screen buffers
pull/116/head
Meziu 2 years ago committed by GitHub
parent
commit
20a8094a07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      ctru-rs/Cargo.toml
  2. 4
      ctru-rs/examples/camera-image.rs
  3. 13
      ctru-rs/examples/gfx-3d-mode.rs
  4. 74
      ctru-rs/examples/gfx-bitmap.rs
  5. 4
      ctru-rs/examples/graphics-bitmap.rs
  6. 142
      ctru-rs/src/services/gfx.rs

2
ctru-rs/Cargo.toml

@ -1,5 +1,5 @@
[package] [package]
authors = [ "Rust3DS Org", "Ronald Kinard <furyhunter600@gmail.com>" ] authors = ["Rust3DS Org", "Ronald Kinard <furyhunter600@gmail.com>"]
description = "A safe wrapper around smealum's ctrulib." description = "A safe wrapper around smealum's ctrulib."
license = "Zlib" license = "Zlib"
name = "ctru-rs" name = "ctru-rs"

4
ctru-rs/examples/camera-image.rs

@ -1,6 +1,6 @@
use ctru::prelude::*; use ctru::prelude::*;
use ctru::services::cam::{Cam, Camera, OutputFormat, ShutterSound, ViewSize}; 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 ctru::services::gspgpu::FramebufferFormat;
use std::time::Duration; use std::time::Duration;
@ -88,7 +88,7 @@ fn main() {
rotate_image_to_screen(&buf, top_screen.raw_framebuffer().ptr, WIDTH, HEIGHT); 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` // 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(); top_screen.swap_buffers();
gfx.wait_for_vblank(); gfx.wait_for_vblank();

13
ctru-rs/examples/gfx-3d-mode.rs

@ -1,5 +1,5 @@
use ctru::prelude::*; 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. /// 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); gfx.top_screen.borrow_mut().set_double_buffering(true);
let top_screen = TopScreen3D::from(&gfx.top_screen); let mut top_screen = TopScreen3D::from(&gfx.top_screen);
let (mut left, mut right) = top_screen.split_mut();
let mut current_side = Side::Left; let mut current_side = Side::Left;
@ -35,6 +34,8 @@ fn main() {
break; break;
} }
let (mut left, mut right) = top_screen.split_mut();
let left_buf = left.raw_framebuffer(); let left_buf = left.raw_framebuffer();
let right_buf = right.raw_framebuffer(); let right_buf = right.raw_framebuffer();
@ -61,8 +62,10 @@ fn main() {
buf.copy_from(IMAGE.as_ptr(), IMAGE.len()); buf.copy_from(IMAGE.as_ptr(), IMAGE.len());
} }
left.flush_buffer(); drop((left, right));
left.swap_buffers();
top_screen.flush_buffers();
top_screen.swap_buffers();
//Wait for VBlank //Wait for VBlank
gfx.wait_for_vblank(); gfx.wait_for_vblank();

74
ctru-rs/examples/gfx-bitmap.rs

@ -0,0 +1,74 @@
use ctru::prelude::*;
use ctru::services::gfx::{Flush, Screen, Swap};
/// Ferris image taken from <https://rustacean.net> 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();
}
}

4
ctru-rs/examples/graphics-bitmap.rs

@ -1,5 +1,5 @@
use ctru::prelude::*; use ctru::prelude::*;
use ctru::services::gfx::Screen; use ctru::services::gfx::{Flush, Screen, Swap};
/// Ferris image taken from <https://rustacean.net> and scaled down to 320x240px. /// Ferris image taken from <https://rustacean.net> and scaled down to 320x240px.
/// To regenerate the data, you will need to install `imagemagick` and run this /// To regenerate the data, you will need to install `imagemagick` and run this
@ -48,7 +48,7 @@ fn main() {
} }
// Flush and swap framebuffers // Flush and swap framebuffers
bottom_screen.flush_buffer(); bottom_screen.flush_buffers();
bottom_screen.swap_buffers(); bottom_screen.swap_buffers();
//Wait for VBlank //Wait for VBlank

142
ctru-rs/src/services/gfx.rs

@ -9,11 +9,12 @@ use crate::services::gspgpu::{self, FramebufferFormat};
use crate::services::ServiceReference; use crate::services::ServiceReference;
mod private { mod private {
use super::{BottomScreen, TopScreen, TopScreenLeft, TopScreenRight}; use super::{BottomScreen, TopScreen, TopScreen3D, TopScreenLeft, TopScreenRight};
pub trait Sealed {} pub trait Sealed {}
impl Sealed for TopScreen {} impl Sealed for TopScreen {}
impl Sealed for TopScreen3D<'_> {}
impl Sealed for TopScreenLeft {} impl Sealed for TopScreenLeft {}
impl Sealed for TopScreenRight {} impl Sealed for TopScreenRight {}
impl Sealed for BottomScreen {} impl Sealed for BottomScreen {}
@ -49,38 +50,21 @@ pub trait Screen: private::Sealed {
/// Sets whether to use double buffering. Enabled by default. /// Sets whether to use double buffering. Enabled by default.
/// ///
/// Note that even when double buffering is disabled, one should still use the `swap_buffers` /// [`Swap::swap_buffers`] must be called after this function for the configuration
/// method on each frame to keep the gsp configuration up to date /// change to take effect.
fn set_double_buffering(&mut self, enabled: bool) { fn set_double_buffering(&mut self, enabled: bool) {
unsafe { ctru_sys::gfxSetDoubleBuffering(self.as_raw(), enabled) } unsafe { ctru_sys::gfxSetDoubleBuffering(self.as_raw(), enabled) }
} }
/// Flushes the video buffer for this screen. /// Gets the framebuffer format.
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
fn framebuffer_format(&self) -> FramebufferFormat { fn framebuffer_format(&self) -> FramebufferFormat {
unsafe { ctru_sys::gfxGetScreenFormat(self.as_raw()) }.into() 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) { fn set_framebuffer_format(&mut self, fmt: FramebufferFormat) {
unsafe { ctru_sys::gfxSetScreenFormat(self.as_raw(), fmt.into()) } 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 /// 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. /// converted into this, 3D mode will be enabled until this struct is dropped.
pub struct TopScreen3D<'top_screen> { pub struct TopScreen3D<'screen> {
screen: &'top_screen RefCell<TopScreen>, screen: &'screen RefCell<TopScreen>,
}
/// 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<S: Screen> 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] #[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 /// The bottom screen. Mutable access to this struct is required to write to the
/// bottom screen's frame buffer. /// bottom screen's frame buffer.
#[derive(Debug)]
#[non_exhaustive]
pub struct BottomScreen; pub struct BottomScreen;
/// Representation of a framebuffer for one [`Side`] of the top screen, or the /// Representation of a framebuffer for one [`Side`] of the top screen, or the
@ -192,22 +257,20 @@ impl Gfx {
impl TopScreen3D<'_> { impl TopScreen3D<'_> {
/// Immutably borrow the two sides of the screen as `(left, right)`. /// Immutably borrow the two sides of the screen as `(left, right)`.
pub fn split(&self) -> (Ref<dyn Screen>, Ref<dyn Screen>) { pub fn split(&self) -> (Ref<TopScreenLeft>, Ref<TopScreenRight>) {
Ref::map_split(self.screen.borrow(), |screen| { Ref::map_split(self.screen.borrow(), |screen| (&screen.left, &screen.right))
(&screen.left as _, &screen.right as _)
})
} }
/// Mutably borrow the two sides of the screen as `(left, right)`. /// Mutably borrow the two sides of the screen as `(left, right)`.
pub fn split_mut(&self) -> (RefMut<dyn Screen>, RefMut<dyn Screen>) { pub fn split_mut(&self) -> (RefMut<TopScreenLeft>, RefMut<TopScreenRight>) {
RefMut::map_split(self.screen.borrow_mut(), |screen| { 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<TopScreen>> for TopScreen3D<'top_screen> { impl<'screen> From<&'screen RefCell<TopScreen>> for TopScreen3D<'screen> {
fn from(top_screen: &'top_screen RefCell<TopScreen>) -> Self { fn from(top_screen: &'screen RefCell<TopScreen>) -> Self {
unsafe { unsafe {
ctru_sys::gfxSet3D(true); ctru_sys::gfxSet3D(true);
} }
@ -233,6 +296,9 @@ impl TopScreen {
} }
/// Enable or disable wide mode on the top screen. /// 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) { pub fn set_wide_mode(&mut self, enable: bool) {
unsafe { unsafe {
ctru_sys::gfxSetWide(enable); 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 { impl Screen for TopScreen {
fn as_raw(&self) -> ctru_sys::gfxScreen_t { fn as_raw(&self) -> ctru_sys::gfxScreen_t {
self.left.as_raw() self.left.as_raw()

Loading…
Cancel
Save