From 793a95073b8c47cb259df91b14a84f5bc51bf52d Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Thu, 27 Jan 2022 22:22:43 -0500 Subject: [PATCH 1/5] First pass of graphics bitmap example --- ctru-rs/examples/assets/ferris.png | Bin 0 -> 29169 bytes ctru-rs/examples/assets/ferris.rgb | Bin 0 -> 230400 bytes ctru-rs/examples/graphics-bitmap.rs | 61 ++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 ctru-rs/examples/assets/ferris.png create mode 100644 ctru-rs/examples/assets/ferris.rgb create mode 100644 ctru-rs/examples/graphics-bitmap.rs diff --git a/ctru-rs/examples/assets/ferris.png b/ctru-rs/examples/assets/ferris.png new file mode 100644 index 0000000000000000000000000000000000000000..73301d5ab9faa91ca462a0c9b281f7cf8b23c4a4 GIT binary patch literal 29169 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 and scaled down to 320x240px. +/// To regenerate the data, you will need to install `imagemagick` and run this +/// command from the `examples` directory: +/// +/// ```sh +/// convert assets/ferris.png -channel B -separate \ +/// assets/ferris.png -channel G -separate \ +/// assets/ferris.png -channel R -separate \ +/// -channel RGB -combine -rotate 90 \ +/// assets/ferris.rgb +/// ``` +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); + + // The "Left" side framebuffer is the only valid one for bottom screen + // TODO: make `get_raw_framebuffer` only accept a side for top screen + // Also, we assume the image is the correct size already... + let (frame_buffer, _width, _height) = bottom_screen.get_raw_framebuffer(Side::Left); + + // Copy the image into the frame buffer + unsafe { + frame_buffer.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(); + } +} From be4784e3bc810d4cbfcc66664887fa7bbdf2682f Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Thu, 27 Jan 2022 22:30:32 -0500 Subject: [PATCH 2/5] Use bottom-only framebuffer API for "left" --- ctru-rs/examples/graphics-bitmap.rs | 10 +++--- ctru-rs/src/gfx.rs | 55 ++++++++++++++++++++--------- 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/ctru-rs/examples/graphics-bitmap.rs b/ctru-rs/examples/graphics-bitmap.rs index 8afc3ac3..6ec7b97a 100644 --- a/ctru-rs/examples/graphics-bitmap.rs +++ b/ctru-rs/examples/graphics-bitmap.rs @@ -15,6 +15,10 @@ use ctru::Gfx; /// -channel RGB -combine -rotate 90 \ /// assets/ferris.rgb /// ``` +/// +/// This creates an image appropriate for the default frame buffer format of +/// BGR888 and rotates the image 90° to account for the screen actually being +/// in portrait mode. static IMAGE: &[u8] = include_bytes!("assets/ferris.rgb"); fn main() { @@ -32,10 +36,8 @@ fn main() { // In this way we can draw our image only once on screen. bottom_screen.set_double_buffering(false); - // The "Left" side framebuffer is the only valid one for bottom screen - // TODO: make `get_raw_framebuffer` only accept a side for top screen - // Also, we assume the image is the correct size already... - let (frame_buffer, _width, _height) = bottom_screen.get_raw_framebuffer(Side::Left); + // We assume the image is the correct size already, so we drop width + height. + let (frame_buffer, _width, _height) = bottom_screen.get_raw_framebuffer(); // Copy the image into the frame buffer unsafe { diff --git a/ctru-rs/src/gfx.rs b/ctru-rs/src/gfx.rs index 10926401..9d392847 100644 --- a/ctru-rs/src/gfx.rs +++ b/ctru-rs/src/gfx.rs @@ -28,26 +28,11 @@ 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; @@ -138,6 +123,44 @@ impl TopScreen { pub fn get_wide_mode(&self) -> bool { unsafe { ctru_sys::gfxIsWide() } } + + /// Returns a tuple containing a pointer to the top screen's framebuffer + /// (as determined by the `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 + pub fn get_raw_framebuffer(&mut 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) + } + } +} + +impl BottomScreen { + /// Returns a tuple containing a pointer to the bottom screen's framebuffer, + /// 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 + pub fn get_raw_framebuffer(&mut self) -> (*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::Left.into(), + &mut width, + &mut height, + ); + // TODO: does it make sense to use a struct for this instead of a tuple? + (buf, width, height) + } + } } impl Screen for TopScreen { From be94b04d1ddbd5000138e15bdc6503ef83d2797d Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Thu, 27 Jan 2022 23:52:45 -0500 Subject: [PATCH 3/5] Use better image conversion command This looks more correct on the device to my eye and is a bit simpler. --- ctru-rs/examples/assets/ferris.rgb | Bin 230400 -> 230400 bytes ctru-rs/examples/graphics-bitmap.rs | 12 ++++-------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/ctru-rs/examples/assets/ferris.rgb b/ctru-rs/examples/assets/ferris.rgb index 0735606b8b82264f85f93d1f3ea08755bca0cf9d..aaa2087579dc72171aea4bc1827d0140b8621e88 100644 GIT binary patch delta 2043 zcma)73rv$&6u#%SFlbi`r9c(>glVIjvxcY(h%;(fHlfABjJl{qmu)VYbh^bXF+voC z%mLEPKTM6oq|q!ci$=L*FCmzbEUsV(WCi_U<{YHL%<=D=o6y#+>ZtQE-C<2tO23lEq!N zLAoT%>Qh>LWTy}8=SO~LXFf&HN}cYND2(V0CY!Ciq5jOZYZuSk9mh`f828K&rH^c$ z)mx*idq0C7SP#JS>U4UX3kwU|+S;b3rsn=L{L|i0$NkXm<9=fBX^Od_v$Tt?JpuP& zH~AV+d?^5oAGj?${GaY7Y zYT-xNjIWiJmWE^l0|Q7F3%uL)XE-vox z???3WhRfyJw{I^ZHSA^s>L*W7pWuyV6q3{F92*-$8Di)_Ab{)c?(Vv}I^4yf)xC~0 zY`88VHqtoQ<4r&{z= ztJQkFUMv>JP$H2?r_-SXL`>BLKT%m+TpY?%sZ=Tyig-475v(OL*w{*T43okjDT)Bw zG7TLx6?LpgCTXvpN!{j@oBd*QguFBR%>($Gs~87y%M+|O!}1mStn76}Y>0&;@KO5q zuB>;%%gh19^SxlmZaFSyJ+sgOs}d3tF~1nvY_=Ra{Ncfa@8{>KqsC)o$CS1t$TbAP z&8`IC9;}he<&;v~wb^W)ot@+3<9MH_m8Vai&dkiZT<#xkTwa$~Nmfp4SzQpWQj<(3 z6C$*?w?FUk(C+n&)->EFtNm(L7UAn3!{DW(Ygbnn?hA_H6|`ues01bA{<%v&Vw}}P zYwa=1p!62xYPDJ zV77yUgGhykd?sPcC?tH$x;3G;9Lz&=q{Qj+MO zumMW}?JX@W0$eEmMK3A*9PnpGJsml66v4|kIil^r{+&FLxfHY;OfgL~H#cM3SO~rJ z|D#8busV@K&4!fpD&2r(CmbX$&7=F%C%b(r(TS{tP#g(klW=J)(wOHZUrdM}$qc&=>#{OV+H#cGsQ&KB7 zRJvOquEZJ0Vg$n|kL)kSK-iobQVg=Z-pfi7OC|45*L&~Y_kP!_wTfO2hx48DzR!8z zbDrmX)rX&~rgl~gev*Ps7x3CRDtve|0J8^WPLx_LHwRS5USSup&@B$oS}Yf(SHg-r3s{7E zgVipm{4d1`xe+T@a@>(4p`f`rP|!Vl2N=LNkMaE_*&$PMhd3Y+KaRqRB&~+W2It=+F377Q(kFloY%g6kBg?~Ui}U7 z#wjn0NjlV;BjRUK#J9D4IfIRiFqqfN{lgmF1}%-**}wRzb+_BD++T}x;`tCpWhG{2 z*oBUqFWSFwXf)O*X?1oMJNGcgO9NU{p6ofgJHwiAD^3kG&{5?hBZJq<%W>t(Lo*>1 zn+P&Bqc$O9v9{c12%MFn*$|Yl=`7IPl;Yhz{FtSnOZs|nB0^jUUhzV zQT(e_Pl(9QJ0!JCtjI?0bBN4Y*iL+it%G>O2b0$56%K1j)lxO=6un|KqGLF>u$juR z=L5X+9*Q!9bDv?`ZB%%*CaF}Meg3ktP?gnPPd?_ z2CJr^8oc79%!k1c+MuWBQFk{zj;4M*?S|Uz56Yglp#vuy9OO8iEjoX~JtH%M6Ey2YLyI4zC49Ge=LsjX* z58opVXhsJoK7F*8oj0}zp91R-@iEGlB!@_cVNSHQ@sklQ;=aMPYphH%HI^bz%Jm@0 z>4g*)qP-oLF5$)vOiyES5_UWDxW1m<$+FgRzqU2>0{m)yu{l!hPm^OJ?;fp?Q$fK! zGpVUarDv1GU$a8C%bd`oLG-8lAR@-j|CZ@-r5Vacy__k3m$95(Tpa(R4#gX#iZ@0~ GzxyAtr#cY; diff --git a/ctru-rs/examples/graphics-bitmap.rs b/ctru-rs/examples/graphics-bitmap.rs index 6ec7b97a..eb536944 100644 --- a/ctru-rs/examples/graphics-bitmap.rs +++ b/ctru-rs/examples/graphics-bitmap.rs @@ -1,5 +1,5 @@ use ctru::console::Console; -use ctru::gfx::{Screen as _, Side}; +use ctru::gfx::Screen as _; use ctru::services::hid::KeyPad; use ctru::services::{Apt, Hid}; use ctru::Gfx; @@ -9,16 +9,12 @@ use ctru::Gfx; /// command from the `examples` directory: /// /// ```sh -/// convert assets/ferris.png -channel B -separate \ -/// assets/ferris.png -channel G -separate \ -/// assets/ferris.png -channel R -separate \ -/// -channel RGB -combine -rotate 90 \ -/// assets/ferris.rgb +/// 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 -/// BGR888 and rotates the image 90° to account for the screen actually being -/// in portrait mode. +/// [`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() { From 98e026373eefa1d2fc95d26e015c511884636e3e Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sat, 29 Jan 2022 00:45:49 -0500 Subject: [PATCH 4/5] Use a struct to represent the raw frame buffer I chose the name RawFrameBuffer in case we'd like to provide a "safer" alternative API at some point. --- ctru-rs/examples/graphics-bitmap.rs | 4 +- ctru-rs/src/gfx.rs | 69 +++++++++++++++++------------ 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/ctru-rs/examples/graphics-bitmap.rs b/ctru-rs/examples/graphics-bitmap.rs index eb536944..fc65c9d2 100644 --- a/ctru-rs/examples/graphics-bitmap.rs +++ b/ctru-rs/examples/graphics-bitmap.rs @@ -33,11 +33,11 @@ fn main() { bottom_screen.set_double_buffering(false); // We assume the image is the correct size already, so we drop width + height. - let (frame_buffer, _width, _height) = bottom_screen.get_raw_framebuffer(); + let frame_buffer = bottom_screen.get_raw_framebuffer(); // Copy the image into the frame buffer unsafe { - frame_buffer.copy_from(IMAGE.as_ptr(), IMAGE.len()); + frame_buffer.ptr.copy_from(IMAGE.as_ptr(), IMAGE.len()); } // Main loop diff --git a/ctru-rs/src/gfx.rs b/ctru-rs/src/gfx.rs index 9d392847..9a701994 100644 --- a/ctru-rs/src/gfx.rs +++ b/ctru-rs/src/gfx.rs @@ -36,6 +36,19 @@ pub struct TopScreen; #[non_exhaustive] pub struct BottomScreen; +#[derive(Debug)] +/// 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. +pub struct RawFrameBuffer { + /// 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, +} + #[derive(Copy, Clone, Debug)] /// Side of top screen framebuffer /// @@ -124,42 +137,32 @@ impl TopScreen { unsafe { ctru_sys::gfxIsWide() } } - /// Returns a tuple containing a pointer to the top screen's framebuffer - /// (as determined by the `side`), the width of the framebuffer in pixels, - /// and the height of the framebuffer in pixels. + /// Returns a [`RawFrameBuffer`] for the given [`Side`] of the top screen. /// - /// Note that the pointer 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) -> (*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) - } + /// 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.as_raw(), side.into()) } } impl BottomScreen { - /// Returns a tuple containing a pointer to the bottom screen's framebuffer, - /// the width of the framebuffer in pixels, and the height of the framebuffer in pixels. + /// Returns a [`RawFrameBuffer`] for the bottom screen. /// - /// Note that the pointer returned by this function can change after each call to this function - /// if double buffering is enabled - pub fn get_raw_framebuffer(&mut self) -> (*mut u8, u16, u16) { - let mut width: u16 = 0; - let mut height: u16 = 0; + /// 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.as_raw(), Side::Left.into()) + } +} + +impl RawFrameBuffer { + fn for_screen_side(screen: ctru_sys::gfxScreen_t, side: ctru_sys::gfx3dSide_t) -> Self { + let mut buf = RawFrameBuffer::default(); unsafe { - let buf: *mut u8 = ctru_sys::gfxGetFramebuffer( - self.as_raw(), - Side::Left.into(), - &mut width, - &mut height, - ); - // TODO: does it make sense to use a struct for this instead of a tuple? - (buf, width, height) + buf.ptr = ctru_sys::gfxGetFramebuffer(screen, side, &mut buf.width, &mut buf.height); } + buf } } @@ -175,6 +178,16 @@ impl Screen for BottomScreen { } } +impl Default for RawFrameBuffer { + fn default() -> Self { + Self { + ptr: std::ptr::null_mut(), + width: 0, + height: 0, + } + } +} + impl From for ctru_sys::gfx3dSide_t { fn from(s: Side) -> ctru_sys::gfx3dSide_t { use self::Side::*; From 9aac807407b103a2a3530ce4fe93f6940920359b Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sat, 29 Jan 2022 09:51:46 -0500 Subject: [PATCH 5/5] Address review comments Keep RawFrameBuffer alive as long as the Screen is, to ensure the Gfx is not dropped while the FrameBuffer is alive. --- ctru-rs/src/gfx.rs | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/ctru-rs/src/gfx.rs b/ctru-rs/src/gfx.rs index 9a701994..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}; @@ -36,17 +37,19 @@ pub struct TopScreen; #[non_exhaustive] pub struct BottomScreen; -#[derive(Debug)] /// 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. -pub struct RawFrameBuffer { +#[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)] @@ -142,7 +145,7 @@ impl TopScreen { /// 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.as_raw(), side.into()) + RawFrameBuffer::for_screen_side(self, side) } } @@ -152,17 +155,23 @@ impl BottomScreen { /// 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.as_raw(), Side::Left.into()) + RawFrameBuffer::for_screen_side(self, Side::Left) } } -impl RawFrameBuffer { - fn for_screen_side(screen: ctru_sys::gfxScreen_t, side: ctru_sys::gfx3dSide_t) -> Self { - let mut buf = RawFrameBuffer::default(); - unsafe { - buf.ptr = ctru_sys::gfxGetFramebuffer(screen, side, &mut buf.width, &mut buf.height); +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, } - buf } } @@ -178,16 +187,6 @@ impl Screen for BottomScreen { } } -impl Default for RawFrameBuffer { - fn default() -> Self { - Self { - ptr: std::ptr::null_mut(), - width: 0, - height: 0, - } - } -} - impl From for ctru_sys::gfx3dSide_t { fn from(s: Side) -> ctru_sys::gfx3dSide_t { use self::Side::*;