1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
3
4#include <deque>
5#include <mutex>
6#include "slint-esp.h"
7#include "slint-platform.h"
8#include "esp_lcd_panel_ops.h"
9#if __has_include("soc/soc_caps.h")
10# include "soc/soc_caps.h"
11#endif
12#if SOC_LCD_RGB_SUPPORTED && ESP_IDF_VERSION_MAJOR >= 5
13# include "esp_lcd_panel_rgb.h"
14#endif
15#include "esp_log.h"
16
17static const char *TAG = "slint_platform";
18
19using RepaintBufferType = slint::platform::SoftwareRenderer::RepaintBufferType;
20
21struct EspPlatform : public slint::platform::Platform
22{
23 EspPlatform(slint::PhysicalSize size, esp_lcd_panel_handle_t panel,
24 std::optional<esp_lcd_touch_handle_t> touch,
25 std::span<slint::platform::Rgb565Pixel> buffer1,
26 std::optional<std::span<slint::platform::Rgb565Pixel>> buffer2 = {}
27#ifdef SLINT_FEATURE_EXPERIMENTAL
28 ,
29 slint::platform::SoftwareRenderer::RenderingRotation rotation = {}
30#endif
31 )
32 : size(size),
33 panel_handle(panel),
34 touch_handle(touch),
35 buffer1(buffer1),
36 buffer2(buffer2)
37#ifdef SLINT_FEATURE_EXPERIMENTAL
38 ,
39 rotation(rotation)
40#endif
41 {
42 }
43
44 std::unique_ptr<slint::platform::WindowAdapter> create_window_adapter() override;
45
46 std::chrono::milliseconds duration_since_start() override;
47 void run_event_loop() override;
48 void quit_event_loop() override;
49 void run_in_event_loop(Task) override;
50
51private:
52 slint::PhysicalSize size;
53 esp_lcd_panel_handle_t panel_handle;
54 std::optional<esp_lcd_touch_handle_t> touch_handle;
55 std::span<slint::platform::Rgb565Pixel> buffer1;
56 std::optional<std::span<slint::platform::Rgb565Pixel>> buffer2;
57#ifdef SLINT_FEATURE_EXPERIMENTAL
58 slint::platform::SoftwareRenderer::RenderingRotation rotation;
59#endif
60 class EspWindowAdapter *m_window = nullptr;
61
62 // Need to be static because we can't pass user data to the touch interrupt callback
63 static TaskHandle_t task;
64 std::mutex queue_mutex;
65 std::deque<slint::platform::Platform::Task> queue; // protected by queue_mutex
66 bool quit = false; // protected by queue_mutex
67};
68
69class EspWindowAdapter : public slint::platform::WindowAdapter
70{
71public:
72 slint::platform::SoftwareRenderer m_renderer;
73 bool needs_redraw = true;
74 const slint::PhysicalSize m_size;
75
76 explicit EspWindowAdapter(RepaintBufferType buffer_type, slint::PhysicalSize size)
77 : m_renderer(buffer_type), m_size(size)
78 {
79 }
80
81 slint::platform::AbstractRenderer &renderer() override { return m_renderer; }
82
83 slint::PhysicalSize size() override { return m_size; }
84
85 void request_redraw() override { needs_redraw = true; }
86};
87
88std::unique_ptr<slint::platform::WindowAdapter> EspPlatform::create_window_adapter()
89{
90 if (m_window != nullptr) {
91 ESP_LOGI(TAG, "FATAL: create_window_adapter called multiple times");
92 return nullptr;
93 }
94
95 auto buffer_type =
96 buffer2 ? RepaintBufferType::SwappedBuffers : RepaintBufferType::ReusedBuffer;
97 auto window = std::make_unique<EspWindowAdapter>(args&: buffer_type, args&: size);
98 m_window = window.get();
99#ifdef SLINT_FEATURE_EXPERIMENTAL
100 m_window->m_renderer.set_rendering_rotation(rotation);
101#endif
102 return window;
103}
104
105std::chrono::milliseconds EspPlatform::duration_since_start()
106{
107 auto ticks = xTaskGetTickCount();
108 return std::chrono::milliseconds(pdTICKS_TO_MS(ticks));
109}
110
111#if SOC_LCD_RGB_SUPPORTED && ESP_IDF_VERSION_MAJOR >= 5
112static SemaphoreHandle_t sem_vsync_end;
113static SemaphoreHandle_t sem_gui_ready;
114
115extern "C" bool on_vsync_event(esp_lcd_panel_handle_t panel,
116 const esp_lcd_rgb_panel_event_data_t *edata, void *)
117{
118 BaseType_t high_task_awoken = pdFALSE;
119 if (xSemaphoreTakeFromISR(sem_gui_ready, &high_task_awoken) == pdTRUE) {
120 xSemaphoreGiveFromISR(sem_vsync_end, &high_task_awoken);
121 }
122 return high_task_awoken == pdTRUE;
123}
124#endif
125
126void EspPlatform::run_event_loop()
127{
128 task = xTaskGetCurrentTaskHandle();
129
130 esp_lcd_panel_disp_on_off(panel_handle, true);
131
132 if (touch_handle) {
133 esp_lcd_touch_register_interrupt_callback(
134 *touch_handle, [](auto) { vTaskNotifyGiveFromISR(task, nullptr); });
135 }
136#if SOC_LCD_RGB_SUPPORTED && ESP_IDF_VERSION_MAJOR >= 5
137 if (buffer2) {
138 sem_vsync_end = xSemaphoreCreateBinary();
139 sem_gui_ready = xSemaphoreCreateBinary();
140 esp_lcd_rgb_panel_event_callbacks_t cbs = {};
141 cbs.on_vsync = on_vsync_event;
142 esp_lcd_rgb_panel_register_event_callbacks(panel_handle, &cbs, this);
143 }
144#endif
145
146 int last_touch_x = 0;
147 int last_touch_y = 0;
148 bool touch_down = false;
149
150 while (true) {
151 slint::platform::update_timers_and_animations();
152
153 std::optional<slint::platform::Platform::Task> event;
154 {
155 std::unique_lock lock(queue_mutex);
156 if (queue.empty()) {
157 if (quit) {
158 quit = false;
159 break;
160 }
161 } else {
162 event = std::move(queue.front());
163 queue.pop_front();
164 }
165 }
166 if (event) {
167 std::move(*event).run();
168 event.reset();
169 continue;
170 }
171
172 if (m_window) {
173
174 if (touch_handle) {
175 uint16_t touchpad_x[1] = { 0 };
176 uint16_t touchpad_y[1] = { 0 };
177 uint8_t touchpad_cnt = 0;
178
179 /* Read touch controller data */
180 esp_lcd_touch_read_data(*touch_handle);
181
182 /* Get coordinates */
183 bool touchpad_pressed = esp_lcd_touch_get_coordinates(
184 *touch_handle, touchpad_x, touchpad_y, NULL, &touchpad_cnt, 1);
185
186 if (touchpad_pressed && touchpad_cnt > 0) {
187 // ESP_LOGI(TAG, "x: %i, y: %i", touchpad_x[0], touchpad_y[0]);
188 last_touch_x = touchpad_x[0];
189 last_touch_y = touchpad_y[0];
190 m_window->window().dispatch_pointer_move_event(
191 pos: slint::LogicalPosition({ .x: float(last_touch_x), .y: float(last_touch_y) }));
192 if (!touch_down) {
193 m_window->window().dispatch_pointer_press_event(
194 pos: slint::LogicalPosition(
195 { .x: float(last_touch_x), .y: float(last_touch_y) }),
196 button: slint::PointerEventButton::Left);
197 }
198 touch_down = true;
199 } else if (touch_down) {
200 m_window->window().dispatch_pointer_release_event(
201 pos: slint::LogicalPosition({ .x: float(last_touch_x), .y: float(last_touch_y) }),
202 button: slint::PointerEventButton::Left);
203 m_window->window().dispatch_pointer_exit_event();
204 touch_down = false;
205 }
206 }
207
208 if (std::exchange(obj&: m_window->needs_redraw, new_val: false)) {
209 auto rotated = false
210#ifdef SLINT_FEATURE_EXPERIMENTAL
211 || rotation
212 == slint::platform::SoftwareRenderer::RenderingRotation::Rotate90
213 || rotation
214 == slint::platform::SoftwareRenderer::RenderingRotation::Rotate270
215#endif
216 ;
217 auto region =
218 m_window->m_renderer.render(buffer: buffer1, pixel_stride: rotated ? size.height : size.width);
219 auto o = region.bounding_box_origin();
220 auto s = region.bounding_box_size();
221 if (s.width > 0 && s.height > 0) {
222 if (buffer2) {
223#if SOC_LCD_RGB_SUPPORTED && ESP_IDF_VERSION_MAJOR >= 5
224 xSemaphoreGive(sem_gui_ready);
225 xSemaphoreTake(sem_vsync_end, portMAX_DELAY);
226#endif
227
228 // Assuming that using double buffer means that the buffer comes from the
229 // driver and we need to pass the exact pointer.
230 // https://github.com/espressif/esp-idf/blob/53ff7d43dbff642d831a937b066ea0735a6aca24/components/esp_lcd/src/esp_lcd_panel_rgb.c#L681
231 esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, size.width, size.height,
232 buffer1.data());
233 std::swap(a&: buffer1, b&: buffer2.value());
234 } else {
235 for (int y = o.y; y < o.y + s.height; y++) {
236 for (int x = o.x; x < o.x + s.width; x++) {
237 // Swap endianess to big endian
238 auto px =
239 reinterpret_cast<uint16_t *>(&buffer1[y * size.width + x]);
240 *px = (*px << 8) | (*px >> 8);
241 }
242 esp_lcd_panel_draw_bitmap(panel_handle, o.x, y, o.x + s.width, y + 1,
243 buffer1.data() + y * size.width + o.x);
244 }
245 }
246 }
247 }
248
249 if (m_window->window().has_active_animations()) {
250 continue;
251 }
252 }
253
254 TickType_t ticks_to_wait;
255 if (auto wait_time = slint::platform::duration_until_next_timer_update()) {
256 ticks_to_wait = pdMS_TO_TICKS(wait_time->count());
257 } else {
258 ticks_to_wait = portMAX_DELAY;
259 }
260
261 ulTaskNotifyTake(/*reset to zero*/ pdTRUE, ticks_to_wait);
262 }
263
264 vTaskDelete(NULL);
265}
266
267void EspPlatform::quit_event_loop()
268{
269 {
270 const std::unique_lock lock(queue_mutex);
271 quit = true;
272 }
273 vTaskNotifyGiveFromISR(task, nullptr);
274}
275
276void EspPlatform::run_in_event_loop(slint::platform::Platform::Task event)
277{
278 {
279 const std::unique_lock lock(queue_mutex);
280 queue.push_back(x: std::move(event));
281 }
282 vTaskNotifyGiveFromISR(task, nullptr);
283}
284
285TaskHandle_t EspPlatform::task = {};
286
287void slint_esp_init(slint::PhysicalSize size, esp_lcd_panel_handle_t panel,
288 std::optional<esp_lcd_touch_handle_t> touch,
289 std::span<slint::platform::Rgb565Pixel> buffer1,
290 std::optional<std::span<slint::platform::Rgb565Pixel>> buffer2
291#ifdef SLINT_FEATURE_EXPERIMENTAL
292 ,
293 slint::platform::SoftwareRenderer::RenderingRotation rotation
294#endif
295)
296{
297 slint::platform::set_platform(std::make_unique<EspPlatform>(size, panel, touch, buffer1, buffer2
298#ifdef SLINT_FEATURE_EXPERIMENTAL
299 ,
300 rotation
301#endif
302 ));
303}
304

source code of slint/api/cpp/esp-idf/slint/src/slint-esp.cpp