diff --git a/firmware/Cargo.lock b/firmware/Cargo.lock index 74faf0e..a75b39d 100644 --- a/firmware/Cargo.lock +++ b/firmware/Cargo.lock @@ -30,7 +30,7 @@ dependencies = [ "esp-backtrace", "esp-bootloader-esp-idf", "esp-hal", - "esp-metadata-generated", + "esp-metadata-generated 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "esp-println", "esp-radio", "esp-rtos", @@ -47,6 +47,7 @@ dependencies = [ "lazy_static", "log", "mutually_exclusive_features", + "ouroboros", "panic-rtt-target", "password-hash 0.1.0", "pastey 0.2.1", @@ -105,6 +106,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + [[package]] name = "aligned" version = "0.4.3" @@ -2029,8 +2036,6 @@ checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" [[package]] name = "esp-alloc" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "641e43d6a60244429117ef2fa7a47182120c7561336ea01f6fb08d634f46bae1" dependencies = [ "allocator-api2 0.3.1", "cfg-if", @@ -2045,13 +2050,11 @@ dependencies = [ [[package]] name = "esp-backtrace" version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3318413fb566c7227387f67736cf70cd74d80a11f2bb31c7b95a9eb48d079669" dependencies = [ "cfg-if", "document-features", "esp-config", - "esp-metadata-generated", + "esp-metadata-generated 0.3.0", "esp-println", "heapless 0.9.2", "riscv", @@ -2062,15 +2065,13 @@ dependencies = [ [[package]] name = "esp-bootloader-esp-idf" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02a56964ab5479ac20c9cf76fa3b0d3f2233b20b5d8554e81ef5d65f63c20567" dependencies = [ "cfg-if", "document-features", "embedded-storage", "esp-config", "esp-hal-procmacros", - "esp-metadata-generated", + "esp-metadata-generated 0.3.0", "esp-rom-sys", "jiff", "log", @@ -2080,11 +2081,9 @@ dependencies = [ [[package]] name = "esp-config" version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "102871054f8dd98202177b9890cb4b71d0c6fe1f1413b7a379a8e0841fc2473c" dependencies = [ "document-features", - "esp-metadata-generated", + "esp-metadata-generated 0.3.0", "serde", "serde_yaml", "somni-expr", @@ -2093,8 +2092,6 @@ dependencies = [ [[package]] name = "esp-hal" version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54786287c0a61ca0f78cb0c338a39427551d1be229103b4444591796c579e093" dependencies = [ "bitfield 0.19.4", "bitflags 2.10.0", @@ -2119,7 +2116,7 @@ dependencies = [ "enumset", "esp-config", "esp-hal-procmacros", - "esp-metadata-generated", + "esp-metadata-generated 0.3.0", "esp-riscv-rt", "esp-rom-sys", "esp-sync", @@ -2149,8 +2146,6 @@ dependencies = [ [[package]] name = "esp-hal-procmacros" version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e025a7a7a0affdb4ff913b5c4494aef96ee03d085bf83c27453ae3a71d50da6" dependencies = [ "document-features", "object", @@ -2161,6 +2156,10 @@ dependencies = [ "termcolor", ] +[[package]] +name = "esp-metadata-generated" +version = "0.3.0" + [[package]] name = "esp-metadata-generated" version = "0.3.0" @@ -2170,26 +2169,22 @@ checksum = "9a93e39c8ad8d390d248dc7b9f4b59a873f313bf535218b8e2351356972399e3" [[package]] name = "esp-phy" version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1facf348e1e251517278fc0f5dc134e95e518251f5796cfbb532ca226a29bf" dependencies = [ "cfg-if", "document-features", "esp-config", "esp-hal", - "esp-metadata-generated", + "esp-metadata-generated 0.3.0", "esp-sync", - "esp-wifi-sys", + "esp-wifi-sys-esp32s3", ] [[package]] name = "esp-println" version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a30e6c9fbcc01c348d46706fef8131c7775ab84c254a3cd65d0cd3f6414d592" dependencies = [ "document-features", - "esp-metadata-generated", + "esp-metadata-generated 0.3.0", "esp-sync", "log", "portable-atomic", @@ -2198,8 +2193,6 @@ dependencies = [ [[package]] name = "esp-radio" version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684c4de2f8907b73c9b891fbda65286a86d34fced4b856f36a7896c211f2f265" dependencies = [ "allocator-api2 0.3.1", "bt-hci", @@ -2213,11 +2206,11 @@ dependencies = [ "esp-config", "esp-hal", "esp-hal-procmacros", - "esp-metadata-generated", + "esp-metadata-generated 0.3.0", "esp-phy", "esp-radio-rtos-driver", "esp-sync", - "esp-wifi-sys", + "esp-wifi-sys-esp32s3", "heapless 0.9.2", "instability", "num-derive", @@ -2230,14 +2223,10 @@ dependencies = [ [[package]] name = "esp-radio-rtos-driver" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "543bc31d1851afd062357e7810c1a9633f282fd3993583499a841ab497cbca6c" [[package]] name = "esp-riscv-rt" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502744a5b1e7268d27fd2a4e56ad45efe42ead517d6c517a6961540de949b0ee" dependencies = [ "document-features", "riscv", @@ -2247,19 +2236,15 @@ dependencies = [ [[package]] name = "esp-rom-sys" version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd66cccc6dd2d13e9f33668a57717ab14a6d217180ec112e6be533de93e7ecbf" dependencies = [ "cfg-if", "document-features", - "esp-metadata-generated", + "esp-metadata-generated 0.3.0", ] [[package]] name = "esp-rtos" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ec711c8d06e79c67b75d01595539e86b0aac209643af98ca87a12250428b3" dependencies = [ "allocator-api2 0.3.1", "cfg-if", @@ -2271,7 +2256,7 @@ dependencies = [ "esp-config", "esp-hal", "esp-hal-procmacros", - "esp-metadata-generated", + "esp-metadata-generated 0.3.0", "esp-radio-rtos-driver", "esp-sync", "portable-atomic", @@ -2280,14 +2265,12 @@ dependencies = [ [[package]] name = "esp-storage" version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1495fc1f5549bdd840b52d9ceb201746200e1620d2636f46958c11e765623b80" dependencies = [ "document-features", "embedded-storage", "esp-hal", "esp-hal-procmacros", - "esp-metadata-generated", + "esp-metadata-generated 0.3.0", "esp-rom-sys", "esp-sync", ] @@ -2295,14 +2278,12 @@ dependencies = [ [[package]] name = "esp-sync" version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d44974639b4e88914f83fe60d2832c00276657d7d857628fdfc966cc7302e8a8" dependencies = [ "cfg-if", "document-features", "embassy-sync 0.6.2", "embassy-sync 0.7.2", - "esp-metadata-generated", + "esp-metadata-generated 0.3.0", "log", "riscv", "xtensa-lx", @@ -2322,13 +2303,9 @@ dependencies = [ ] [[package]] -name = "esp-wifi-sys" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b6544f6f0cb86169d1f93ba2101a8d50358a040c5043676ed86b793e09b12c" -dependencies = [ - "anyhow", -] +name = "esp-wifi-sys-esp32s3" +version = "0.1.0" +source = "git+https://github.com/esp-rs/esp-wifi-sys?rev=7623c8d#7623c8d746b55cd8d9f7473359069aef381b7d3b" [[package]] name = "esp32" @@ -3780,6 +3757,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -5353,6 +5336,30 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "ouroboros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.114", +] + [[package]] name = "p256" version = "0.13.2" @@ -5761,6 +5768,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "version_check", + "yansi", +] + [[package]] name = "prodash" version = "31.0.0" @@ -6706,7 +6726,7 @@ dependencies = [ "bindgen", "cc", "flate2", - "heck", + "heck 0.5.0", "pkg-config", "regex", "serde_json", @@ -6855,7 +6875,6 @@ checksum = "a0f368519fc6c85fc1afdb769fb5a51123f6158013e143656e25a3485a0d401c" [[package]] name = "spectre-api-sys" version = "0.1.0" -source = "git+https://github.com/Limeth/spectre-api-sys?rev=9e844eb056c3dfee8286ac21ec40fa689a8b8aa2#9e844eb056c3dfee8286ac21ec40fa689a8b8aa2" dependencies = [ "bindgen", "cc", @@ -6941,7 +6960,7 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.114", @@ -8449,8 +8468,6 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] name = "xtensa-lx" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e012d667b0aa6d2592ace8ef145a98bff3e76cca7a644f4181ecd7a916ed289b" dependencies = [ "critical-section", ] @@ -8458,8 +8475,6 @@ dependencies = [ [[package]] name = "xtensa-lx-rt" version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8709f037fb123fe7ff146d2bce86f9dc0dfc53045c016bfd9d703317b6502845" dependencies = [ "document-features", "xtensa-lx", @@ -8469,8 +8484,6 @@ dependencies = [ [[package]] name = "xtensa-lx-rt-proc-macros" version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96fb42cd29c42f8744c74276e9f5bee7b06685bbe5b88df891516d72cb320450" dependencies = [ "proc-macro2", "quote", @@ -8503,6 +8516,12 @@ dependencies = [ "hashlink", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yazi" version = "0.2.1" diff --git a/firmware/acid-firmware/Cargo.toml b/firmware/acid-firmware/Cargo.toml index f32166c..1fbda72 100644 --- a/firmware/acid-firmware/Cargo.toml +++ b/firmware/acid-firmware/Cargo.toml @@ -86,6 +86,7 @@ tinyvec = { version = "1.10.0", default-features = false, features = ["alloc"] } esp-metadata-generated = { version = "0.3.0", features = ["esp32s3"] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } indoc = "2.0.7" +ouroboros = "0.18.5" # A fork of slint with patches for `allocator_api` support. # Don't forget to change `slint-build` in build dependencies, if this is changed. diff --git a/firmware/acid-firmware/src/logging.rs b/firmware/acid-firmware/src/logging.rs index 0a5c184..6fa6a61 100644 --- a/firmware/acid-firmware/src/logging.rs +++ b/firmware/acid-firmware/src/logging.rs @@ -4,7 +4,9 @@ use log::LevelFilter; pub const LOG_LEVEL_FILTER: LevelFilter = { if let Some(string) = option_env!("ESP_LOG") { - if string.eq_ignore_ascii_case("ERROR") { + if string.eq_ignore_ascii_case("OFF") { + LevelFilter::Off + } else if string.eq_ignore_ascii_case("ERROR") { LevelFilter::Error } else if string.eq_ignore_ascii_case("WARN") { LevelFilter::Warn diff --git a/firmware/acid-firmware/src/main.rs b/firmware/acid-firmware/src/main.rs index ce0c9e0..282c220 100644 --- a/firmware/acid-firmware/src/main.rs +++ b/firmware/acid-firmware/src/main.rs @@ -32,7 +32,7 @@ use alloc::vec::Vec; use cfg_if::cfg_if; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_embedded_hal::flash::partition::Partition; -use embassy_executor::Spawner; +use embassy_executor::{SendSpawner, Spawner, SpawnerTraceExt}; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::channel::Channel; use embassy_sync::mutex::Mutex; @@ -88,8 +88,8 @@ use {esp_alloc as _, esp_backtrace as _}; use crate::matrix::IoeMatrix; use crate::peripherals::st7701s::St7701s; use crate::proxy::create_hid_report_interceptor; -use crate::ui::backend::{FramebufferPtr, SlintBackend}; -use crate::ui::dpi::{DmaBounce, DmaTxBounceBuf, Framebuffer, allocate_dma_buffer_in}; +use crate::ui::backend::SlintBackend; +use crate::ui::dpi::{DmaBounce, DmaTxBounceBuf, Framebuffer, Swapchain, allocate_dma_buffer_in}; use crate::vial::{ CustomKeycodes, VIAL_KEYBOARD_DEF, VIAL_KEYBOARD_ID, VIAL_KEYBOARD_NAME, VIAL_PRODUCT_ID, VIAL_VENDOR_ID, @@ -178,14 +178,25 @@ async fn test_bounce_buffers( internal_memory: InternalBurstConfig::Enabled, external_memory: EXTERNAL_BURST_CONFIG, }; - let buffer_src = Box::leak(allocate_dma_buffer_in( - HEIGHT_PIXELS * WIDTH_STORED_PIXELS * BYTES_PER_PIXEL, - burst_config, - &PSRAM_ALLOCATOR, - )); + let (swapchain_reader, mut swapchain_writer) = Swapchain { + framebuffers: [ + Box::leak(allocate_dma_buffer_in( + HEIGHT_PIXELS * WIDTH_STORED_PIXELS * BYTES_PER_PIXEL, + burst_config, + &PSRAM_ALLOCATOR, + )), + Box::leak(allocate_dma_buffer_in( + HEIGHT_PIXELS * WIDTH_STORED_PIXELS * BYTES_PER_PIXEL, + burst_config, + &PSRAM_ALLOCATOR, + )), + ], + } + .into_reader_writer(); { - let buffer_src = bytemuck::cast_slice_mut::(buffer_src); + let write_guard = &mut swapchain_writer.write(); + let buffer_src = write_guard.cast::(); let colors = (0..WIDTH_VISIBLE_PIXELS as u8 / 2) .rev() .map(|val| Rgb565Pixel::from_rgb(0xFF, val * 2, 0)) @@ -212,19 +223,19 @@ async fn test_bounce_buffers( warn!("WIDTH_STORED_PIXELS: {WIDTH_STORED_PIXELS}"); warn!("ROWS_PER_WINDOW: {ROWS_PER_WINDOW}"); - let mut buf = DmaBounce::new( + let buf = DmaBounce::new( Global, channel, AnySpi::from(peripheral), st7701s.dpi, - buffer_src, + swapchain_reader, FRONT_PORCH_SKIPPED_PIXELS * BYTES_PER_PIXEL, WIDTH_STORED_PIXELS * BYTES_PER_PIXEL, ROWS_PER_WINDOW, burst_config, true, ); - buf.send().await; + buf.launch_interrupt_driven_task().await; error!("TEST BOUNCE BUFFERS SECTION DONE"); } @@ -321,11 +332,17 @@ async fn main(_spawner: Spawner) { static EXECUTOR_CORE_0: StaticCell> = StaticCell::new(); let executor_core_0 = InterruptExecutor::new(software_interrupt.software_interrupt2); let executor_core_0 = EXECUTOR_CORE_0.init(executor_core_0); - let interrupt_core_0_spawner = executor_core_0.start(interrupt::Priority::Priority3); + let interrupt_core_0_spawner = executor_core_0.start(interrupt::Priority::Priority1); + + // static EXECUTOR_CORE_1: StaticCell> = StaticCell::new(); + // let executor_core_1 = InterruptExecutor::new(software_interrupt.software_interrupt3); + // let executor_core_1 = EXECUTOR_CORE_1.init(executor_core_1); + // let interrupt_core_1_spawner = executor_core_1.start(interrupt::Priority::Priority2); info!("ESP-RTOS started!"); let main_task_peripherals = MainPeripherals { + // high_priority_task_spawner: interrupt_core_1_spawner, uart_rx, software_interrupt1: software_interrupt.software_interrupt1, RNG: peripherals.RNG, @@ -376,6 +393,7 @@ async fn main(_spawner: Spawner) { /// Peripherals passed to the main task. #[allow(non_snake_case)] struct MainPeripherals { + // high_priority_task_spawner: SendSpawner, uart_rx: UartRx<'static, Blocking>, software_interrupt1: SoftwareInterrupt<'static, 1>, RNG: esp_hal::peripherals::RNG<'static>, @@ -706,14 +724,14 @@ async fn main_task(peripherals: MainPeripherals) { 112, 368 - 112, 960, - 16, - false, + 8, + true, )); info!("Framebuffer created!"); let window_size = [framebuffer.height, framebuffer.width]; - let framebuffer_ptr = FramebufferPtr(framebuffer.as_target_pixels() as _); + let swapchain_writer = framebuffer.swapchain.take().unwrap(); static SECOND_CORE_STACK: StaticCell> = StaticCell::new(); let second_core_stack = SECOND_CORE_STACK.init(Stack::new()); @@ -736,7 +754,7 @@ async fn main_task(peripherals: MainPeripherals) { // peripherals: RefCell::new(Some(peripherals)), window_size, window: RefCell::new(None), - framebuffer: framebuffer_ptr, + swapchain: RefCell::new(swapchain_writer), quit_event_loop: Default::default(), events: Arc::new(critical_section::Mutex::new(RefCell::new(VecDeque::new()))), }; @@ -751,6 +769,19 @@ async fn main_task(peripherals: MainPeripherals) { info!("Awaiting on all tasks..."); + // let spawner = unsafe { Spawner::for_current_executor() }.await; + + // peripherals + // .high_priority_task_spawner + // .must_spawn(run_bounce_buffers(framebuffer)); + + framebuffer + .bounce_buffers + .take() + .unwrap() + .launch_interrupt_driven_task() + .await; + // TODO: Probably want to select! instead and re-try. join_all![ run_alloc_stats_reporter(), @@ -762,7 +793,7 @@ async fn main_task(peripherals: MainPeripherals) { // warn!("Waited."); // framebuffer.bounce_buffers.send().await; // }, - framebuffer.bounce_buffers.send(), + // framebuffer.bounce_buffers.send(), // ui::dpi::run_lcd(st7701s, framebuffer), // lcd_task, run_devices! ( @@ -785,6 +816,11 @@ async fn main_task(peripherals: MainPeripherals) { .await; } +#[embassy_executor::task] +async fn run_bounce_buffers(framebuffer: &'static mut Framebuffer) { + framebuffer.bounce_buffers.as_mut().unwrap().send().await; +} + async fn run_alloc_stats_reporter() { let mut psram_used_prev = 0; let mut heap_used_prev = 0; diff --git a/firmware/acid-firmware/src/peripherals/st7701s/mod.rs b/firmware/acid-firmware/src/peripherals/st7701s/mod.rs index 604e48b..221538b 100644 --- a/firmware/acid-firmware/src/peripherals/st7701s/mod.rs +++ b/firmware/acid-firmware/src/peripherals/st7701s/mod.rs @@ -1276,7 +1276,7 @@ where // // Adafruit would use 11 MHz. // I had lowered the frequency, so that `DmaBounce` could keep up. - .with_frequency(Rate::from_mhz(7)) // From Adafruit + .with_frequency(Rate::from_mhz(5)) // From Adafruit .with_clock_mode(ClockMode { polarity: Polarity::IdleLow, // From Adafruit phase: Phase::ShiftHigh, // From Adafruit diff --git a/firmware/acid-firmware/src/ui/backend.rs b/firmware/acid-firmware/src/ui/backend.rs index 80e0f93..4b57ee5 100644 --- a/firmware/acid-firmware/src/ui/backend.rs +++ b/firmware/acid-firmware/src/ui/backend.rs @@ -19,18 +19,17 @@ use slint::{ }; use xkbcommon::xkb::{self, Keysym}; -use crate::proxy::{KEY_MESSAGE_CHANNEL, TryFromKeysym}; +use crate::{ + proxy::{KEY_MESSAGE_CHANNEL, TryFromKeysym}, + ui::dpi::SwapchainWriter, +}; use super::window_adapter::SoftwareWindowAdapter; -pub struct FramebufferPtr(pub *mut [Rgb565Pixel]); - -unsafe impl Send for FramebufferPtr {} - pub struct SlintBackend { pub window_size: [u32; 2], pub window: RefCell>>, - pub framebuffer: FramebufferPtr, + pub swapchain: RefCell, pub quit_event_loop: Arc, pub events: Arc>>>>, } @@ -39,9 +38,8 @@ impl slint::platform::Platform for SlintBackend { fn create_window_adapter( &self, ) -> Result, slint::PlatformError> { - let renderer = SoftwareRenderer::new_with_repaint_buffer_type( - RepaintBufferType::ReusedBuffer, /* TODO: Implement a swapchain */ - ); + let renderer = + SoftwareRenderer::new_with_repaint_buffer_type(RepaintBufferType::SwappedBuffers); renderer.set_rendering_rotation(RenderingRotation::Rotate270); let window = SoftwareWindowAdapter::new(renderer); // window.set_scale_factor(4.0); @@ -134,8 +132,10 @@ impl slint::platform::Platform for SlintBackend { } window.draw_if_needed(|renderer| { - // TODO: Proper synchronization. - let framebuffer = unsafe { &mut *self.framebuffer.0 }; + // TODO: VSYNC support. + let mut swapchain = self.swapchain.borrow_mut(); + let mut write_guard = swapchain.write(); + let framebuffer = write_guard.cast::(); renderer.render(framebuffer, self.window_size[1] as usize); info!("UI rendered."); }); diff --git a/firmware/acid-firmware/src/ui/dpi.rs b/firmware/acid-firmware/src/ui/dpi.rs index cf0339e..a6c5171 100644 --- a/firmware/acid-firmware/src/ui/dpi.rs +++ b/firmware/acid-firmware/src/ui/dpi.rs @@ -1,5 +1,8 @@ use core::{ alloc::Layout, + cell::UnsafeCell, + marker::PhantomData, + ops::{Deref, DerefMut}, pin::Pin, sync::atomic::{self, AtomicBool, AtomicU32, AtomicUsize}, }; @@ -7,8 +10,10 @@ use core::{ use alloc::{ alloc::{Allocator, Global}, boxed::Box, + sync::Arc, vec, }; +use bytemuck::{AnyBitPattern, NoUninit}; use embassy_sync::{ channel::{Channel, TrySendError}, signal::Signal, @@ -20,7 +25,7 @@ use esp_hal::{ dma::{ self, AnyGdmaChannel, BufView, BurstConfig, DmaChannel, DmaChannelConvert, DmaDescriptor, DmaDescriptorFlags, DmaEligible, DmaRxStreamBuf, DmaTxBuf, DmaTxBuffer, DmaTxInterrupt, - ExternalBurstConfig, InternalBurstConfig, Mem2Mem, SimpleMem2MemTransfer, + ExternalBurstConfig, InternalBurstConfig, Mem2Mem, SimpleMem2Mem, SimpleMem2MemTransfer, }, dma_descriptors, handler, interrupt::{self, Priority}, @@ -33,8 +38,9 @@ use esp_sync::RawMutex; use i_slint_core::software_renderer::{Rgb565Pixel, TargetPixel}; use indoc::{formatdoc, indoc}; use log::{error, info, warn}; +use ouroboros::self_referencing; use rmk::{ - futures::{FutureExt, pin_mut}, + futures::{self, FutureExt, pin_mut}, join_all, }; @@ -76,6 +82,125 @@ pub unsafe fn cache_invalidate_addr(addr: u32, size: u32) { const DMA_CHANNEL_OUTBOUND: usize = 2; const INTERRUPT_OUTBOUND: Interrupt = Interrupt::DMA_OUT_CH2; +#[self_referencing] +struct ReceivingTransfer { + mem2mem: SimpleMem2Mem<'static, Blocking>, + #[borrows(mut mem2mem)] + #[covariant] + transfer: Option>, +} + +pub struct Swapchain { + pub framebuffers: [&'static mut [u8]; 2], +} + +impl Swapchain { + pub fn into_reader_writer(self) -> (SwapchainReader, SwapchainWriter) { + assert_eq!( + self.framebuffers[0].len(), + self.framebuffers[1].len(), + "framebuffers in a swapchain must have an equal length" + ); + + let reader_index = Arc::new(AtomicBool::new(true)); + + ( + SwapchainReader { + framebuffers_rw: [ + self.framebuffers[0] as *const [u8], + self.framebuffers[1] as *const [u8], + ], + reader_index: reader_index.clone(), + }, + SwapchainWriter { + framebuffers_wr: [ + self.framebuffers[1] as *mut [u8], + self.framebuffers[0] as *mut [u8], + ], + reader_index, + }, + ) + } +} + +// TODO: Don't need to store the framebuffer length twice. Use `*const u8` instead, and store length separately. +pub struct SwapchainReader { + /// These are in the opposite order to `SwapchainWriter`'s framebuffers. + framebuffers_rw: [*const [u8]; 2], + reader_index: Arc, +} + +unsafe impl Send for SwapchainReader {} + +impl SwapchainReader { + fn len(&self) -> usize { + self.framebuffers_rw[0].len() + } + + fn load_read_index(&self) -> usize { + self.reader_index.load(atomic::Ordering::SeqCst) as usize + } + + fn get_latest_framebuffer(&self) -> &[u8] { + unsafe { &*self.framebuffers_rw[self.load_read_index()] } + } +} + +// TODO: Don't need to store the framebuffer length twice. Use `*mut u8` instead, and store length separately. +pub struct SwapchainWriter { + /// These are in the opposite order to `SwapchainReader`'s framebuffers. + framebuffers_wr: [*mut [u8]; 2], + reader_index: Arc, +} + +unsafe impl Send for SwapchainWriter {} + +impl SwapchainWriter { + fn len(&self) -> usize { + self.framebuffers_wr[0].len() + } + + pub fn write(&mut self) -> SwapchainWriteGuard<'_> { + let framebuffer_ptr = + self.framebuffers_wr[self.reader_index.load(atomic::Ordering::SeqCst) as usize]; + SwapchainWriteGuard { + framebuffer: unsafe { &mut *framebuffer_ptr }, + reader_index: &self.reader_index, + } + } +} + +pub struct SwapchainWriteGuard<'a> { + framebuffer: &'a mut [u8], + reader_index: &'a AtomicBool, +} + +impl<'a> SwapchainWriteGuard<'a> { + pub fn cast(&mut self) -> &mut [T] { + bytemuck::cast_slice_mut::<_, T>(&mut self.framebuffer) + } +} + +impl Drop for SwapchainWriteGuard<'_> { + fn drop(&mut self) { + self.reader_index.fetch_xor(true, atomic::Ordering::SeqCst); + } +} + +impl<'a> Deref for SwapchainWriteGuard<'a> { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.framebuffer + } +} + +impl<'a> DerefMut for SwapchainWriteGuard<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.framebuffer + } +} + pub struct DmaBounce { // TODO: Make these generic. // They currently cannot be generic, because they lacks a `reborrow` method. @@ -84,6 +209,8 @@ pub struct DmaBounce { peripheral_src: AnySpi<'static>, // This can also be more generic, see `DmaEligible` in `Mem2Mem::new`. peripheral_dst: Option>, + // TODO: Combine with peripheral_dst using an enum? + transfer_dst: Option>, // TODO: Consider having a separate burst config for the two transfers. burst_config: BurstConfig, @@ -92,7 +219,7 @@ pub struct DmaBounce { window_size: usize, /// The number of windows. windows_len: usize, - buffer_src: &'static mut [u8], + swapchain_src: SwapchainReader, // Two buffers of size `window_size`, // one of which is being written to, while the other is being read from. bounce_buffer_dst: &'static mut [u8], @@ -105,9 +232,11 @@ pub struct DmaBounce { bounce_dst_descs: &'static mut [DmaDescriptor], // A cyclic descriptor list that spans the buffers `bounce_buffer_dst` and `bounce_buffer_src`. bounce_src_descs: &'static mut [DmaDescriptor], + descriptors_per_window: usize, // The index of the next window about to be received into the destination bounce buffer. window_index_next: usize, frame_index_next: usize, + receiving_transfer: Option, } impl DmaBounce { @@ -126,7 +255,7 @@ impl DmaBounce { channel: DMA_CH0<'static>, peripheral_src: AnySpi<'static>, peripheral_dst: Dpi<'static, Blocking>, - buffer_src: &'static mut [u8], + swapchain_src: SwapchainReader, row_front_porch_bytes: usize, row_width_bytes: usize, window_size_rows: usize, @@ -136,20 +265,23 @@ impl DmaBounce { let window_size = row_width_bytes * window_size_rows; assert_eq!( - buffer_src.len() % window_size, + swapchain_src.len() % window_size, 0, "the size of a source buffer must be a multiple of the window size ({window_size} bytes), but it is {len} bytes large", - len = buffer_src.len() + len = swapchain_src.len() ); // Conservative alignment. Maxiumum of the cartesian product of [tx, rx] × [internal, external]. let alignment = burst_config.min_compatible_alignment(); - assert_eq!( - buffer_src.as_ptr() as usize % alignment, - 0, - "the source buffer must be sufficiently aligned to {alignment} bytes for the burst config", - ); + for &swapchain_ptr in &swapchain_src.framebuffers_rw { + assert_eq!( + unsafe { &*swapchain_ptr }.as_ptr() as usize % alignment, + 0, + "the source buffer must be sufficiently aligned to {alignment} bytes for the burst config", + ); + } + assert_eq!( row_width_bytes % alignment, 0, @@ -168,7 +300,7 @@ impl DmaBounce { "front porch too large" ); - let windows_len = buffer_src.len() / window_size; + let windows_len = swapchain_src.len() / window_size; // TODO: Figure out a way to avoid `leak`ing memory. // We probably want to store the `Box`es and then unsafely extend the lifetime at sites of usage. let bounce_buffer_dst = @@ -183,51 +315,40 @@ impl DmaBounce { }); let bounce_dst_descs = Self::linear_descriptors_for_buffer(window_size, burst_config, |_| {}); - let bounce_src_descs = if cyclic { - Self::bounce_descriptors_for_buffer_cyclic( - row_front_porch_bytes, - row_width_bytes, - window_size_rows, - unsafe { - ( - &mut *(bounce_buffer_dst as *mut _), - &mut *(bounce_buffer_src as *mut _), - ) - }, - burst_config, - ) - } else { - Self::bounce_descriptors_for_buffer_single( - windows_len, - row_front_porch_bytes, - row_width_bytes, - window_size_rows, - unsafe { - ( - &mut *(bounce_buffer_dst as *mut _), - &mut *(bounce_buffer_src as *mut _), - ) - }, - burst_config, - ) - }; + let (bounce_src_descs, descriptors_per_window) = Self::bounce_descriptors_for_buffer( + windows_len, + row_front_porch_bytes, + row_width_bytes, + window_size_rows, + unsafe { + ( + &mut *(bounce_buffer_dst as *mut _), + &mut *(bounce_buffer_src as *mut _), + ) + }, + burst_config, + cyclic, + ); Self { channel, peripheral_src, peripheral_dst: Some(peripheral_dst), + transfer_dst: None, burst_config, cyclic, window_size, windows_len, - buffer_src, + swapchain_src, bounce_buffer_dst, bounce_buffer_src, src_descs, bounce_dst_descs, bounce_src_descs, + descriptors_per_window, window_index_next: 0, frame_index_next: 0, + receiving_transfer: None, } } @@ -358,81 +479,15 @@ impl DmaBounce { ); } - fn bounce_descriptors_for_buffer_cyclic( - row_front_porch_bytes: usize, - row_width_bytes: usize, - window_size_rows: usize, - bounce_buffers: (&'static mut [u8], &'static mut [u8]), - burst_config: BurstConfig, - ) -> &'static mut [DmaDescriptor] { - assert_eq!( - bounce_buffers.0.len(), - bounce_buffers.1.len(), - "bounce buffers must be equal in size" - ); - - let buffer_len = bounce_buffers.0.len(); - - assert_eq!( - buffer_len, - row_width_bytes * window_size_rows, - "the provided bounce buffers have an invalid size" - ); - - let max_chunk_size = burst_config.max_compatible_chunk_size(); - let descriptors_per_row_front_porch = - dma::descriptor_count(row_front_porch_bytes, max_chunk_size, false); - let descriptors_per_row_stored = - dma::descriptor_count(row_width_bytes, max_chunk_size, false); - let descriptors_per_row = descriptors_per_row_stored + descriptors_per_row_front_porch; - let descriptors_per_window = window_size_rows * descriptors_per_row; - let descriptors_combined = - Box::leak(vec![DmaDescriptor::EMPTY; 2 * descriptors_per_window].into_boxed_slice()); - let descriptors_pair = descriptors_combined.split_at_mut(descriptors_per_window); - - // Link up the descriptors. - fn link_up_descriptors( - descriptors: &mut [DmaDescriptor], - descriptors_other: &mut [DmaDescriptor], - ) { - let mut next = descriptors_other.first_mut().unwrap(); - for desc in descriptors.iter_mut().rev() { - desc.next = next; - next = desc; - } - } - - link_up_descriptors(descriptors_pair.0, descriptors_pair.1); - link_up_descriptors(descriptors_pair.1, descriptors_pair.0); - - // Prepare each descriptor's buffer size. - for (bounce_buffer, descriptors) in [ - (bounce_buffers.0, descriptors_pair.0), - (bounce_buffers.1, descriptors_pair.1), - ] { - Self::prepare_descriptors_window( - bounce_buffer, - descriptors, - row_front_porch_bytes, - row_width_bytes, - window_size_rows, - max_chunk_size, - descriptors_per_row, - descriptors_per_row_front_porch, - ); - } - - descriptors_combined - } - - fn bounce_descriptors_for_buffer_single( + fn bounce_descriptors_for_buffer( windows_len: usize, row_front_porch_bytes: usize, row_width_bytes: usize, window_size_rows: usize, bounce_buffers: (&'static mut [u8], &'static mut [u8]), burst_config: BurstConfig, - ) -> &'static mut [DmaDescriptor] { + cyclic: bool, + ) -> (&'static mut [DmaDescriptor], usize) { assert_eq!( bounce_buffers.0.len(), bounce_buffers.1.len(), @@ -449,6 +504,15 @@ impl DmaBounce { "the provided bounce buffers have an invalid size" ); + // Implementation note: + // A cyclic descriptor could consist of just a set of descriptors per window, + // so two sets in total, because there are two bounce buffers. + // However, we can also access the pointer of the EOF descriptor within the + // EOF interrupt handler, which lets us compute which descriptor generated that + // interrupt. + // This is useful in the case when an interrupt is missed. Then the number of interrupts + // handled doesn't correspond to the number of windows sent to the destination peripheral. + // In that case, the number of windows sent can be computed from the address of the descriptor. let max_chunk_size = burst_config.max_compatible_chunk_size(); let descriptors_per_row_front_porch = dma::descriptor_count(row_front_porch_bytes, max_chunk_size, false); @@ -459,9 +523,15 @@ impl DmaBounce { let descriptors_per_frame = descriptors_per_window * windows_len; let descriptors_frame = Box::leak(vec![DmaDescriptor::EMPTY; descriptors_per_frame].into_boxed_slice()); + let descriptors_frame_ptr = descriptors_frame.as_ptr(); // Link up the descriptors. - let mut next = core::ptr::null_mut(); + let mut next = if cyclic { + descriptors_frame.first_mut().unwrap() as *mut _ + } else { + core::ptr::null_mut() + }; + for desc in descriptors_frame.iter_mut().rev() { desc.next = next; next = desc; @@ -497,10 +567,31 @@ impl DmaBounce { windows_len * window_size_rows * (row_front_porch_bytes + row_width_bytes) ); - descriptors_frame + (descriptors_frame, descriptors_per_window) } fn linear_descriptors_prepare( + descriptors: &mut [DmaDescriptor], + mut buffer: Option<&[u8]>, + mut setup_desc: impl FnMut(&mut DmaDescriptor), + ) { + for descriptor in descriptors.iter_mut() { + if let Some(inner_buffer) = buffer { + descriptor.buffer = inner_buffer.as_ptr() as *mut u8; + buffer = Some(&inner_buffer[descriptor.size()..]); + } + (setup_desc)(descriptor); + } + + if let Some(buffer) = buffer { + assert!( + buffer.is_empty(), + "a buffer of an incompatible length was assigned to a descriptor set" + ); + } + } + + fn linear_descriptors_prepare_mut( descriptors: &mut [DmaDescriptor], mut buffer: Option<&mut [u8]>, mut setup_desc: impl FnMut(&mut DmaDescriptor), @@ -551,10 +642,13 @@ impl DmaBounce { /// Receive a window of bytes into the current dst bounce buffer. /// Finally, swaps the bounce buffers. - async fn receive_window(&mut self) { + /// + /// # Safety: + /// TODO + unsafe fn receive_window_start(&mut self) -> ReceivingTransfer { // Descriptors are initialized by `DmaTxBuf::new`. - let buffer_src_window = - &mut self.buffer_src[self.window_index_next * self.window_size..][..self.window_size]; + let buffer_src_window = &self.swapchain_src.get_latest_framebuffer() + [self.window_index_next * self.window_size..][..self.window_size]; Self::linear_descriptors_prepare(self.src_descs, Some(buffer_src_window), |_desc| { // No need to call `DmaDescriptor::reset_for_tx`, because @@ -562,7 +656,59 @@ impl DmaBounce { // 2. the EOF flag is already set during the construction of this buffer. }); // TODO: Precompute a descriptor list for each buffer, then use `None` instead of `Some(&mut *self.bounce_buffer_dst)`. - Self::linear_descriptors_prepare( + Self::linear_descriptors_prepare_mut( + self.bounce_dst_descs, + Some(&mut *self.bounce_buffer_dst), + |desc| { + desc.reset_for_rx(); + }, + ); + + // Extend the lifetime to 'static because it is required by Mem2Mem. + // + // Safety: + // Pointees are done being used by the driver before this scope ends, + // this is because we `SimpleMem2MemTransfer::wait()` on the transfer to finish. + let bounce_dst_descs: &'static mut [DmaDescriptor] = + unsafe { &mut *(self.bounce_dst_descs as *mut _) }; + let src_descs: &'static mut [DmaDescriptor] = unsafe { &mut *(self.src_descs as *mut _) }; + + let mem2mem = unsafe { + Mem2Mem::new( + self.channel.clone_unchecked(), + self.peripheral_src.clone_unchecked(), + ) + } + .with_descriptors(bounce_dst_descs, src_descs, self.burst_config) + .unwrap(); + + ReceivingTransferBuilder { + mem2mem, + transfer_builder: |mem2mem| { + Some( + mem2mem + .start_transfer(&mut self.bounce_buffer_dst, buffer_src_window) + .unwrap(), + ) + }, + } + .build() + } + + /// Receive a window of bytes into the current dst bounce buffer. + /// Finally, swaps the bounce buffers. + async fn receive_window(&mut self) { + // Descriptors are initialized by `DmaTxBuf::new`. + let buffer_src_window = &self.swapchain_src.get_latest_framebuffer() + [self.window_index_next * self.window_size..][..self.window_size]; + + Self::linear_descriptors_prepare(self.src_descs, Some(buffer_src_window), |_desc| { + // No need to call `DmaDescriptor::reset_for_tx`, because + // 1. we don't rely on the ownership flag; + // 2. the EOF flag is already set during the construction of this buffer. + }); + // TODO: Precompute a descriptor list for each buffer, then use `None` instead of `Some(&mut *self.bounce_buffer_dst)`. + Self::linear_descriptors_prepare_mut( self.bounce_dst_descs, Some(&mut *self.bounce_buffer_dst), |desc| { @@ -595,14 +741,39 @@ impl DmaBounce { self.increase_window_counter(1); } - fn increase_window_counter(&mut self, windows: usize) { - if windows % 2 == 1 { + fn increase_window_counter(&mut self, windows: isize) { + if windows.rem_euclid(2) == 1 { core::mem::swap(&mut self.bounce_buffer_dst, &mut self.bounce_buffer_src); } - self.window_index_next += windows; - self.frame_index_next += self.window_index_next / self.windows_len; - self.window_index_next = self.window_index_next % self.windows_len; + let window_index_next = self.window_index_next as isize + windows; + self.frame_index_next = (self.frame_index_next as isize + + window_index_next / self.windows_len as isize) + as usize; + self.window_index_next = window_index_next.rem_euclid(self.windows_len as isize) as usize; + } + + pub async fn launch_interrupt_driven_task(mut self) { + Self::enable_interrupts(); + + // Receive the first 2 windows, so that the outbound transfer can read valid data. + self.receive_window().await; + + let dma_tx_buffer = self.get_dma_tx_buffer(); + let transfer = self + .peripheral_dst + .take() + .unwrap() + .send(self.cyclic /* Send perpetually */, dma_tx_buffer) + .unwrap_or_else(|(error, _, _)| { + panic!("failed to begin the transmission of the first frame: {error:?}"); + }); + self.transfer_dst = Some(transfer); + self.receiving_transfer = Some(unsafe { self.receive_window_start() }); + + unsafe { + *DMA_STATE.0.get() = Some(self); + } } pub async fn send(&mut self) { @@ -633,31 +804,17 @@ impl DmaBounce { // "Window received: {} {}", // self.window_index_next, self.frame_index_next // ); - let windows_skipped = WINDOWS_SKIPPED + let windows_sent = WINDOWS_SENT .wait() .with_timeout(Duration::from_millis(100)) .await .unwrap_or_else(|_| { error!("Timed out when waiting for skipped windows."); - 0 // TODO: This should be -1 to repeat the same window. + 0 }); + let windows_skipped = windows_sent as isize - 1; - // let windows_skipped = match windows_skipped { - // Ok(windows_skipped) => windows_skipped, - // Err(_) => { - // warn!( - // "Waiting for skipped windows timed out. Transfer done: {}", - // transfer.is_done() - // ); - // if transfer.is_done() { - // let (result, _, _) = transfer.wait(); - // panic!("Transfer result: {result:?}"); - // } - // 0 - // } - // }; - - if windows_skipped > 0 { + if windows_skipped != 0 { self.increase_window_counter(windows_skipped); windows_skipped_total += windows_skipped; // error!( @@ -674,7 +831,9 @@ impl DmaBounce { if !self.cyclic && (self.window_index_next == 1 || transfer.is_done()) { if self.window_index_next > 1 { - self.increase_window_counter(self.windows_len - self.window_index_next + 1); + self.increase_window_counter( + self.windows_len as isize - self.window_index_next as isize + 1, + ); } else if self.window_index_next == 0 { self.increase_window_counter(1); } @@ -763,25 +922,94 @@ unsafe impl DmaTxBuffer for DmaTxBounceBuf { /// Intended to be listened on by the renderer, to synchronize the refresh frequency with. pub static FRAMES_SKIPPED: Signal = Signal::new(); -static WINDOWS_SKIPPED: Signal = Signal::new(); -// static INBOUND_TRANSFER_FINISHED: Signal = Signal::new(); +static WINDOWS_SENT: Signal = Signal::new(); +static DMA_STATE: SyncUnsafeCell> = SyncUnsafeCell(UnsafeCell::new(None)); + +#[repr(transparent)] +pub struct SyncUnsafeCell(UnsafeCell); + +unsafe impl Sync for SyncUnsafeCell {} + +// #[derive(Clone, Copy)] +// struct DmaState { +// descriptors_ptr: *const DmaDescriptor, +// descriptors_per_window: usize, +// windows_per_frame: usize, +// last_window_index: usize, +// } + +// unsafe impl Sync for DmaState {} #[handler(priority = Priority::Priority3)] #[ram] // Improves performance. fn dma_outbound_interrupt_handler() { let interrupt = DMA::regs().ch(DMA_CHANNEL_OUTBOUND).out_int(); - let bounce_buffer_processed = interrupt.st().read().out_eof().bit_is_set(); - if bounce_buffer_processed { - // Clear the bit by writing 1 to the clear bits. - interrupt.clr().write(|w| w.out_eof().bit(true)); + let bounce_buffer_sent = interrupt.st().read().out_eof().bit_is_set(); - WINDOWS_SKIPPED.signal( - WINDOWS_SKIPPED - .try_take() - .map(|windows_skipped| windows_skipped + 1) - .unwrap_or_default(), + if !bounce_buffer_sent { + return; + } + + // Clear the bit by writing 1 to the clear bits. + interrupt.clr().write(|w| w.out_eof().bit(true)); + + // SAFETY: This value is only ever read in our interrupt handler, + // and interrupts are disabled, and we only use this in one thread. + let Some(dma_state) = unsafe { &mut *DMA_STATE.0.get() }.as_mut() else { + error!("no DMA state available when executing DMA interrupt handler"); + return; + }; + + // The descriptor of the buffer with an EOF flag that just finished being sent. + let descriptor_ptr = DMA::regs() + .ch(DMA_CHANNEL_OUTBOUND) + .out_eof_des_addr() + .read() + .out_eof_des_addr() + .bits() as *const DmaDescriptor; + // This is the index of the window that just finished being transmitted to the destination peripheral. + let window_sent_index = + unsafe { descriptor_ptr.offset_from_unsigned(dma_state.bounce_src_descs.as_ptr()) } + / dma_state.descriptors_per_window; + // warn!("{window_sent_index}"); + // The next window to be sent is `(window_sent_index + 1) % dma_state.windows_len`. + // That is not the window we want to buffer, because the transmissions would race. + // We instead want to buffer the next window: + let window_index_next = (window_sent_index + 2) % dma_state.windows_len; + + // Swap bounce buffers. + if (dma_state.windows_len + window_index_next - dma_state.window_index_next) % 2 == 1 { + core::mem::swap( + &mut dma_state.bounce_buffer_dst, + &mut dma_state.bounce_buffer_src, ); } + + dma_state.window_index_next = window_index_next; + + // warn!("{window_sent_index} {window_index_next}"); + + let mut receiving_transfer = dma_state + .receiving_transfer + .take() + .expect("no ongoing transfer to a bounce buffer present"); + let receiving_transfer = receiving_transfer + .with_mut(|x| x.transfer.take()) + .expect("no ongoing inner transfer to a bounce buffer present"); + + // if !receiving_transfer.is_done() { + // error!("{window_sent_index}"); + // error!("the transfer to a bounce buffer has not finished yet, aborting"); + // } + + if receiving_transfer.is_done() { + drop(receiving_transfer); + } else { + receiving_transfer.wait().unwrap(); + } + + // If there is any ongoing transfer, cancel it and start a new one. + dma_state.receiving_transfer = Some(unsafe { dma_state.receive_window_start() }); } // #[handler(priority = Priority::Priority3)] @@ -879,10 +1107,12 @@ pub fn allocate_dma_buffer_in( } } +// TODO: Rename or get rid of. pub struct Framebuffer { pub width: u32, pub height: u32, - pub bounce_buffers: DmaBounce, + pub swapchain: Option, + pub bounce_buffers: Option, } impl Framebuffer { @@ -899,17 +1129,25 @@ impl Framebuffer { ) -> Self { const BYTES_PER_PIXEL: usize = core::mem::size_of::(); let buffer_size = width_pixels as usize * height_pixels as usize * BYTES_PER_PIXEL; - let psram_buffer = Box::leak(allocate_dma_buffer_in( - buffer_size, - burst_config, - &PSRAM_ALLOCATOR, - )); + let framebuffers = [ + Box::leak(allocate_dma_buffer_in( + buffer_size, + burst_config, + &PSRAM_ALLOCATOR, + )), + Box::leak(allocate_dma_buffer_in( + buffer_size, + burst_config, + &PSRAM_ALLOCATOR, + )), + ]; + let (swapchain_reader, swapchain_writer) = Swapchain { framebuffers }.into_reader_writer(); let bounce_buffers = DmaBounce::new( Global, channel, peripheral_src, peripheral_dst, - psram_buffer, + swapchain_reader, front_porch_pixels as usize * BYTES_PER_PIXEL, width_pixels as usize * BYTES_PER_PIXEL, rows_per_window, @@ -920,11 +1158,8 @@ impl Framebuffer { Self { width: width_pixels, height: height_pixels, - bounce_buffers, + swapchain: Some(swapchain_writer), + bounce_buffers: Some(bounce_buffers), } } - - pub fn as_target_pixels(&mut self) -> &mut [Rgb565Pixel] { - bytemuck::cast_slice_mut::<_, Rgb565Pixel>(self.bounce_buffers.buffer_src) - } } diff --git a/firmware/acid-firmware/src/ui/mod.rs b/firmware/acid-firmware/src/ui/mod.rs index 06f1307..ba82cf8 100644 --- a/firmware/acid-firmware/src/ui/mod.rs +++ b/firmware/acid-firmware/src/ui/mod.rs @@ -377,11 +377,12 @@ impl State { // SIGNAL_LCD_SUBMIT.signal(()); #[cfg(feature = "limit-fps")] embassy_time::Timer::after(FRAME_DURATION_MIN).await; - let frames_skipped = FRAMES_SKIPPED.wait().await; + // let frames_skipped = FRAMES_SKIPPED.wait().await; + + // if frames_skipped > 0 { + // error!("Renderer missed {frames_skipped} frames."); + // } - if frames_skipped > 0 { - error!("Renderer missed {frames_skipped} frames."); - } // SIGNAL_UI_RENDER.wait().await; }