Skip to content

Commit

Permalink
esp-idf: Add support for line-by-line rendering
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
tronical committed Mar 28, 2024
1 parent d5c8add commit 3a39d7a
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 36 deletions.
10 changes: 7 additions & 3 deletions api/cpp/esp-idf/slint/include/slint-esp.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<esp_lcd_touch_handle_t> touch,
std::span<slint::platform::Rgb565Pixel> buffer1,
std::optional<std::span<slint::platform::Rgb565Pixel>> buffer1 = {},
std::optional<std::span<slint::platform::Rgb565Pixel>> buffer2 = {}
#ifdef SLINT_FEATURE_EXPERIMENTAL
,
Expand Down
92 changes: 60 additions & 32 deletions api/cpp/esp-idf/slint/src/slint-esp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ struct EspPlatform : public slint::platform::Platform
{
EspPlatform(slint::PhysicalSize size, esp_lcd_panel_handle_t panel,
std::optional<esp_lcd_touch_handle_t> touch,
std::span<slint::platform::Rgb565Pixel> buffer1,
std::optional<std::span<slint::platform::Rgb565Pixel>> buffer1,
std::optional<std::span<slint::platform::Rgb565Pixel>> buffer2 = {}
#ifdef SLINT_FEATURE_EXPERIMENTAL
,
Expand Down Expand Up @@ -52,7 +52,7 @@ struct EspPlatform : public slint::platform::Platform
slint::PhysicalSize size;
esp_lcd_panel_handle_t panel_handle;
std::optional<esp_lcd_touch_handle_t> touch_handle;
std::span<slint::platform::Rgb565Pixel> buffer1;
std::optional<std::span<slint::platform::Rgb565Pixel>> buffer1;
std::optional<std::span<slint::platform::Rgb565Pixel>> buffer2;
#ifdef SLINT_FEATURE_EXPERIMENTAL
slint::platform::SoftwareRenderer::RenderingRotation rotation;
Expand Down Expand Up @@ -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<uint16_t *>(&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<uint16_t *>(
&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<slint::platform::
Rgb565Pixel>
&),
void *render_fn_data) {
std::span<slint::platform::Rgb565Pixel> 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);
}
}

Expand Down Expand Up @@ -292,7 +320,7 @@ TaskHandle_t EspPlatform::task = {};

void slint_esp_init(slint::PhysicalSize size, esp_lcd_panel_handle_t panel,
std::optional<esp_lcd_touch_handle_t> touch,
std::span<slint::platform::Rgb565Pixel> buffer1,
std::optional<std::span<slint::platform::Rgb565Pixel>> buffer1,
std::optional<std::span<slint::platform::Rgb565Pixel>> buffer2
#ifdef SLINT_FEATURE_EXPERIMENTAL
,
Expand Down
50 changes: 50 additions & 0 deletions api/cpp/include/slint-platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "slint.h"

#include <cassert>
#include <cstdint>
#include <utility>

struct xcb_connection_t;
Expand Down Expand Up @@ -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<std::invocable<std::size_t, std::size_t, std::size_t,
void (*)(void *, std::span<Rgb565Pixel> &), 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<Callback *>(process_line_callback_ptr))(
std::size_t(line), std::size_t(line_start), std::size_t(line_end),
[](void *rfad_ptr, std::span<Rgb565Pixel> &line_span) {
RenderFnAndData *rfad =
reinterpret_cast<RenderFnAndData *>(rfad_ptr);
rfad->render_fn(rfad->render_fn_data,
reinterpret_cast<uint16_t *>(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()
Expand Down
85 changes: 84 additions & 1 deletion api/cpp/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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<usize>,
render_fn: impl FnOnce(&mut [Rgb565Pixel]),
) {
self.cpp_process_line(line, range, render_fn);
}
}

impl Rgb565Processor {
fn cpp_process_line<RenderFn: FnOnce(&mut [Rgb565Pixel])>(
&mut self,
line: usize,
range: core::ops::Range<usize>,
render_fn: RenderFn,
) {
let mut render_fn = Some(render_fn);
let render_fn_ptr =
&mut render_fn as *mut Option<RenderFn> as *const core::ffi::c_void;

extern "C" fn cpp_render_line_callback<RenderFn: FnOnce(&mut [Rgb565Pixel])>(
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<RenderFn>)).take().unwrap() };
render_fn(line_slice);
}

(self.process_line_fn)(
self.user_data,
line,
range.start,
range.end,
cpp_render_line_callback::<RenderFn>,
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(
Expand Down

0 comments on commit 3a39d7a

Please sign in to comment.