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