From 3a39d7adebcdafe33faf561232313decd58cf780 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Mon, 18 Mar 2024 11:30:03 +0100 Subject: [PATCH] esp-idf: Add support for line-by-line rendering Rendering by line into a line buffer that's in IRAM can be faster than accessing framebuffers in slower PSRAM, so offer this by allowing users to omit even the initial framebuffer. --- api/cpp/esp-idf/slint/include/slint-esp.h | 10 ++- api/cpp/esp-idf/slint/src/slint-esp.cpp | 92 +++++++++++++++-------- api/cpp/include/slint-platform.h | 50 ++++++++++++ api/cpp/platform.rs | 85 ++++++++++++++++++++- 4 files changed, 201 insertions(+), 36 deletions(-) diff --git a/api/cpp/esp-idf/slint/include/slint-esp.h b/api/cpp/esp-idf/slint/include/slint-esp.h index a843e319740..17042cb133e 100644 --- a/api/cpp/esp-idf/slint/include/slint-esp.h +++ b/api/cpp/esp-idf/slint/include/slint-esp.h @@ -15,15 +15,19 @@ * - `size` is the size of the screen * - `panel` is a handle to the display. * - `touch` is a handle to the touch screen, if the device has a touch screen - * - `buffer1` is a buffer of at least the size of the frame in which the slint scene will be drawn. - * Slint will take care to flush it to the screen + * - `buffer1`, if specified, is a buffer of at least the size of the frame in which the slint scene + * will be drawn. Slint will take care to flush it to the screen * - `buffer2`, if specified, is a second buffer to be used with double buffering, * both buffer1 and buffer2 should then be obtained with `esp_lcd_rgb_panel_get_frame_buffer` * - `rotation` applies a transformation while rendering in the buffer + * + * If no buffer1 is specified, Slint assumes that no direct framebuffers are accessible and instead + * will render line-by-line, by allocating a line buffer with MALLOC_CAP_INTERNAL, and flush it to + * the screen with esp_lcd_panel_draw_bitmap. */ void slint_esp_init(slint::PhysicalSize size, esp_lcd_panel_handle_t panel, std::optional touch, - std::span buffer1, + std::optional> buffer1 = {}, std::optional> buffer2 = {} #ifdef SLINT_FEATURE_EXPERIMENTAL , diff --git a/api/cpp/esp-idf/slint/src/slint-esp.cpp b/api/cpp/esp-idf/slint/src/slint-esp.cpp index b1d6287ef71..95cab26cd8d 100644 --- a/api/cpp/esp-idf/slint/src/slint-esp.cpp +++ b/api/cpp/esp-idf/slint/src/slint-esp.cpp @@ -22,7 +22,7 @@ struct EspPlatform : public slint::platform::Platform { EspPlatform(slint::PhysicalSize size, esp_lcd_panel_handle_t panel, std::optional touch, - std::span buffer1, + std::optional> buffer1, std::optional> buffer2 = {} #ifdef SLINT_FEATURE_EXPERIMENTAL , @@ -52,7 +52,7 @@ struct EspPlatform : public slint::platform::Platform slint::PhysicalSize size; esp_lcd_panel_handle_t panel_handle; std::optional touch_handle; - std::span buffer1; + std::optional> buffer1; std::optional> buffer2; #ifdef SLINT_FEATURE_EXPERIMENTAL slint::platform::SoftwareRenderer::RenderingRotation rotation; @@ -214,43 +214,71 @@ void EspPlatform::run_event_loop() } if (std::exchange(m_window->needs_redraw, false)) { - auto rotated = false + if (buffer1) { + auto buffer1 = *this->buffer1; + auto rotated = false #ifdef SLINT_FEATURE_EXPERIMENTAL - || rotation - == slint::platform::SoftwareRenderer::RenderingRotation::Rotate90 - || rotation - == slint::platform::SoftwareRenderer::RenderingRotation::Rotate270 + || rotation + == slint::platform::SoftwareRenderer::RenderingRotation:: + Rotate90 + || rotation + == slint::platform::SoftwareRenderer::RenderingRotation:: + Rotate270 #endif - ; - auto region = - m_window->m_renderer.render(buffer1, rotated ? size.height : size.width); - auto o = region.bounding_box_origin(); - auto s = region.bounding_box_size(); - if (s.width > 0 && s.height > 0) { - if (buffer2) { + ; + auto region = m_window->m_renderer.render(buffer1, + rotated ? size.height : size.width); + auto o = region.bounding_box_origin(); + auto s = region.bounding_box_size(); + if (s.width > 0 && s.height > 0) { + if (buffer2) { #if SOC_LCD_RGB_SUPPORTED && ESP_IDF_VERSION_MAJOR >= 5 - xSemaphoreGive(sem_gui_ready); - xSemaphoreTake(sem_vsync_end, portMAX_DELAY); + xSemaphoreGive(sem_gui_ready); + xSemaphoreTake(sem_vsync_end, portMAX_DELAY); #endif - // Assuming that using double buffer means that the buffer comes from the - // driver and we need to pass the exact pointer. - // https://github.com/espressif/esp-idf/blob/53ff7d43dbff642d831a937b066ea0735a6aca24/components/esp_lcd/src/esp_lcd_panel_rgb.c#L681 - esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, size.width, size.height, - buffer1.data()); - std::swap(buffer1, buffer2.value()); - } else { - for (int y = o.y; y < o.y + s.height; y++) { - for (int x = o.x; x < o.x + s.width; x++) { - // Swap endianess to big endian - auto px = - reinterpret_cast(&buffer1[y * size.width + x]); - *px = (*px << 8) | (*px >> 8); + // Assuming that using double buffer means that the buffer comes from + // the driver and we need to pass the exact pointer. + // https://github.com/espressif/esp-idf/blob/53ff7d43dbff642d831a937b066ea0735a6aca24/components/esp_lcd/src/esp_lcd_panel_rgb.c#L681 + esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, size.width, size.height, + buffer1.data()); + + std::swap(buffer1, buffer2.value()); + } else { + for (int y = o.y; y < o.y + s.height; y++) { + for (int x = o.x; x < o.x + s.width; x++) { + // Swap endianess to big endian + auto px = reinterpret_cast( + &buffer1[y * size.width + x]); + *px = (*px << 8) | (*px >> 8); + } + esp_lcd_panel_draw_bitmap(panel_handle, o.x, y, o.x + s.width, + y + 1, + buffer1.data() + y * size.width + o.x); } - esp_lcd_panel_draw_bitmap(panel_handle, o.x, y, o.x + s.width, y + 1, - buffer1.data() + y * size.width + o.x); } } + } else { + slint::platform::Rgb565Pixel *lb = + (slint::platform::Rgb565Pixel *)heap_caps_malloc( + size.width * 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + + m_window->m_renderer.render_by_line([this, + &lb](std::size_t line_y, + std::size_t line_start, + std::size_t line_end, + void (*render_fn)( + void *, + std::span + &), + void *render_fn_data) { + std::span view { lb, line_end - line_start }; + render_fn(render_fn_data, view); + esp_lcd_panel_draw_bitmap(panel_handle, line_start, line_y, line_end, + line_y + 1, lb); + }); + free(lb); } } @@ -292,7 +320,7 @@ TaskHandle_t EspPlatform::task = {}; void slint_esp_init(slint::PhysicalSize size, esp_lcd_panel_handle_t panel, std::optional touch, - std::span buffer1, + std::optional> buffer1, std::optional> buffer2 #ifdef SLINT_FEATURE_EXPERIMENTAL , diff --git a/api/cpp/include/slint-platform.h b/api/cpp/include/slint-platform.h index 7369ba7f850..3813558ea0d 100644 --- a/api/cpp/include/slint-platform.h +++ b/api/cpp/include/slint-platform.h @@ -6,6 +6,7 @@ #include "slint.h" #include +#include #include struct xcb_connection_t; @@ -630,6 +631,55 @@ class SoftwareRenderer : public AbstractRenderer return PhysicalRegion { r }; } + /// Render the window scene, line by line. The provided Callback will be invoked for each line + /// that needs to rendered. + /// + /// The renderer uses a cache internally and will only render the part of the window + /// which are dirty. + /// + /// This function returns the physical region that was rendered considering the rotation. + /// + /// The callback is invoked with the line number as first parameter, and the start x and end x + /// coordinates of the line as second and third parameter. The implementation must provide a + /// line buffer (as std::span) and invoke the provided fourth function pointer (render_fn) with + /// it, to fill it with pixels. The last parameter of Callback is a private data pointer that + /// must be provided as first argument to render_fn. + /// After the line buffer is filled with pixels, your implementation is free to flush that line + /// to the screen for display. + template &), void *> + Callback> + PhysicalRegion render_by_line(Callback process_line_callback) const + { + auto r = cbindgen_private::slint_software_renderer_render_by_line_rgb565( + inner, + [](void *process_line_callback_ptr, uintptr_t line, uintptr_t line_start, + uintptr_t line_end, void (*render_fn)(const void *, uint16_t *, std::size_t), + const void *render_fn_data) { + struct RenderFnAndData + { + void (*render_fn)(const void *, uint16_t *, std::size_t); + const void *render_fn_data; + }; + RenderFnAndData rfad; + rfad.render_fn = render_fn; + rfad.render_fn_data = render_fn_data; + + (*reinterpret_cast(process_line_callback_ptr))( + std::size_t(line), std::size_t(line_start), std::size_t(line_end), + [](void *rfad_ptr, std::span &line_span) { + RenderFnAndData *rfad = + reinterpret_cast(rfad_ptr); + rfad->render_fn(rfad->render_fn_data, + reinterpret_cast(line_span.data()), + line_span.size()); + }, + &rfad); + }, + &process_line_callback); + return PhysicalRegion { r }; + } + # ifdef SLINT_FEATURE_EXPERIMENTAL /// This enum describes the rotation that is applied to the buffer when rendering. /// To be used in set_rendering_rotation() diff --git a/api/cpp/platform.rs b/api/cpp/platform.rs index c8122ce60e8..256997a0f47 100644 --- a/api/cpp/platform.rs +++ b/api/cpp/platform.rs @@ -351,7 +351,9 @@ mod software_renderer { use super::*; type SoftwareRendererOpaque = *const c_void; use i_slint_core::graphics::{IntRect, Rgb8Pixel}; - use i_slint_core::software_renderer::{RepaintBufferType, Rgb565Pixel, SoftwareRenderer}; + use i_slint_core::software_renderer::{ + LineBufferProvider, RepaintBufferType, Rgb565Pixel, SoftwareRenderer, + }; #[no_mangle] pub unsafe extern "C" fn slint_software_renderer_new( @@ -400,6 +402,87 @@ mod software_renderer { i_slint_core::graphics::euclid::rect(orig.x, orig.y, size.width as i32, size.height as i32) } + #[no_mangle] + pub unsafe extern "C" fn slint_software_renderer_render_by_line_rgb565( + r: SoftwareRendererOpaque, + process_line_fn: extern "C" fn( + *mut core::ffi::c_void, + usize, + usize, + usize, + extern "C" fn(*const core::ffi::c_void, *mut u16, usize), + *const core::ffi::c_void, + ), + user_data: *mut core::ffi::c_void, + ) -> IntRect { + struct Rgb565Processor { + process_line_fn: extern "C" fn( + *mut core::ffi::c_void, + usize, + usize, + usize, + extern "C" fn(*const core::ffi::c_void, *mut u16, usize), + *const core::ffi::c_void, + ), + user_data: *mut core::ffi::c_void, + } + + impl LineBufferProvider for Rgb565Processor { + type TargetPixel = Rgb565Pixel; + fn process_line( + &mut self, + line: usize, + range: core::ops::Range, + render_fn: impl FnOnce(&mut [Rgb565Pixel]), + ) { + self.cpp_process_line(line, range, render_fn); + } + } + + impl Rgb565Processor { + fn cpp_process_line( + &mut self, + line: usize, + range: core::ops::Range, + render_fn: RenderFn, + ) { + let mut render_fn = Some(render_fn); + let render_fn_ptr = + &mut render_fn as *mut Option as *const core::ffi::c_void; + + extern "C" fn cpp_render_line_callback( + render_fn_ptr: *const core::ffi::c_void, + line_start: *mut u16, + len: usize, + ) { + let line_slice = unsafe { + core::slice::from_raw_parts_mut(line_start as *mut Rgb565Pixel, len) + }; + let render_fn = + unsafe { (*(render_fn_ptr as *mut Option)).take().unwrap() }; + render_fn(line_slice); + } + + (self.process_line_fn)( + self.user_data, + line, + range.start, + range.end, + cpp_render_line_callback::, + render_fn_ptr, + ); + } + } + + let renderer = &*(r as *const SoftwareRenderer); + + let processor = Rgb565Processor { process_line_fn, user_data }; + + let r = renderer.render_by_line(processor); + let (orig, size) = (r.bounding_box_origin(), r.bounding_box_size()); + i_slint_core::graphics::euclid::rect(orig.x, orig.y, size.width as i32, size.height as i32) + } + #[cfg(feature = "experimental")] #[no_mangle] pub unsafe extern "C" fn slint_software_renderer_set_rendering_rotation(