From 09361e50da2e42732a7963f4a6a786c7c4dae0ad Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Sat, 23 Dec 2023 22:24:04 -0500 Subject: [PATCH] Try using double buffering to reduce flickering (actually increased :/) Required refactoring Console to take screen type as a generic. Not all Screen types support swapping or flushing. --- ctru-rs/examples/ir-user.rs | 75 ++++++++++++++++++++++--------------- ctru-rs/src/console.rs | 15 +++++--- 2 files changed, 55 insertions(+), 35 deletions(-) diff --git a/ctru-rs/examples/ir-user.rs b/ctru-rs/examples/ir-user.rs index ee1d426..acd4b1d 100644 --- a/ctru-rs/examples/ir-user.rs +++ b/ctru-rs/examples/ir-user.rs @@ -1,10 +1,10 @@ //! A demo of using the ir:USER service to connect to the Circle Pad Pro. use ctru::prelude::*; +use ctru::services::gfx::{BottomScreen, Flush, Swap, TopScreen}; use ctru::services::ir_user::{CirclePadProInputResponse, ConnectionStatus, IrDeviceId, IrUser}; use ctru::services::srv::HandleExt; use ctru_sys::Handle; -use std::io::Write; use std::time::Duration; const PACKET_INFO_SIZE: usize = 8; @@ -19,7 +19,7 @@ fn main() { let gfx = Gfx::new().unwrap(); let top_console = Console::new(gfx.top_screen.borrow_mut()); let bottom_console = Console::new(gfx.bottom_screen.borrow_mut()); - let demo = CirclePadProDemo::new(top_console, bottom_console); + let mut demo = CirclePadProDemo::new(top_console, bottom_console); demo.print_status_info(); // Initialize HID after ir:USER because libctru also initializes ir:rst, @@ -62,8 +62,8 @@ fn main() { } struct CirclePadProDemo<'screen> { - top_console: Console<'screen>, - bottom_console: Console<'screen>, + top_console: Console<'screen, TopScreen>, + bottom_console: Console<'screen, BottomScreen>, ir_user: IrUser, connection_status_event: Handle, receive_packet_event: Handle, @@ -75,7 +75,17 @@ enum ConnectionResult { } impl<'screen> CirclePadProDemo<'screen> { - fn new(top_console: Console<'screen>, bottom_console: Console<'screen>) -> Self { + fn new( + mut top_console: Console<'screen, TopScreen>, + bottom_console: Console<'screen, BottomScreen>, + ) -> Self { + // Set up double buffering on top screen + top_console.with_screen(|screen| { + screen.set_double_buffering(true); + screen.swap_buffers(); + }); + + // Write messages to bottom screen (not double buffered) bottom_console.select(); println!("Welcome to the ir:USER / Circle Pad Pro Demo"); @@ -106,14 +116,18 @@ impl<'screen> CirclePadProDemo<'screen> { } } - fn print_status_info(&self) { + fn print_status_info(&mut self) { self.top_console.select(); self.top_console.clear(); println!("{:#x?}", self.ir_user.get_status_info()); + self.top_console.with_screen(|screen| { + screen.flush_buffers(); + screen.swap_buffers(); + }); self.bottom_console.select(); } - fn connect_to_cpp(&self, hid: &mut Hid) -> ConnectionResult { + fn connect_to_cpp(&mut self, hid: &mut Hid) -> ConnectionResult { // Connection loop loop { hid.scan_input(); @@ -192,7 +206,7 @@ impl<'screen> CirclePadProDemo<'screen> { ConnectionResult::Connected } - fn handle_packets(&self) { + fn handle_packets(&mut self) { let packets = self .ir_user .get_packets() @@ -201,32 +215,33 @@ impl<'screen> CirclePadProDemo<'screen> { let Some(last_packet) = packets.last() else { return; }; + let status_info = self.ir_user.get_status_info(); + let cpp_response = CirclePadProInputResponse::try_from(last_packet) + .expect("Failed to parse CPP response from IR packet"); - // Use a buffer to avoid flickering the screen (write all output at once) - let mut output_buffer = Vec::with_capacity(0x1000); - - writeln!(&mut output_buffer, "{:x?}", self.ir_user.get_status_info()).unwrap(); + // Write data to top screen + self.top_console.select(); + self.top_console.clear(); + println!("{:x?}", status_info); self.ir_user.process_shared_memory(|ir_mem| { - writeln!(&mut output_buffer, "\nReceiveBufferInfo:").unwrap(); - write_buffer_as_hex(&ir_mem[0x10..0x20], &mut output_buffer); + println!("\nReceiveBufferInfo:"); + print_buffer_as_hex(&ir_mem[0x10..0x20]); - writeln!(&mut output_buffer, "\nReceiveBuffer:").unwrap(); - write_buffer_as_hex(&ir_mem[0x20..0x20 + PACKET_BUFFER_SIZE], &mut output_buffer); - writeln!(&mut output_buffer).unwrap(); + println!("\nReceiveBuffer:"); + print_buffer_as_hex(&ir_mem[0x20..0x20 + PACKET_BUFFER_SIZE]); + println!(); }); - writeln!(&mut output_buffer, "\nPacket count: {packet_count}").unwrap(); - writeln!(&mut output_buffer, "{last_packet:02x?}").unwrap(); - - let cpp_response = CirclePadProInputResponse::try_from(last_packet) - .expect("Failed to parse CPP response from IR packet"); - writeln!(&mut output_buffer, "\n{cpp_response:#02x?}").unwrap(); + println!("\nPacket count: {packet_count}"); + println!("{last_packet:02x?}"); + println!("\n{cpp_response:#02x?}"); - // Write output to top screen - self.top_console.select(); - self.top_console.clear(); - std::io::stdout().write_all(&output_buffer).unwrap(); + // Flush output and switch back to bottom screen + self.top_console.with_screen(|screen| { + screen.flush_buffers(); + screen.swap_buffers(); + }); self.bottom_console.select(); // Done handling the packets, release them @@ -241,13 +256,13 @@ impl<'screen> CirclePadProDemo<'screen> { } } -fn write_buffer_as_hex(buffer: &[u8], output: &mut Vec) { +fn print_buffer_as_hex(buffer: &[u8]) { let mut counter = 0; for byte in buffer { - write!(output, "{byte:02x} ").unwrap(); + print!("{byte:02x} "); counter += 1; if counter % 16 == 0 { - writeln!(output).unwrap(); + println!(); } } } diff --git a/ctru-rs/src/console.rs b/ctru-rs/src/console.rs index 5a8c772..452c785 100644 --- a/ctru-rs/src/console.rs +++ b/ctru-rs/src/console.rs @@ -58,12 +58,12 @@ pub enum Dimension { /// you can try using [`Soc::redirect_to_3dslink`](crate::services::soc::Soc::redirect_to_3dslink) while activating the `--server` flag for `3dslink` (also supported by `cargo-3ds`). /// More info in the [`cargo-3ds` docs](https://github.com/rust3ds/cargo-3ds#running-executables). #[doc(alias = "PrintConsole")] -pub struct Console<'screen> { +pub struct Console<'screen, S: Screen> { context: Box, - screen: RefMut<'screen, dyn Screen>, + screen: RefMut<'screen, S>, } -impl<'screen> Console<'screen> { +impl<'screen, S: Screen> Console<'screen, S> { /// Initialize a console on the chosen screen. /// /// # Notes @@ -102,7 +102,7 @@ impl<'screen> Console<'screen> { /// # } /// ``` #[doc(alias = "consoleInit")] - pub fn new(screen: RefMut<'screen, dyn Screen>) -> Self { + pub fn new(screen: RefMut<'screen, S>) -> Self { let mut context = Box::::default(); unsafe { consoleInit(screen.as_raw(), context.as_mut()) }; @@ -322,9 +322,14 @@ impl<'screen> Console<'screen> { _ => unreachable!(), } } + + /// Run a function with access to the underlying screen. + pub fn with_screen(&mut self, action: impl FnOnce(&mut RefMut<'_, S>)) { + action(&mut self.screen); + } } -impl Drop for Console<'_> { +impl Drop for Console<'_, S> { fn drop(&mut self) { unsafe { // Safety: We are about to deallocate the PrintConsole data pointed