1 /**
2 * @file lv_sdl_window.h
3 *
4 */
5
6 /*********************
7 * INCLUDES
8 *********************/
9 #include "lv_sdl_window.h"
10 #if LV_USE_SDL
11 #include <stdbool.h>
12 #include "../../core/lv_refr.h"
13 #include "../../stdlib/lv_string.h"
14 #include "../../core/lv_global.h"
15 #include "../../display/lv_display_private.h"
16 #include "../../lv_init.h"
17 #include "../../draw/lv_draw_buf.h"
18
19 /* for aligned_alloc */
20 #ifndef __USE_ISOC11
21 #define __USE_ISOC11
22 #endif
23 #include <stdlib.h>
24
25 #define SDL_MAIN_HANDLED /*To fix SDL's "undefined reference to WinMain" issue*/
26 #include "lv_sdl_private.h"
27
28 /*********************
29 * DEFINES
30 *********************/
31 #define lv_deinit_in_progress LV_GLOBAL_DEFAULT()->deinit_in_progress
32
33 /**********************
34 * TYPEDEFS
35 **********************/
36 typedef struct {
37 SDL_Window * window;
38 SDL_Renderer * renderer;
39 #if LV_USE_DRAW_SDL == 0
40 SDL_Texture * texture;
41 uint8_t * fb1;
42 uint8_t * fb2;
43 uint8_t * fb_act;
44 uint8_t * buf1;
45 uint8_t * buf2;
46 uint8_t * rotated_buf;
47 size_t rotated_buf_size;
48 #endif
49 float zoom;
50 uint8_t ignore_size_chg;
51 } lv_sdl_window_t;
52
53 /**********************
54 * STATIC PROTOTYPES
55 **********************/
56 static inline int sdl_render_mode(void);
57 static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * color_p);
58 static void window_create(lv_display_t * disp);
59 static void window_update(lv_display_t * disp);
60 #if LV_USE_DRAW_SDL == 0
61 static void texture_resize(lv_display_t * disp);
62 static void * sdl_draw_buf_realloc_aligned(void * ptr, size_t new_size);
63 static void sdl_draw_buf_free(void * ptr);
64 #endif
65 static void sdl_event_handler(lv_timer_t * t);
66 static void release_disp_cb(lv_event_t * e);
67 static void res_chg_event_cb(lv_event_t * e);
68
69 /**********************
70 * STATIC VARIABLES
71 **********************/
72 static bool inited = false;
73 static lv_timer_t * event_handler_timer;
74
75 /**********************
76 * MACROS
77 **********************/
78
79 /**********************
80 * GLOBAL FUNCTIONS
81 **********************/
82
lv_sdl_window_create(int32_t hor_res,int32_t ver_res)83 lv_display_t * lv_sdl_window_create(int32_t hor_res, int32_t ver_res)
84 {
85 if(!inited) {
86 SDL_Init(SDL_INIT_VIDEO);
87 SDL_StartTextInput();
88 event_handler_timer = lv_timer_create(sdl_event_handler, 5, NULL);
89 lv_tick_set_cb(SDL_GetTicks);
90 lv_delay_set_cb(SDL_Delay);
91
92 inited = true;
93 }
94
95 lv_sdl_window_t * dsc = lv_malloc_zeroed(sizeof(lv_sdl_window_t));
96 LV_ASSERT_MALLOC(dsc);
97 if(dsc == NULL) return NULL;
98
99 lv_display_t * disp = lv_display_create(hor_res, ver_res);
100 if(disp == NULL) {
101 lv_free(dsc);
102 return NULL;
103 }
104 lv_display_add_event_cb(disp, release_disp_cb, LV_EVENT_DELETE, disp);
105 lv_display_set_driver_data(disp, dsc);
106 window_create(disp);
107
108 lv_display_set_flush_cb(disp, flush_cb);
109
110 #if LV_USE_DRAW_SDL == 0
111 if(sdl_render_mode() == LV_DISPLAY_RENDER_MODE_PARTIAL) {
112 uint32_t palette_size = LV_COLOR_INDEXED_PALETTE_SIZE(lv_display_get_color_format(disp)) * 4;
113 uint32_t buffer_size_bytes = 32 * 1024 + palette_size;
114 dsc->buf1 = sdl_draw_buf_realloc_aligned(NULL, buffer_size_bytes);
115 #if LV_SDL_BUF_COUNT == 2
116 dsc->buf2 = sdl_draw_buf_realloc_aligned(NULL, buffer_size_bytes);
117 #endif
118 lv_display_set_buffers(disp, dsc->buf1, dsc->buf2, buffer_size_bytes, LV_DISPLAY_RENDER_MODE_PARTIAL);
119 }
120 /*LV_DISPLAY_RENDER_MODE_DIRECT or FULL */
121 else {
122 uint32_t stride = lv_draw_buf_width_to_stride(disp->hor_res,
123 lv_display_get_color_format(disp));
124 lv_display_set_buffers(disp, dsc->fb1, dsc->fb2, stride * disp->ver_res,
125 LV_SDL_RENDER_MODE);
126 }
127 #else /*LV_USE_DRAW_SDL == 1*/
128 /*It will render directly to default Texture, so the buffer is not used, so just set something*/
129 static lv_draw_buf_t draw_buf;
130 static uint8_t dummy_buf; /*It won't be used as it will render to the SDL textures directly*/
131 lv_draw_buf_init(&draw_buf, 4096, 4096, LV_COLOR_FORMAT_ARGB8888, 4096 * 4, &dummy_buf, 4096 * 4096 * 4);
132
133 lv_display_set_draw_buffers(disp, &draw_buf, NULL);
134 lv_display_set_render_mode(disp, LV_DISPLAY_RENDER_MODE_DIRECT);
135 #endif /*LV_USE_DRAW_SDL == 0*/
136 lv_display_add_event_cb(disp, res_chg_event_cb, LV_EVENT_RESOLUTION_CHANGED, NULL);
137 /*Process the initial events*/
138 sdl_event_handler(NULL);
139
140 return disp;
141 }
142
lv_sdl_window_set_resizeable(lv_display_t * disp,bool value)143 void lv_sdl_window_set_resizeable(lv_display_t * disp, bool value)
144 {
145 lv_sdl_window_t * dsc = lv_display_get_driver_data(disp);
146 SDL_SetWindowResizable(dsc->window, value);
147 }
148
lv_sdl_window_set_zoom(lv_display_t * disp,float zoom)149 void lv_sdl_window_set_zoom(lv_display_t * disp, float zoom)
150 {
151 lv_sdl_window_t * dsc = lv_display_get_driver_data(disp);
152 dsc->zoom = zoom;
153 lv_display_send_event(disp, LV_EVENT_RESOLUTION_CHANGED, NULL);
154 lv_refr_now(disp);
155 }
156
lv_sdl_window_get_zoom(lv_display_t * disp)157 float lv_sdl_window_get_zoom(lv_display_t * disp)
158 {
159 lv_sdl_window_t * dsc = lv_display_get_driver_data(disp);
160 return dsc->zoom;
161 }
162
lv_sdl_get_disp_from_win_id(uint32_t win_id)163 lv_display_t * lv_sdl_get_disp_from_win_id(uint32_t win_id)
164 {
165 lv_display_t * disp = lv_display_get_next(NULL);
166 if(win_id == UINT32_MAX) return disp;
167
168 while(disp) {
169 lv_sdl_window_t * dsc = lv_display_get_driver_data(disp);
170 if(dsc != NULL && SDL_GetWindowID(dsc->window) == win_id) {
171 return disp;
172 }
173 disp = lv_display_get_next(disp);
174 }
175 return NULL;
176 }
177
lv_sdl_window_set_title(lv_display_t * disp,const char * title)178 void lv_sdl_window_set_title(lv_display_t * disp, const char * title)
179 {
180 lv_sdl_window_t * dsc = lv_display_get_driver_data(disp);
181 SDL_SetWindowTitle(dsc->window, title);
182 }
183
lv_sdl_window_get_renderer(lv_display_t * disp)184 void * lv_sdl_window_get_renderer(lv_display_t * disp)
185 {
186 lv_sdl_window_t * dsc = lv_display_get_driver_data(disp);
187 return dsc->renderer;
188 }
189
lv_sdl_quit(void)190 void lv_sdl_quit(void)
191 {
192 if(inited) {
193 SDL_Quit();
194 lv_timer_delete(event_handler_timer);
195 event_handler_timer = NULL;
196 inited = false;
197 }
198 }
199
200 /**********************
201 * STATIC FUNCTIONS
202 **********************/
203
sdl_render_mode(void)204 static inline int sdl_render_mode(void)
205 {
206 return LV_SDL_RENDER_MODE;
207 }
208
flush_cb(lv_display_t * disp,const lv_area_t * area,uint8_t * px_map)209 static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map)
210 {
211 #if LV_USE_DRAW_SDL == 0
212 lv_sdl_window_t * dsc = lv_display_get_driver_data(disp);
213 lv_color_format_t cf = lv_display_get_color_format(disp);
214 uint32_t * argb_px_map = NULL;
215
216 if(sdl_render_mode() == LV_DISPLAY_RENDER_MODE_PARTIAL) {
217 /*Update values in a special OLED I1 --> ARGB8888 case
218 We render everything in I1, but display it in ARGB8888*/
219 if(cf == LV_COLOR_FORMAT_I1) {
220 /*I1 uses 1 bit wide pixels, ARGB8888 uses 4 byte wide pixels*/
221 cf = LV_COLOR_FORMAT_ARGB8888;
222 uint32_t width = lv_area_get_width(area);
223 uint32_t height = lv_area_get_height(area);
224 uint32_t argb_px_map_size = width * height * 4;
225 argb_px_map = malloc(argb_px_map_size);
226 if(argb_px_map == NULL) {
227 LV_LOG_ERROR("malloc failed");
228 lv_display_flush_ready(disp);
229 return;
230 }
231 /* skip the palette */
232 px_map += LV_COLOR_INDEXED_PALETTE_SIZE(LV_COLOR_FORMAT_I1) * 4;
233 lv_draw_sw_i1_to_argb8888(px_map, argb_px_map, width, height, width / 8, width * 4, 0xFF000000u, 0xFFFFFFFFu);
234 px_map = (uint8_t *)argb_px_map;
235 }
236
237 lv_area_t rotated_area = *area;
238 lv_display_rotate_area(disp, &rotated_area);
239
240 int32_t px_map_w = lv_area_get_width(area);
241 int32_t px_map_h = lv_area_get_height(area);
242 uint32_t px_map_stride = lv_draw_buf_width_to_stride(lv_area_get_width(area), cf);
243 uint32_t px_size = lv_color_format_get_size(cf);
244
245 int32_t fb_stride = lv_draw_buf_width_to_stride(disp->hor_res, cf);
246 uint8_t * fb_start = dsc->fb_act;
247 fb_start += rotated_area.y1 * fb_stride + rotated_area.x1 * px_size;
248 lv_display_rotation_t rotation = lv_display_get_rotation(disp);
249
250 if(rotation == LV_DISPLAY_ROTATION_0) {
251 uint32_t px_map_line_bytes = lv_area_get_width(area) * px_size;
252
253 int32_t y;
254 for(y = area->y1; y <= area->y2; y++) {
255 lv_memcpy(fb_start, px_map, px_map_line_bytes);
256 px_map += px_map_stride;
257 fb_start += fb_stride;
258 }
259 }
260 else {
261 lv_draw_sw_rotate(px_map, fb_start, px_map_w, px_map_h, px_map_stride, fb_stride, rotation, cf);
262 }
263 }
264
265 if(lv_display_flush_is_last(disp)) {
266 if(sdl_render_mode() != LV_DISPLAY_RENDER_MODE_PARTIAL) {
267 dsc->fb_act = px_map;
268 }
269
270 window_update(disp);
271 }
272 free(argb_px_map);
273 #else
274 LV_UNUSED(area);
275 LV_UNUSED(px_map);
276 if(lv_display_flush_is_last(disp)) {
277 window_update(disp);
278 }
279 #endif /*LV_USE_DRAW_SDL == 0*/
280
281 /*IMPORTANT! It must be called to tell the system the flush is ready*/
282 lv_display_flush_ready(disp);
283 }
284
285 /**
286 * SDL main thread. All SDL related task have to be handled here!
287 * It initializes SDL, handles drawing and the mouse.
288 */
sdl_event_handler(lv_timer_t * t)289 static void sdl_event_handler(lv_timer_t * t)
290 {
291 LV_UNUSED(t);
292
293 /*Refresh handling*/
294 SDL_Event event;
295 while(SDL_PollEvent(&event)) {
296 lv_sdl_mouse_handler(&event);
297 #if LV_SDL_MOUSEWHEEL_MODE == LV_SDL_MOUSEWHEEL_MODE_ENCODER
298 lv_sdl_mousewheel_handler(&event);
299 #endif
300 lv_sdl_keyboard_handler(&event);
301
302 if(event.type == SDL_WINDOWEVENT) {
303 lv_display_t * disp = lv_sdl_get_disp_from_win_id(event.window.windowID);
304 if(disp == NULL) continue;
305 lv_sdl_window_t * dsc = lv_display_get_driver_data(disp);
306 switch(event.window.event) {
307 #if SDL_VERSION_ATLEAST(2, 0, 5)
308 case SDL_WINDOWEVENT_TAKE_FOCUS:
309 #endif
310 case SDL_WINDOWEVENT_EXPOSED:
311 window_update(disp);
312 break;
313 case SDL_WINDOWEVENT_RESIZED:
314 dsc->ignore_size_chg = 1;
315 int32_t hres = (int32_t)((float)(event.window.data1) / dsc->zoom);
316 int32_t vres = (int32_t)((float)(event.window.data2) / dsc->zoom);
317 lv_display_set_resolution(disp, hres, vres);
318 dsc->ignore_size_chg = 0;
319 lv_refr_now(disp);
320 break;
321 case SDL_WINDOWEVENT_CLOSE:
322 lv_display_delete(disp);
323 break;
324 default:
325 break;
326 }
327 }
328 if(event.type == SDL_QUIT) {
329 SDL_Quit();
330 lv_deinit();
331 inited = false;
332 #if LV_SDL_DIRECT_EXIT
333 exit(0);
334 #endif
335 }
336 }
337 }
338
window_create(lv_display_t * disp)339 static void window_create(lv_display_t * disp)
340 {
341 lv_sdl_window_t * dsc = lv_display_get_driver_data(disp);
342 dsc->zoom = 1.0;
343
344 int flag = SDL_WINDOW_RESIZABLE;
345 #if LV_SDL_FULLSCREEN
346 flag |= SDL_WINDOW_FULLSCREEN;
347 #endif
348
349 int32_t hor_res = (int32_t)((float)(disp->hor_res) * dsc->zoom);
350 int32_t ver_res = (int32_t)((float)(disp->ver_res) * dsc->zoom);
351 dsc->window = SDL_CreateWindow("LVGL Simulator",
352 SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
353 hor_res, ver_res, flag); /*last param. SDL_WINDOW_BORDERLESS to hide borders*/
354
355 dsc->renderer = SDL_CreateRenderer(dsc->window, -1,
356 LV_SDL_ACCELERATED ? SDL_RENDERER_ACCELERATED : SDL_RENDERER_SOFTWARE);
357 #if LV_USE_DRAW_SDL == 0
358 texture_resize(disp);
359
360 uint32_t px_size = lv_color_format_get_size(lv_display_get_color_format(disp));
361 lv_memset(dsc->fb1, 0xff, hor_res * ver_res * px_size);
362 #if LV_SDL_BUF_COUNT == 2
363 lv_memset(dsc->fb2, 0xff, hor_res * ver_res * px_size);
364 #endif
365 #endif /*LV_USE_DRAW_SDL == 0*/
366 /*Some platforms (e.g. Emscripten) seem to require setting the size again */
367 SDL_SetWindowSize(dsc->window, hor_res, ver_res);
368 #if LV_USE_DRAW_SDL == 0
369 texture_resize(disp);
370 #endif /*LV_USE_DRAW_SDL == 0*/
371 }
372
window_update(lv_display_t * disp)373 static void window_update(lv_display_t * disp)
374 {
375 lv_sdl_window_t * dsc = lv_display_get_driver_data(disp);
376 #if LV_USE_DRAW_SDL == 0
377 int32_t hor_res = disp->hor_res;
378 lv_color_format_t cf = lv_display_get_color_format(disp);
379 if(cf == LV_COLOR_FORMAT_I1) {
380 cf = LV_COLOR_FORMAT_ARGB8888;
381 }
382 uint32_t stride = lv_draw_buf_width_to_stride(hor_res, cf);
383 SDL_UpdateTexture(dsc->texture, NULL, dsc->fb_act, stride);
384
385 SDL_RenderClear(dsc->renderer);
386
387 /*Update the renderer with the texture containing the rendered image*/
388 SDL_RenderCopy(dsc->renderer, dsc->texture, NULL, NULL);
389 #endif
390 SDL_RenderPresent(dsc->renderer);
391 }
392
393 #if LV_USE_DRAW_SDL == 0
texture_resize(lv_display_t * disp)394 static void texture_resize(lv_display_t * disp)
395 {
396 lv_color_format_t cf = lv_display_get_color_format(disp);
397 /*In some cases SDL stride might be different than LVGL render stride, like in I1 format.
398 SDL still uses ARGB8888 as the color format, but LVGL renders in I1, thus causing a mismatch
399 This ensures correct stride for SDL buffers in this case.*/
400 if(cf == LV_COLOR_FORMAT_I1) {
401 cf = LV_COLOR_FORMAT_ARGB8888;
402 }
403 uint32_t stride = lv_draw_buf_width_to_stride(disp->hor_res, cf);
404 lv_sdl_window_t * dsc = lv_display_get_driver_data(disp);
405
406 dsc->fb1 = sdl_draw_buf_realloc_aligned(dsc->fb1, stride * disp->ver_res);
407 lv_memzero(dsc->fb1, stride * disp->ver_res);
408
409 if(sdl_render_mode() == LV_DISPLAY_RENDER_MODE_PARTIAL) {
410 dsc->fb_act = dsc->fb1;
411 }
412 else {
413 #if LV_SDL_BUF_COUNT == 2
414 dsc->fb2 = sdl_draw_buf_realloc_aligned(dsc->fb2, stride * disp->ver_res);
415 memset(dsc->fb2, 0x00, stride * disp->ver_res);
416 #endif
417 lv_display_set_buffers(disp, dsc->fb1, dsc->fb2, stride * disp->ver_res, LV_SDL_RENDER_MODE);
418 }
419 if(dsc->texture) SDL_DestroyTexture(dsc->texture);
420
421 #if LV_COLOR_DEPTH == 32 || LV_COLOR_DEPTH == 1
422 SDL_PixelFormatEnum px_format =
423 SDL_PIXELFORMAT_RGB888; /*same as SDL_PIXELFORMAT_RGB888, but it's not supported in older versions*/
424 #elif LV_COLOR_DEPTH == 24
425 SDL_PixelFormatEnum px_format = SDL_PIXELFORMAT_BGR24;
426 #elif LV_COLOR_DEPTH == 16
427 SDL_PixelFormatEnum px_format = SDL_PIXELFORMAT_RGB565;
428 #else
429 #error("Unsupported color format")
430 #endif
431
432 dsc->texture = SDL_CreateTexture(dsc->renderer, px_format,
433 SDL_TEXTUREACCESS_STATIC, disp->hor_res, disp->ver_res);
434 SDL_SetTextureBlendMode(dsc->texture, SDL_BLENDMODE_BLEND);
435 }
436
sdl_draw_buf_realloc_aligned(void * ptr,size_t new_size)437 static void * sdl_draw_buf_realloc_aligned(void * ptr, size_t new_size)
438 {
439 if(ptr) {
440 sdl_draw_buf_free(ptr);
441 }
442
443 /* No need copy for drawing buffer */
444
445 #ifndef _WIN32
446 /* Size must be multiple of align, See: https://en.cppreference.com/w/c/memory/aligned_alloc */
447
448 #define BUF_ALIGN (LV_DRAW_BUF_ALIGN < sizeof(void *) ? sizeof(void *) : LV_DRAW_BUF_ALIGN)
449 return aligned_alloc(BUF_ALIGN, LV_ALIGN_UP(new_size, BUF_ALIGN));
450 #else
451 return _aligned_malloc(LV_ALIGN_UP(new_size, LV_DRAW_BUF_ALIGN), LV_DRAW_BUF_ALIGN);
452 #endif /* _WIN32 */
453 }
454
sdl_draw_buf_free(void * ptr)455 static void sdl_draw_buf_free(void * ptr)
456 {
457 #ifndef _WIN32
458 free(ptr);
459 #else
460 _aligned_free(ptr);
461 #endif /* _WIN32 */
462 }
463 #endif
464
res_chg_event_cb(lv_event_t * e)465 static void res_chg_event_cb(lv_event_t * e)
466 {
467 lv_display_t * disp = lv_event_get_current_target(e);
468
469 lv_sdl_window_t * dsc = lv_display_get_driver_data(disp);
470 if(dsc->ignore_size_chg == false) {
471 SDL_SetWindowSize(dsc->window,
472 (int)((float)(disp->hor_res)*dsc->zoom), (int)((float)(disp->ver_res)*dsc->zoom));
473 }
474
475 #if LV_USE_DRAW_SDL == 0
476 texture_resize(disp);
477 #endif
478 }
479
release_disp_cb(lv_event_t * e)480 static void release_disp_cb(lv_event_t * e)
481 {
482 if(lv_deinit_in_progress) {
483 lv_sdl_quit();
484 }
485
486 lv_display_t * disp = (lv_display_t *) lv_event_get_user_data(e);
487
488 lv_sdl_window_t * dsc = lv_display_get_driver_data(disp);
489 #if LV_USE_DRAW_SDL == 0
490 SDL_DestroyTexture(dsc->texture);
491 #endif
492 SDL_DestroyRenderer(dsc->renderer);
493 SDL_DestroyWindow(dsc->window);
494 #if LV_USE_DRAW_SDL == 0
495 if(dsc->fb1) sdl_draw_buf_free(dsc->fb1);
496 if(dsc->fb2) sdl_draw_buf_free(dsc->fb2);
497 if(dsc->buf1) sdl_draw_buf_free(dsc->buf1);
498 if(dsc->buf2) sdl_draw_buf_free(dsc->buf2);
499 #endif
500 lv_free(dsc);
501 lv_display_set_driver_data(disp, NULL);
502 }
503
504 #endif /*LV_USE_SDL*/
505