Skip to content

Commit 78e9b08

Browse files
committed
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.
1 parent ab358b6 commit 78e9b08

File tree

4 files changed

+201
-36
lines changed

4 files changed

+201
-36
lines changed

api/cpp/esp-idf/slint/include/slint-esp.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,19 @@
1515
* - `size` is the size of the screen
1616
* - `panel` is a handle to the display.
1717
* - `touch` is a handle to the touch screen, if the device has a touch screen
18-
* - `buffer1` is a buffer of at least the size of the frame in which the slint scene will be drawn.
19-
* Slint will take care to flush it to the screen
18+
* - `buffer1`, if specified, is a buffer of at least the size of the frame in which the slint scene
19+
* will be drawn. Slint will take care to flush it to the screen
2020
* - `buffer2`, if specified, is a second buffer to be used with double buffering,
2121
* both buffer1 and buffer2 should then be obtained with `esp_lcd_rgb_panel_get_frame_buffer`
2222
* - `rotation` applies a transformation while rendering in the buffer
23+
*
24+
* If no buffer1 is specified, Slint assumes that no direct framebuffers are accessible and instead
25+
* will render line-by-line, by allocating a line buffer with MALLOC_CAP_INTERNAL, and flush it to
26+
* the screen with esp_lcd_panel_draw_bitmap.
2327
*/
2428
void slint_esp_init(slint::PhysicalSize size, esp_lcd_panel_handle_t panel,
2529
std::optional<esp_lcd_touch_handle_t> touch,
26-
std::span<slint::platform::Rgb565Pixel> buffer1,
30+
std::optional<std::span<slint::platform::Rgb565Pixel>> buffer1 = {},
2731
std::optional<std::span<slint::platform::Rgb565Pixel>> buffer2 = {}
2832
#ifdef SLINT_FEATURE_EXPERIMENTAL
2933
,

api/cpp/esp-idf/slint/src/slint-esp.cpp

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ struct EspPlatform : public slint::platform::Platform
2222
{
2323
EspPlatform(slint::PhysicalSize size, esp_lcd_panel_handle_t panel,
2424
std::optional<esp_lcd_touch_handle_t> touch,
25-
std::span<slint::platform::Rgb565Pixel> buffer1,
25+
std::optional<std::span<slint::platform::Rgb565Pixel>> buffer1,
2626
std::optional<std::span<slint::platform::Rgb565Pixel>> buffer2 = {}
2727
#ifdef SLINT_FEATURE_EXPERIMENTAL
2828
,
@@ -52,7 +52,7 @@ struct EspPlatform : public slint::platform::Platform
5252
slint::PhysicalSize size;
5353
esp_lcd_panel_handle_t panel_handle;
5454
std::optional<esp_lcd_touch_handle_t> touch_handle;
55-
std::span<slint::platform::Rgb565Pixel> buffer1;
55+
std::optional<std::span<slint::platform::Rgb565Pixel>> buffer1;
5656
std::optional<std::span<slint::platform::Rgb565Pixel>> buffer2;
5757
#ifdef SLINT_FEATURE_EXPERIMENTAL
5858
slint::platform::SoftwareRenderer::RenderingRotation rotation;
@@ -214,43 +214,71 @@ void EspPlatform::run_event_loop()
214214
}
215215

216216
if (std::exchange(m_window->needs_redraw, false)) {
217-
auto rotated = false
217+
if (buffer1) {
218+
auto buffer1 = *this->buffer1;
219+
auto rotated = false
218220
#ifdef SLINT_FEATURE_EXPERIMENTAL
219-
|| rotation
220-
== slint::platform::SoftwareRenderer::RenderingRotation::Rotate90
221-
|| rotation
222-
== slint::platform::SoftwareRenderer::RenderingRotation::Rotate270
221+
|| rotation
222+
== slint::platform::SoftwareRenderer::RenderingRotation::
223+
Rotate90
224+
|| rotation
225+
== slint::platform::SoftwareRenderer::RenderingRotation::
226+
Rotate270
223227
#endif
224-
;
225-
auto region =
226-
m_window->m_renderer.render(buffer1, rotated ? size.height : size.width);
227-
auto o = region.bounding_box_origin();
228-
auto s = region.bounding_box_size();
229-
if (s.width > 0 && s.height > 0) {
230-
if (buffer2) {
228+
;
229+
auto region = m_window->m_renderer.render(buffer1,
230+
rotated ? size.height : size.width);
231+
auto o = region.bounding_box_origin();
232+
auto s = region.bounding_box_size();
233+
if (s.width > 0 && s.height > 0) {
234+
if (buffer2) {
231235
#if SOC_LCD_RGB_SUPPORTED && ESP_IDF_VERSION_MAJOR >= 5
232-
xSemaphoreGive(sem_gui_ready);
233-
xSemaphoreTake(sem_vsync_end, portMAX_DELAY);
236+
xSemaphoreGive(sem_gui_ready);
237+
xSemaphoreTake(sem_vsync_end, portMAX_DELAY);
234238
#endif
235239

236-
// Assuming that using double buffer means that the buffer comes from the
237-
// driver and we need to pass the exact pointer.
238-
// https://github.com/espressif/esp-idf/blob/53ff7d43dbff642d831a937b066ea0735a6aca24/components/esp_lcd/src/esp_lcd_panel_rgb.c#L681
239-
esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, size.width, size.height,
240-
buffer1.data());
241-
std::swap(buffer1, buffer2.value());
242-
} else {
243-
for (int y = o.y; y < o.y + s.height; y++) {
244-
for (int x = o.x; x < o.x + s.width; x++) {
245-
// Swap endianess to big endian
246-
auto px =
247-
reinterpret_cast<uint16_t *>(&buffer1[y * size.width + x]);
248-
*px = (*px << 8) | (*px >> 8);
240+
// Assuming that using double buffer means that the buffer comes from
241+
// the driver and we need to pass the exact pointer.
242+
// https://github.com/espressif/esp-idf/blob/53ff7d43dbff642d831a937b066ea0735a6aca24/components/esp_lcd/src/esp_lcd_panel_rgb.c#L681
243+
esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, size.width, size.height,
244+
buffer1.data());
245+
246+
std::swap(buffer1, buffer2.value());
247+
} else {
248+
for (int y = o.y; y < o.y + s.height; y++) {
249+
for (int x = o.x; x < o.x + s.width; x++) {
250+
// Swap endianess to big endian
251+
auto px = reinterpret_cast<uint16_t *>(
252+
&buffer1[y * size.width + x]);
253+
*px = (*px << 8) | (*px >> 8);
254+
}
255+
esp_lcd_panel_draw_bitmap(panel_handle, o.x, y, o.x + s.width,
256+
y + 1,
257+
buffer1.data() + y * size.width + o.x);
249258
}
250-
esp_lcd_panel_draw_bitmap(panel_handle, o.x, y, o.x + s.width, y + 1,
251-
buffer1.data() + y * size.width + o.x);
252259
}
253260
}
261+
} else {
262+
slint::platform::Rgb565Pixel *lb =
263+
(slint::platform::Rgb565Pixel *)heap_caps_malloc(
264+
size.width * 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
265+
266+
m_window->m_renderer.render_by_line([this,
267+
&lb](std::size_t line_y,
268+
std::size_t line_start,
269+
std::size_t line_end,
270+
void (*render_fn)(
271+
void *,
272+
std::span<slint::platform::
273+
Rgb565Pixel>
274+
&),
275+
void *render_fn_data) {
276+
std::span<slint::platform::Rgb565Pixel> view { lb, line_end - line_start };
277+
render_fn(render_fn_data, view);
278+
esp_lcd_panel_draw_bitmap(panel_handle, line_start, line_y, line_end,
279+
line_y + 1, lb);
280+
});
281+
free(lb);
254282
}
255283
}
256284

@@ -292,7 +320,7 @@ TaskHandle_t EspPlatform::task = {};
292320

293321
void slint_esp_init(slint::PhysicalSize size, esp_lcd_panel_handle_t panel,
294322
std::optional<esp_lcd_touch_handle_t> touch,
295-
std::span<slint::platform::Rgb565Pixel> buffer1,
323+
std::optional<std::span<slint::platform::Rgb565Pixel>> buffer1,
296324
std::optional<std::span<slint::platform::Rgb565Pixel>> buffer2
297325
#ifdef SLINT_FEATURE_EXPERIMENTAL
298326
,

api/cpp/include/slint-platform.h

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "slint.h"
77

88
#include <cassert>
9+
#include <cstdint>
910
#include <utility>
1011

1112
struct xcb_connection_t;
@@ -630,6 +631,55 @@ class SoftwareRenderer : public AbstractRenderer
630631
return PhysicalRegion { r };
631632
}
632633

634+
/// Render the window scene, line by line. The provided Callback will be invoked for each line
635+
/// that needs to rendered.
636+
///
637+
/// The renderer uses a cache internally and will only render the part of the window
638+
/// which are dirty.
639+
///
640+
/// This function returns the physical region that was rendered considering the rotation.
641+
///
642+
/// The callback is invoked with the line number as first parameter, and the start x and end x
643+
/// coordinates of the line as second and third parameter. The implementation must provide a
644+
/// line buffer (as std::span) and invoke the provided fourth function pointer (render_fn) with
645+
/// it, to fill it with pixels. The last parameter of Callback is a private data pointer that
646+
/// must be provided as first argument to render_fn.
647+
/// After the line buffer is filled with pixels, your implementation is free to flush that line
648+
/// to the screen for display.
649+
template<std::invocable<std::size_t, std::size_t, std::size_t,
650+
void (*)(void *, std::span<Rgb565Pixel> &), void *>
651+
Callback>
652+
PhysicalRegion render_by_line(Callback process_line_callback) const
653+
{
654+
auto r = cbindgen_private::slint_software_renderer_render_by_line_rgb565(
655+
inner,
656+
[](void *process_line_callback_ptr, uintptr_t line, uintptr_t line_start,
657+
uintptr_t line_end, void (*render_fn)(const void *, uint16_t *, std::size_t),
658+
const void *render_fn_data) {
659+
struct RenderFnAndData
660+
{
661+
void (*render_fn)(const void *, uint16_t *, std::size_t);
662+
const void *render_fn_data;
663+
};
664+
RenderFnAndData rfad;
665+
rfad.render_fn = render_fn;
666+
rfad.render_fn_data = render_fn_data;
667+
668+
(*reinterpret_cast<Callback *>(process_line_callback_ptr))(
669+
std::size_t(line), std::size_t(line_start), std::size_t(line_end),
670+
[](void *rfad_ptr, std::span<Rgb565Pixel> &line_span) {
671+
RenderFnAndData *rfad =
672+
reinterpret_cast<RenderFnAndData *>(rfad_ptr);
673+
rfad->render_fn(rfad->render_fn_data,
674+
reinterpret_cast<uint16_t *>(line_span.data()),
675+
line_span.size());
676+
},
677+
&rfad);
678+
},
679+
&process_line_callback);
680+
return PhysicalRegion { r };
681+
}
682+
633683
# ifdef SLINT_FEATURE_EXPERIMENTAL
634684
/// This enum describes the rotation that is applied to the buffer when rendering.
635685
/// To be used in set_rendering_rotation()

api/cpp/platform.rs

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,9 @@ mod software_renderer {
351351
use super::*;
352352
type SoftwareRendererOpaque = *const c_void;
353353
use i_slint_core::graphics::{IntRect, Rgb8Pixel};
354-
use i_slint_core::software_renderer::{RepaintBufferType, Rgb565Pixel, SoftwareRenderer};
354+
use i_slint_core::software_renderer::{
355+
LineBufferProvider, RepaintBufferType, Rgb565Pixel, SoftwareRenderer,
356+
};
355357

356358
#[no_mangle]
357359
pub unsafe extern "C" fn slint_software_renderer_new(
@@ -400,6 +402,87 @@ mod software_renderer {
400402
i_slint_core::graphics::euclid::rect(orig.x, orig.y, size.width as i32, size.height as i32)
401403
}
402404

405+
#[no_mangle]
406+
pub unsafe extern "C" fn slint_software_renderer_render_by_line_rgb565(
407+
r: SoftwareRendererOpaque,
408+
process_line_fn: extern "C" fn(
409+
*mut core::ffi::c_void,
410+
usize,
411+
usize,
412+
usize,
413+
extern "C" fn(*const core::ffi::c_void, *mut u16, usize),
414+
*const core::ffi::c_void,
415+
),
416+
user_data: *mut core::ffi::c_void,
417+
) -> IntRect {
418+
struct Rgb565Processor {
419+
process_line_fn: extern "C" fn(
420+
*mut core::ffi::c_void,
421+
usize,
422+
usize,
423+
usize,
424+
extern "C" fn(*const core::ffi::c_void, *mut u16, usize),
425+
*const core::ffi::c_void,
426+
),
427+
user_data: *mut core::ffi::c_void,
428+
}
429+
430+
impl LineBufferProvider for Rgb565Processor {
431+
type TargetPixel = Rgb565Pixel;
432+
fn process_line(
433+
&mut self,
434+
line: usize,
435+
range: core::ops::Range<usize>,
436+
render_fn: impl FnOnce(&mut [Rgb565Pixel]),
437+
) {
438+
self.cpp_process_line(line, range, render_fn);
439+
}
440+
}
441+
442+
impl Rgb565Processor {
443+
fn cpp_process_line<RenderFn: FnOnce(&mut [Rgb565Pixel])>(
444+
&mut self,
445+
line: usize,
446+
range: core::ops::Range<usize>,
447+
render_fn: RenderFn,
448+
) {
449+
let mut render_fn = Some(render_fn);
450+
let render_fn_ptr =
451+
&mut render_fn as *mut Option<RenderFn> as *const core::ffi::c_void;
452+
453+
extern "C" fn cpp_render_line_callback<RenderFn: FnOnce(&mut [Rgb565Pixel])>(
454+
render_fn_ptr: *const core::ffi::c_void,
455+
line_start: *mut u16,
456+
len: usize,
457+
) {
458+
let line_slice = unsafe {
459+
core::slice::from_raw_parts_mut(line_start as *mut Rgb565Pixel, len)
460+
};
461+
let render_fn =
462+
unsafe { (*(render_fn_ptr as *mut Option<RenderFn>)).take().unwrap() };
463+
render_fn(line_slice);
464+
}
465+
466+
(self.process_line_fn)(
467+
self.user_data,
468+
line,
469+
range.start,
470+
range.end,
471+
cpp_render_line_callback::<RenderFn>,
472+
render_fn_ptr,
473+
);
474+
}
475+
}
476+
477+
let renderer = &*(r as *const SoftwareRenderer);
478+
479+
let processor = Rgb565Processor { process_line_fn, user_data };
480+
481+
let r = renderer.render_by_line(processor);
482+
let (orig, size) = (r.bounding_box_origin(), r.bounding_box_size());
483+
i_slint_core::graphics::euclid::rect(orig.x, orig.y, size.width as i32, size.height as i32)
484+
}
485+
403486
#[cfg(feature = "experimental")]
404487
#[no_mangle]
405488
pub unsafe extern "C" fn slint_software_renderer_set_rendering_rotation(

0 commit comments

Comments
 (0)