diff --git a/ctru-rs/examples/assets/ferris.png b/ctru-rs/examples/assets/ferris.png new file mode 100644 index 00000000..73301d5a Binary files /dev/null and b/ctru-rs/examples/assets/ferris.png differ diff --git a/ctru-rs/examples/assets/ferris.rgb b/ctru-rs/examples/assets/ferris.rgb new file mode 100644 index 00000000..aaa20875 Binary files /dev/null and b/ctru-rs/examples/assets/ferris.rgb differ diff --git a/ctru-rs/examples/graphics-bitmap.rs b/ctru-rs/examples/graphics-bitmap.rs new file mode 100644 index 00000000..fc65c9d2 --- /dev/null +++ b/ctru-rs/examples/graphics-bitmap.rs @@ -0,0 +1,59 @@ +use ctru::console::Console; +use ctru::gfx::Screen as _; +use ctru::services::hid::KeyPad; +use ctru::services::{Apt, Hid}; +use ctru::Gfx; + +/// 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::init(); + let gfx = Gfx::default(); + 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_mut()); + + println!("\x1b[21;16HPress Start to exit."); + + 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); + + // We assume the image is the correct size already, so we drop width + height. + let frame_buffer = bottom_screen.get_raw_framebuffer(); + + // Copy the image into the frame buffer + unsafe { + frame_buffer.ptr.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; + } + + // 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 10926401..c7615498 100644 --- a/ctru-rs/src/gfx.rs +++ b/ctru-rs/src/gfx.rs @@ -2,6 +2,7 @@ use std::cell::RefCell; use std::default::Default; +use std::marker::PhantomData; use std::ops::Drop; use crate::services::gspgpu::{self, FramebufferFormat}; @@ -28,29 +29,29 @@ pub trait Screen { fn set_framebuffer_format(&mut self, fmt: FramebufferFormat) { unsafe { ctru_sys::gfxSetScreenFormat(self.as_raw(), fmt.into()) } } - - /// Returns a tuple containing a pointer to the specifified framebuffer (as determined by the - /// calling screen and `Side`), the width of the framebuffer in pixels, and the height of - /// the framebuffer in pixels - /// - /// Note that the pointer returned by this function can change after each call to this function - /// if double buffering is enabled - fn get_raw_framebuffer(&self, side: Side) -> (*mut u8, u16, u16) { - let mut width: u16 = 0; - let mut height: u16 = 0; - unsafe { - let buf: *mut u8 = - ctru_sys::gfxGetFramebuffer(self.as_raw(), side.into(), &mut width, &mut height); - (buf, width, height) - } - } } #[non_exhaustive] pub struct TopScreen; + #[non_exhaustive] pub struct BottomScreen; +/// Representation of a framebuffer for one [`Side`] of the top screen, or the +/// entire bottom screen. The inner pointer is only valid for one frame if double +/// buffering is enabled. Data written to `ptr` will be rendered to the screen. +#[derive(Debug)] +pub struct RawFrameBuffer<'screen> { + /// Pointer to graphics data to be rendered. + pub ptr: *mut u8, + /// The width of the framebuffer in pixels. + pub width: u16, + /// The height of the framebuffer in pixels. + pub height: u16, + /// Keep a mutable reference to the Screen for which this framebuffer is tied. + screen: PhantomData<&'screen mut dyn Screen>, +} + #[derive(Copy, Clone, Debug)] /// Side of top screen framebuffer /// @@ -138,6 +139,40 @@ impl TopScreen { 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 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) + } +} + +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 TopScreen {