1 /**
2 * @file lv_refr.c
3 *
4 */
5
6 /*********************
7 * INCLUDES
8 *********************/
9 #include <stddef.h>
10 #include "lv_refr.h"
11 #include "lv_disp.h"
12 #include "../hal/lv_hal_tick.h"
13 #include "../hal/lv_hal_disp.h"
14 #include "../misc/lv_timer.h"
15 #include "../misc/lv_mem.h"
16 #include "../misc/lv_math.h"
17 #include "../misc/lv_gc.h"
18 #include "../draw/lv_draw.h"
19 #include "../font/lv_font_fmt_txt.h"
20 #include "../extra/others/snapshot/lv_snapshot.h"
21
22 #if LV_USE_PERF_MONITOR || LV_USE_MEM_MONITOR
23 #include "../widgets/lv_label.h"
24 #endif
25
26 /*********************
27 * DEFINES
28 *********************/
29
30 /**********************
31 * TYPEDEFS
32 **********************/
33 typedef struct {
34 uint32_t perf_last_time;
35 uint32_t elaps_sum;
36 uint32_t frame_cnt;
37 uint32_t fps_sum_cnt;
38 uint32_t fps_sum_all;
39 #if LV_USE_LABEL
40 lv_obj_t * perf_label;
41 #endif
42 } perf_monitor_t;
43
44 typedef struct {
45 uint32_t mem_last_time;
46 #if LV_USE_LABEL
47 lv_obj_t * mem_label;
48 #endif
49 } mem_monitor_t;
50
51 /**********************
52 * STATIC PROTOTYPES
53 **********************/
54 static void lv_refr_join_area(void);
55 static void refr_invalid_areas(void);
56 static void refr_sync_areas(void);
57 static void refr_area(const lv_area_t * area_p);
58 static void refr_area_part(lv_draw_ctx_t * draw_ctx);
59 static lv_obj_t * lv_refr_get_top_obj(const lv_area_t * area_p, lv_obj_t * obj);
60 static void refr_obj_and_children(lv_draw_ctx_t * draw_ctx, lv_obj_t * top_obj);
61 static void refr_obj(lv_draw_ctx_t * draw_ctx, lv_obj_t * obj);
62 static uint32_t get_max_row(lv_disp_t * disp, lv_coord_t area_w, lv_coord_t area_h);
63 static void draw_buf_flush(lv_disp_t * disp);
64 static void call_flush_cb(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_p);
65
66 #if LV_USE_PERF_MONITOR
67 static void perf_monitor_init(perf_monitor_t * perf_monitor);
68 #endif
69 #if LV_USE_MEM_MONITOR
70 static void mem_monitor_init(mem_monitor_t * mem_monitor);
71 #endif
72
73 /**********************
74 * STATIC VARIABLES
75 **********************/
76 static uint32_t px_num;
77 static lv_disp_t * disp_refr; /*Display being refreshed*/
78
79 #if LV_USE_PERF_MONITOR
80 static perf_monitor_t perf_monitor;
81 #endif
82
83 #if LV_USE_MEM_MONITOR
84 static mem_monitor_t mem_monitor;
85 #endif
86
87 /**********************
88 * MACROS
89 **********************/
90 #if LV_LOG_TRACE_DISP_REFR
91 #define REFR_TRACE(...) LV_LOG_TRACE(__VA_ARGS__)
92 #else
93 #define REFR_TRACE(...)
94 #endif
95
96 /**********************
97 * GLOBAL FUNCTIONS
98 **********************/
99
100 /**
101 * Initialize the screen refresh subsystem
102 */
_lv_refr_init(void)103 void _lv_refr_init(void)
104 {
105 #if LV_USE_PERF_MONITOR
106 perf_monitor_init(&perf_monitor);
107 #endif
108 #if LV_USE_MEM_MONITOR
109 mem_monitor_init(&mem_monitor);
110 #endif
111 }
112
lv_refr_now(lv_disp_t * disp)113 void lv_refr_now(lv_disp_t * disp)
114 {
115 lv_anim_refr_now();
116
117 if(disp) {
118 if(disp->refr_timer) _lv_disp_refr_timer(disp->refr_timer);
119 }
120 else {
121 lv_disp_t * d;
122 d = lv_disp_get_next(NULL);
123 while(d) {
124 if(d->refr_timer) _lv_disp_refr_timer(d->refr_timer);
125 d = lv_disp_get_next(d);
126 }
127 }
128 }
129
lv_obj_redraw(lv_draw_ctx_t * draw_ctx,lv_obj_t * obj)130 void lv_obj_redraw(lv_draw_ctx_t * draw_ctx, lv_obj_t * obj)
131 {
132 const lv_area_t * clip_area_ori = draw_ctx->clip_area;
133 lv_area_t clip_coords_for_obj;
134
135 /*Truncate the clip area to `obj size + ext size` area*/
136 lv_area_t obj_coords_ext;
137 lv_obj_get_coords(obj, &obj_coords_ext);
138 lv_coord_t ext_draw_size = _lv_obj_get_ext_draw_size(obj);
139 lv_area_increase(&obj_coords_ext, ext_draw_size, ext_draw_size);
140 bool com_clip_res = _lv_area_intersect(&clip_coords_for_obj, clip_area_ori, &obj_coords_ext);
141 /*If the object is visible on the current clip area OR has overflow visible draw it.
142 *With overflow visible drawing should happen to apply the masks which might affect children */
143 bool should_draw = com_clip_res || lv_obj_has_flag(obj, LV_OBJ_FLAG_OVERFLOW_VISIBLE);
144 if(should_draw) {
145 draw_ctx->clip_area = &clip_coords_for_obj;
146
147 lv_event_send(obj, LV_EVENT_DRAW_MAIN_BEGIN, draw_ctx);
148 lv_event_send(obj, LV_EVENT_DRAW_MAIN, draw_ctx);
149 lv_event_send(obj, LV_EVENT_DRAW_MAIN_END, draw_ctx);
150 #if LV_USE_REFR_DEBUG
151 lv_color_t debug_color = lv_color_make(lv_rand(0, 0xFF), lv_rand(0, 0xFF), lv_rand(0, 0xFF));
152 lv_draw_rect_dsc_t draw_dsc;
153 lv_draw_rect_dsc_init(&draw_dsc);
154 draw_dsc.bg_color.full = debug_color.full;
155 draw_dsc.bg_opa = LV_OPA_20;
156 draw_dsc.border_width = 1;
157 draw_dsc.border_opa = LV_OPA_30;
158 draw_dsc.border_color = debug_color;
159 lv_draw_rect(draw_ctx, &draw_dsc, &obj_coords_ext);
160 #endif
161 }
162
163 /*With overflow visible keep the previous clip area to let the children visible out of this object too
164 *With not overflow visible limit the clip are to the object's coordinates to clip the children*/
165 lv_area_t clip_coords_for_children;
166 bool refr_children = true;
167 if(lv_obj_has_flag(obj, LV_OBJ_FLAG_OVERFLOW_VISIBLE)) {
168 clip_coords_for_children = *clip_area_ori;
169 }
170 else {
171 if(!_lv_area_intersect(&clip_coords_for_children, clip_area_ori, &obj->coords)) {
172 refr_children = false;
173 }
174 }
175
176 if(refr_children) {
177 draw_ctx->clip_area = &clip_coords_for_children;
178 uint32_t i;
179 uint32_t child_cnt = lv_obj_get_child_cnt(obj);
180 for(i = 0; i < child_cnt; i++) {
181 lv_obj_t * child = obj->spec_attr->children[i];
182 refr_obj(draw_ctx, child);
183 }
184 }
185
186 /*If the object was visible on the clip area call the post draw events too*/
187 if(should_draw) {
188 draw_ctx->clip_area = &clip_coords_for_obj;
189
190 /*If all the children are redrawn make 'post draw' draw*/
191 lv_event_send(obj, LV_EVENT_DRAW_POST_BEGIN, draw_ctx);
192 lv_event_send(obj, LV_EVENT_DRAW_POST, draw_ctx);
193 lv_event_send(obj, LV_EVENT_DRAW_POST_END, draw_ctx);
194 }
195
196 draw_ctx->clip_area = clip_area_ori;
197 }
198
199 /**
200 * Invalidate an area on display to redraw it
201 * @param area_p pointer to area which should be invalidated (NULL: delete the invalidated areas)
202 * @param disp pointer to display where the area should be invalidated (NULL can be used if there is
203 * only one display)
204 */
_lv_inv_area(lv_disp_t * disp,const lv_area_t * area_p)205 void _lv_inv_area(lv_disp_t * disp, const lv_area_t * area_p)
206 {
207 if(!disp) disp = lv_disp_get_default();
208 if(!disp) return;
209 if(!lv_disp_is_invalidation_enabled(disp)) return;
210
211 if(disp->rendering_in_progress) {
212 LV_LOG_ERROR("detected modifying dirty areas in render");
213 return;
214 }
215
216 /*Clear the invalidate buffer if the parameter is NULL*/
217 if(area_p == NULL) {
218 disp->inv_p = 0;
219 return;
220 }
221
222 lv_area_t scr_area;
223 scr_area.x1 = 0;
224 scr_area.y1 = 0;
225 scr_area.x2 = lv_disp_get_hor_res(disp) - 1;
226 scr_area.y2 = lv_disp_get_ver_res(disp) - 1;
227
228 lv_area_t com_area;
229 bool suc;
230
231 suc = _lv_area_intersect(&com_area, area_p, &scr_area);
232 if(suc == false) return; /*Out of the screen*/
233
234 /*If there were at least 1 invalid area in full refresh mode, redraw the whole screen*/
235 if(disp->driver->full_refresh) {
236 disp->inv_areas[0] = scr_area;
237 disp->inv_p = 1;
238 if(disp->refr_timer) lv_timer_resume(disp->refr_timer);
239 return;
240 }
241
242 if(disp->driver->rounder_cb) disp->driver->rounder_cb(disp->driver, &com_area);
243
244 /*Save only if this area is not in one of the saved areas*/
245 uint16_t i;
246 for(i = 0; i < disp->inv_p; i++) {
247 if(_lv_area_is_in(&com_area, &disp->inv_areas[i], 0) != false) return;
248 }
249
250 /*Save the area*/
251 if(disp->inv_p < LV_INV_BUF_SIZE) {
252 lv_area_copy(&disp->inv_areas[disp->inv_p], &com_area);
253 }
254 else { /*If no place for the area add the screen*/
255 disp->inv_p = 0;
256 lv_area_copy(&disp->inv_areas[disp->inv_p], &scr_area);
257 }
258 disp->inv_p++;
259 if(disp->refr_timer) lv_timer_resume(disp->refr_timer);
260 }
261
262 /**
263 * Get the display which is being refreshed
264 * @return the display being refreshed
265 */
_lv_refr_get_disp_refreshing(void)266 lv_disp_t * _lv_refr_get_disp_refreshing(void)
267 {
268 return disp_refr;
269 }
270
271 /**
272 * Set the display which is being refreshed.
273 * It shouldn't be used directly by the user.
274 * It can be used to trick the drawing functions about there is an active display.
275 * @param the display being refreshed
276 */
_lv_refr_set_disp_refreshing(lv_disp_t * disp)277 void _lv_refr_set_disp_refreshing(lv_disp_t * disp)
278 {
279 disp_refr = disp;
280 }
281
282 /**
283 * Called periodically to handle the refreshing
284 * @param tmr pointer to the timer itself
285 */
_lv_disp_refr_timer(lv_timer_t * tmr)286 void _lv_disp_refr_timer(lv_timer_t * tmr)
287 {
288 REFR_TRACE("begin");
289
290 uint32_t start = lv_tick_get();
291 volatile uint32_t elaps = 0;
292
293 if(tmr) {
294 disp_refr = tmr->user_data;
295 #if LV_USE_PERF_MONITOR == 0 && LV_USE_MEM_MONITOR == 0
296 /**
297 * Ensure the timer does not run again automatically.
298 * This is done before refreshing in case refreshing invalidates something else.
299 */
300 lv_timer_pause(tmr);
301 #endif
302 }
303 else {
304 disp_refr = lv_disp_get_default();
305 }
306
307 /*Refresh the screen's layout if required*/
308 lv_obj_update_layout(disp_refr->act_scr);
309 if(disp_refr->prev_scr) lv_obj_update_layout(disp_refr->prev_scr);
310
311 lv_obj_update_layout(disp_refr->top_layer);
312 lv_obj_update_layout(disp_refr->sys_layer);
313
314 /*Do nothing if there is no active screen*/
315 if(disp_refr->act_scr == NULL) {
316 disp_refr->inv_p = 0;
317 LV_LOG_WARN("there is no active screen");
318 REFR_TRACE("finished");
319 return;
320 }
321
322 lv_refr_join_area();
323 refr_sync_areas();
324 refr_invalid_areas();
325
326 /*If refresh happened ...*/
327 if(disp_refr->inv_p != 0) {
328
329 /*Copy invalid areas for sync next refresh in double buffered direct mode*/
330 if(disp_refr->driver->direct_mode && disp_refr->driver->draw_buf->buf2) {
331
332 uint16_t i;
333 for(i = 0; i < disp_refr->inv_p; i++) {
334 if(disp_refr->inv_area_joined[i])
335 continue;
336
337 lv_area_t * sync_area = _lv_ll_ins_tail(&disp_refr->sync_areas);
338 *sync_area = disp_refr->inv_areas[i];
339 }
340 }
341
342 /*Clean up*/
343 lv_memset_00(disp_refr->inv_areas, sizeof(disp_refr->inv_areas));
344 lv_memset_00(disp_refr->inv_area_joined, sizeof(disp_refr->inv_area_joined));
345 disp_refr->inv_p = 0;
346
347 elaps = lv_tick_elaps(start);
348
349 /*Call monitor cb if present*/
350 if(disp_refr->driver->monitor_cb) {
351 disp_refr->driver->monitor_cb(disp_refr->driver, elaps, px_num);
352 }
353 }
354
355 lv_mem_buf_free_all();
356 _lv_font_clean_up_fmt_txt();
357
358 #if LV_DRAW_COMPLEX
359 _lv_draw_mask_cleanup();
360 #endif
361
362 #if LV_USE_PERF_MONITOR && LV_USE_LABEL
363 lv_obj_t * perf_label = perf_monitor.perf_label;
364 if(perf_label == NULL) {
365 perf_label = lv_label_create(lv_layer_sys());
366 lv_obj_set_style_bg_opa(perf_label, LV_OPA_50, 0);
367 lv_obj_set_style_bg_color(perf_label, lv_color_black(), 0);
368 lv_obj_set_style_text_color(perf_label, lv_color_white(), 0);
369 lv_obj_set_style_pad_top(perf_label, 3, 0);
370 lv_obj_set_style_pad_bottom(perf_label, 3, 0);
371 lv_obj_set_style_pad_left(perf_label, 3, 0);
372 lv_obj_set_style_pad_right(perf_label, 3, 0);
373 lv_obj_set_style_text_align(perf_label, LV_TEXT_ALIGN_RIGHT, 0);
374 lv_label_set_text(perf_label, "?");
375 lv_obj_align(perf_label, LV_USE_PERF_MONITOR_POS, 0, 0);
376 perf_monitor.perf_label = perf_label;
377 }
378
379 if(lv_tick_elaps(perf_monitor.perf_last_time) < 300) {
380 if(px_num > 5000) {
381 perf_monitor.elaps_sum += elaps;
382 perf_monitor.frame_cnt ++;
383 }
384 }
385 else {
386 perf_monitor.perf_last_time = lv_tick_get();
387 uint32_t fps_limit;
388 uint32_t fps;
389
390 if(disp_refr->refr_timer) {
391 fps_limit = 1000 / disp_refr->refr_timer->period;
392 }
393 else {
394 fps_limit = 1000 / LV_DISP_DEF_REFR_PERIOD;
395 }
396
397 if(perf_monitor.elaps_sum == 0) {
398 perf_monitor.elaps_sum = 1;
399 }
400 if(perf_monitor.frame_cnt == 0) {
401 fps = fps_limit;
402 }
403 else {
404 fps = (1000 * perf_monitor.frame_cnt) / perf_monitor.elaps_sum;
405 }
406 perf_monitor.elaps_sum = 0;
407 perf_monitor.frame_cnt = 0;
408 if(fps > fps_limit) {
409 fps = fps_limit;
410 }
411
412 perf_monitor.fps_sum_all += fps;
413 perf_monitor.fps_sum_cnt ++;
414 uint32_t cpu = 100 - lv_timer_get_idle();
415 lv_label_set_text_fmt(perf_label, "%"LV_PRIu32" FPS\n%"LV_PRIu32"%% CPU", fps, cpu);
416 }
417 #endif
418
419 #if LV_USE_MEM_MONITOR && LV_MEM_CUSTOM == 0 && LV_USE_LABEL
420 lv_obj_t * mem_label = mem_monitor.mem_label;
421 if(mem_label == NULL) {
422 mem_label = lv_label_create(lv_layer_sys());
423 lv_obj_set_style_bg_opa(mem_label, LV_OPA_50, 0);
424 lv_obj_set_style_bg_color(mem_label, lv_color_black(), 0);
425 lv_obj_set_style_text_color(mem_label, lv_color_white(), 0);
426 lv_obj_set_style_pad_top(mem_label, 3, 0);
427 lv_obj_set_style_pad_bottom(mem_label, 3, 0);
428 lv_obj_set_style_pad_left(mem_label, 3, 0);
429 lv_obj_set_style_pad_right(mem_label, 3, 0);
430 lv_label_set_text(mem_label, "?");
431 lv_obj_align(mem_label, LV_USE_MEM_MONITOR_POS, 0, 0);
432 mem_monitor.mem_label = mem_label;
433 }
434
435 if(lv_tick_elaps(mem_monitor.mem_last_time) > 300) {
436 mem_monitor.mem_last_time = lv_tick_get();
437 lv_mem_monitor_t mon;
438 lv_mem_monitor(&mon);
439 uint32_t used_size = mon.total_size - mon.free_size;;
440 uint32_t used_kb = used_size / 1024;
441 uint32_t used_kb_tenth = (used_size - (used_kb * 1024)) / 102;
442 lv_label_set_text_fmt(mem_label,
443 "%"LV_PRIu32 ".%"LV_PRIu32 " kB used (%d %%)\n"
444 "%d%% frag.",
445 used_kb, used_kb_tenth, mon.used_pct,
446 mon.frag_pct);
447 }
448 #endif
449
450 REFR_TRACE("finished");
451 }
452
453 #if LV_USE_PERF_MONITOR
lv_refr_reset_fps_counter(void)454 void lv_refr_reset_fps_counter(void)
455 {
456 perf_monitor.fps_sum_all = 0;
457 perf_monitor.fps_sum_cnt = 0;
458 }
459
lv_refr_get_fps_avg(void)460 uint32_t lv_refr_get_fps_avg(void)
461 {
462 if(perf_monitor.fps_sum_cnt == 0) {
463 return 0;
464 }
465 return perf_monitor.fps_sum_all / perf_monitor.fps_sum_cnt;
466 }
467 #endif
468
469 /**********************
470 * STATIC FUNCTIONS
471 **********************/
472
473 /**
474 * Join the areas which has got common parts
475 */
lv_refr_join_area(void)476 static void lv_refr_join_area(void)
477 {
478 uint32_t join_from;
479 uint32_t join_in;
480 lv_area_t joined_area;
481 for(join_in = 0; join_in < disp_refr->inv_p; join_in++) {
482 if(disp_refr->inv_area_joined[join_in] != 0) continue;
483
484 /*Check all areas to join them in 'join_in'*/
485 for(join_from = 0; join_from < disp_refr->inv_p; join_from++) {
486 /*Handle only unjoined areas and ignore itself*/
487 if(disp_refr->inv_area_joined[join_from] != 0 || join_in == join_from) {
488 continue;
489 }
490
491 /*Check if the areas are on each other*/
492 if(_lv_area_is_on(&disp_refr->inv_areas[join_in], &disp_refr->inv_areas[join_from]) == false) {
493 continue;
494 }
495
496 _lv_area_join(&joined_area, &disp_refr->inv_areas[join_in], &disp_refr->inv_areas[join_from]);
497
498 /*Join two area only if the joined area size is smaller*/
499 if(lv_area_get_size(&joined_area) < (lv_area_get_size(&disp_refr->inv_areas[join_in]) +
500 lv_area_get_size(&disp_refr->inv_areas[join_from]))) {
501 lv_area_copy(&disp_refr->inv_areas[join_in], &joined_area);
502
503 /*Mark 'join_form' is joined into 'join_in'*/
504 disp_refr->inv_area_joined[join_from] = 1;
505 }
506 }
507 }
508 }
509
510 /**
511 * Refresh the sync areas
512 */
refr_sync_areas(void)513 static void refr_sync_areas(void)
514 {
515 /*Do not sync if not direct mode*/
516 if(!disp_refr->driver->direct_mode) return;
517
518 /*Do not sync if not double buffered*/
519 if(disp_refr->driver->draw_buf->buf2 == NULL) return;
520
521 /*Do not sync if no sync areas*/
522 if(_lv_ll_is_empty(&disp_refr->sync_areas)) return;
523
524 /*The buffers are already swapped.
525 *So the active buffer is the off screen buffer where LVGL will render*/
526 void * buf_off_screen = disp_refr->driver->draw_buf->buf_act;
527 void * buf_on_screen = disp_refr->driver->draw_buf->buf_act == disp_refr->driver->draw_buf->buf1
528 ? disp_refr->driver->draw_buf->buf2
529 : disp_refr->driver->draw_buf->buf1;
530
531 /*Get stride for buffer copy*/
532 lv_coord_t stride = lv_disp_get_hor_res(disp_refr);
533
534 /*Iterate through invalidated areas to see if sync area should be copied*/
535 lv_area_t res[4] = {0};
536 int8_t res_c, j;
537 uint32_t i;
538 lv_area_t * sync_area, * new_area, * next_area;
539 for(i = 0; i < disp_refr->inv_p; i++) {
540 /*Skip joined areas*/
541 if(disp_refr->inv_area_joined[i]) continue;
542
543 /*Iterate over sync areas*/
544 sync_area = _lv_ll_get_head(&disp_refr->sync_areas);
545 while(sync_area != NULL) {
546 /*Get next sync area*/
547 next_area = _lv_ll_get_next(&disp_refr->sync_areas, sync_area);
548
549 /*Remove intersect of redraw area from sync area and get remaining areas*/
550 res_c = _lv_area_diff(res, sync_area, &disp_refr->inv_areas[i]);
551
552 /*New sub areas created after removing intersect*/
553 if(res_c != -1) {
554 /*Replace old sync area with new areas*/
555 for(j = 0; j < res_c; j++) {
556 new_area = _lv_ll_ins_prev(&disp_refr->sync_areas, sync_area);
557 *new_area = res[j];
558 }
559 _lv_ll_remove(&disp_refr->sync_areas, sync_area);
560 lv_mem_free(sync_area);
561 }
562
563 /*Move on to next sync area*/
564 sync_area = next_area;
565 }
566 }
567
568 /*Copy sync areas (if any remaining)*/
569 for(sync_area = _lv_ll_get_head(&disp_refr->sync_areas); sync_area != NULL;
570 sync_area = _lv_ll_get_next(&disp_refr->sync_areas, sync_area)) {
571 disp_refr->driver->draw_ctx->buffer_copy(
572 disp_refr->driver->draw_ctx,
573 buf_off_screen, stride, sync_area,
574 buf_on_screen, stride, sync_area
575 );
576 }
577
578 /*Clear sync areas*/
579 _lv_ll_clear(&disp_refr->sync_areas);
580 }
581
582 /**
583 * Refresh the joined areas
584 */
refr_invalid_areas(void)585 static void refr_invalid_areas(void)
586 {
587 px_num = 0;
588
589 if(disp_refr->inv_p == 0) return;
590
591 /*Find the last area which will be drawn*/
592 int32_t i;
593 int32_t last_i = 0;
594 for(i = disp_refr->inv_p - 1; i >= 0; i--) {
595 if(disp_refr->inv_area_joined[i] == 0) {
596 last_i = i;
597 break;
598 }
599 }
600
601 /*Notify the display driven rendering has started*/
602 if(disp_refr->driver->render_start_cb) {
603 disp_refr->driver->render_start_cb(disp_refr->driver);
604 }
605
606 disp_refr->driver->draw_buf->last_area = 0;
607 disp_refr->driver->draw_buf->last_part = 0;
608 disp_refr->rendering_in_progress = true;
609
610 for(i = 0; i < disp_refr->inv_p; i++) {
611 /*Refresh the unjoined areas*/
612 if(disp_refr->inv_area_joined[i] == 0) {
613
614 if(i == last_i) disp_refr->driver->draw_buf->last_area = 1;
615 disp_refr->driver->draw_buf->last_part = 0;
616 refr_area(&disp_refr->inv_areas[i]);
617
618 px_num += lv_area_get_size(&disp_refr->inv_areas[i]);
619 }
620 }
621
622 disp_refr->rendering_in_progress = false;
623 }
624
625 /**
626 * Refresh an area if there is Virtual Display Buffer
627 * @param area_p pointer to an area to refresh
628 */
refr_area(const lv_area_t * area_p)629 static void refr_area(const lv_area_t * area_p)
630 {
631 lv_draw_ctx_t * draw_ctx = disp_refr->driver->draw_ctx;
632 draw_ctx->buf = disp_refr->driver->draw_buf->buf_act;
633
634 /*With full refresh just redraw directly into the buffer*/
635 /*In direct mode draw directly on the absolute coordinates of the buffer*/
636 if(disp_refr->driver->full_refresh || disp_refr->driver->direct_mode) {
637 lv_area_t disp_area;
638 lv_area_set(&disp_area, 0, 0, lv_disp_get_hor_res(disp_refr) - 1, lv_disp_get_ver_res(disp_refr) - 1);
639 draw_ctx->buf_area = &disp_area;
640
641 if(disp_refr->driver->full_refresh) {
642 disp_refr->driver->draw_buf->last_part = 1;
643 draw_ctx->clip_area = &disp_area;
644 refr_area_part(draw_ctx);
645 }
646 else {
647 disp_refr->driver->draw_buf->last_part = disp_refr->driver->draw_buf->last_area;
648 draw_ctx->clip_area = area_p;
649 refr_area_part(draw_ctx);
650 }
651 return;
652 }
653
654 /*Normal refresh: draw the area in parts*/
655 /*Calculate the max row num*/
656 lv_coord_t w = lv_area_get_width(area_p);
657 lv_coord_t h = lv_area_get_height(area_p);
658 lv_coord_t y2 = area_p->y2 >= lv_disp_get_ver_res(disp_refr) ?
659 lv_disp_get_ver_res(disp_refr) - 1 : area_p->y2;
660
661 int32_t max_row = get_max_row(disp_refr, w, h);
662
663 lv_coord_t row;
664 lv_coord_t row_last = 0;
665 lv_area_t sub_area;
666 for(row = area_p->y1; row + max_row - 1 <= y2; row += max_row) {
667 /*Calc. the next y coordinates of draw_buf*/
668 sub_area.x1 = area_p->x1;
669 sub_area.x2 = area_p->x2;
670 sub_area.y1 = row;
671 sub_area.y2 = row + max_row - 1;
672 draw_ctx->buf_area = &sub_area;
673 draw_ctx->clip_area = &sub_area;
674 draw_ctx->buf = disp_refr->driver->draw_buf->buf_act;
675 if(sub_area.y2 > y2) sub_area.y2 = y2;
676 row_last = sub_area.y2;
677 if(y2 == row_last) disp_refr->driver->draw_buf->last_part = 1;
678 refr_area_part(draw_ctx);
679 }
680
681 /*If the last y coordinates are not handled yet ...*/
682 if(y2 != row_last) {
683 /*Calc. the next y coordinates of draw_buf*/
684 sub_area.x1 = area_p->x1;
685 sub_area.x2 = area_p->x2;
686 sub_area.y1 = row;
687 sub_area.y2 = y2;
688 draw_ctx->buf_area = &sub_area;
689 draw_ctx->clip_area = &sub_area;
690 draw_ctx->buf = disp_refr->driver->draw_buf->buf_act;
691 disp_refr->driver->draw_buf->last_part = 1;
692 refr_area_part(draw_ctx);
693 }
694 }
695
refr_area_part(lv_draw_ctx_t * draw_ctx)696 static void refr_area_part(lv_draw_ctx_t * draw_ctx)
697 {
698 lv_disp_draw_buf_t * draw_buf = lv_disp_get_draw_buf(disp_refr);
699
700 if(draw_ctx->init_buf)
701 draw_ctx->init_buf(draw_ctx);
702
703 /* Below the `area_p` area will be redrawn into the draw buffer.
704 * In single buffered mode wait here until the buffer is freed.
705 * In full double buffered mode wait here while the buffers are swapped and a buffer becomes available*/
706 bool full_sized = draw_buf->size == (uint32_t)disp_refr->driver->hor_res * disp_refr->driver->ver_res;
707 if((draw_buf->buf1 && !draw_buf->buf2) ||
708 (draw_buf->buf1 && draw_buf->buf2 && full_sized)) {
709 while(draw_buf->flushing) {
710 if(disp_refr->driver->wait_cb) disp_refr->driver->wait_cb(disp_refr->driver);
711 }
712
713 /*If the screen is transparent initialize it when the flushing is ready*/
714 #if LV_COLOR_SCREEN_TRANSP
715 if(disp_refr->driver->screen_transp) {
716 if(disp_refr->driver->clear_cb) {
717 disp_refr->driver->clear_cb(disp_refr->driver, disp_refr->driver->draw_buf->buf_act, disp_refr->driver->draw_buf->size);
718 }
719 else {
720 lv_memset_00(disp_refr->driver->draw_buf->buf_act, disp_refr->driver->draw_buf->size * LV_IMG_PX_SIZE_ALPHA_BYTE);
721 }
722 }
723 #endif
724 }
725
726 lv_obj_t * top_act_scr = NULL;
727 lv_obj_t * top_prev_scr = NULL;
728
729 /*Get the most top object which is not covered by others*/
730 top_act_scr = lv_refr_get_top_obj(draw_ctx->buf_area, lv_disp_get_scr_act(disp_refr));
731 if(disp_refr->prev_scr) {
732 top_prev_scr = lv_refr_get_top_obj(draw_ctx->buf_area, disp_refr->prev_scr);
733 }
734
735 /*Draw a display background if there is no top object*/
736 if(top_act_scr == NULL && top_prev_scr == NULL) {
737 lv_area_t a;
738 lv_area_set(&a, 0, 0,
739 lv_disp_get_hor_res(disp_refr) - 1, lv_disp_get_ver_res(disp_refr) - 1);
740 if(draw_ctx->draw_bg) {
741 lv_draw_rect_dsc_t dsc;
742 lv_draw_rect_dsc_init(&dsc);
743 dsc.bg_img_src = disp_refr->bg_img;
744 dsc.bg_img_opa = disp_refr->bg_opa;
745 dsc.bg_color = disp_refr->bg_color;
746 dsc.bg_opa = disp_refr->bg_opa;
747 draw_ctx->draw_bg(draw_ctx, &dsc, &a);
748 }
749 else if(disp_refr->bg_img) {
750 lv_img_header_t header;
751 lv_res_t res = lv_img_decoder_get_info(disp_refr->bg_img, &header);
752 if(res == LV_RES_OK) {
753 lv_draw_img_dsc_t dsc;
754 lv_draw_img_dsc_init(&dsc);
755 dsc.opa = disp_refr->bg_opa;
756 lv_draw_img(draw_ctx, &dsc, &a, disp_refr->bg_img);
757 }
758 else {
759 LV_LOG_WARN("Can't draw the background image");
760 }
761 }
762 else {
763 lv_draw_rect_dsc_t dsc;
764 lv_draw_rect_dsc_init(&dsc);
765 dsc.bg_color = disp_refr->bg_color;
766 dsc.bg_opa = disp_refr->bg_opa;
767 lv_draw_rect(draw_ctx, &dsc, draw_ctx->buf_area);
768 }
769 }
770
771 if(disp_refr->draw_prev_over_act) {
772 if(top_act_scr == NULL) top_act_scr = disp_refr->act_scr;
773 refr_obj_and_children(draw_ctx, top_act_scr);
774
775 /*Refresh the previous screen if any*/
776 if(disp_refr->prev_scr) {
777 if(top_prev_scr == NULL) top_prev_scr = disp_refr->prev_scr;
778 refr_obj_and_children(draw_ctx, top_prev_scr);
779 }
780 }
781 else {
782 /*Refresh the previous screen if any*/
783 if(disp_refr->prev_scr) {
784 if(top_prev_scr == NULL) top_prev_scr = disp_refr->prev_scr;
785 refr_obj_and_children(draw_ctx, top_prev_scr);
786 }
787
788 if(top_act_scr == NULL) top_act_scr = disp_refr->act_scr;
789 refr_obj_and_children(draw_ctx, top_act_scr);
790 }
791
792 /*Also refresh top and sys layer unconditionally*/
793 refr_obj_and_children(draw_ctx, lv_disp_get_layer_top(disp_refr));
794 refr_obj_and_children(draw_ctx, lv_disp_get_layer_sys(disp_refr));
795
796 draw_buf_flush(disp_refr);
797 }
798
799 /**
800 * Search the most top object which fully covers an area
801 * @param area_p pointer to an area
802 * @param obj the first object to start the searching (typically a screen)
803 * @return
804 */
lv_refr_get_top_obj(const lv_area_t * area_p,lv_obj_t * obj)805 static lv_obj_t * lv_refr_get_top_obj(const lv_area_t * area_p, lv_obj_t * obj)
806 {
807 lv_obj_t * found_p = NULL;
808
809 if(_lv_area_is_in(area_p, &obj->coords, 0) == false) return NULL;
810 if(lv_obj_has_flag(obj, LV_OBJ_FLAG_HIDDEN)) return NULL;
811 if(_lv_obj_get_layer_type(obj) != LV_LAYER_TYPE_NONE) return NULL;
812
813 /*If this object is fully cover the draw area then check the children too*/
814 lv_cover_check_info_t info;
815 info.res = LV_COVER_RES_COVER;
816 info.area = area_p;
817 lv_event_send(obj, LV_EVENT_COVER_CHECK, &info);
818 if(info.res == LV_COVER_RES_MASKED) return NULL;
819
820 int32_t i;
821 int32_t child_cnt = lv_obj_get_child_cnt(obj);
822 for(i = child_cnt - 1; i >= 0; i--) {
823 lv_obj_t * child = obj->spec_attr->children[i];
824 found_p = lv_refr_get_top_obj(area_p, child);
825
826 /*If a children is ok then break*/
827 if(found_p != NULL) {
828 break;
829 }
830 }
831
832 /*If no better children use this object*/
833 if(found_p == NULL && info.res == LV_COVER_RES_COVER) {
834 found_p = obj;
835 }
836
837 return found_p;
838 }
839
840 /**
841 * Make the refreshing from an object. Draw all its children and the youngers too.
842 * @param top_p pointer to an objects. Start the drawing from it.
843 * @param mask_p pointer to an area, the objects will be drawn only here
844 */
refr_obj_and_children(lv_draw_ctx_t * draw_ctx,lv_obj_t * top_obj)845 static void refr_obj_and_children(lv_draw_ctx_t * draw_ctx, lv_obj_t * top_obj)
846 {
847 /*Normally always will be a top_obj (at least the screen)
848 *but in special cases (e.g. if the screen has alpha) it won't.
849 *In this case use the screen directly*/
850 if(top_obj == NULL) top_obj = lv_disp_get_scr_act(disp_refr);
851 if(top_obj == NULL) return; /*Shouldn't happen*/
852
853 /*Refresh the top object and its children*/
854 refr_obj(draw_ctx, top_obj);
855
856 /*Draw the 'younger' sibling objects because they can be on top_obj*/
857 lv_obj_t * parent;
858 lv_obj_t * border_p = top_obj;
859
860 parent = lv_obj_get_parent(top_obj);
861
862 /*Do until not reach the screen*/
863 while(parent != NULL) {
864 bool go = false;
865 uint32_t i;
866 uint32_t child_cnt = lv_obj_get_child_cnt(parent);
867 for(i = 0; i < child_cnt; i++) {
868 lv_obj_t * child = parent->spec_attr->children[i];
869 if(!go) {
870 if(child == border_p) go = true;
871 }
872 else {
873 /*Refresh the objects*/
874 refr_obj(draw_ctx, child);
875 }
876 }
877
878 /*Call the post draw draw function of the parents of the to object*/
879 lv_event_send(parent, LV_EVENT_DRAW_POST_BEGIN, (void *)draw_ctx);
880 lv_event_send(parent, LV_EVENT_DRAW_POST, (void *)draw_ctx);
881 lv_event_send(parent, LV_EVENT_DRAW_POST_END, (void *)draw_ctx);
882
883 /*The new border will be the last parents,
884 *so the 'younger' brothers of parent will be refreshed*/
885 border_p = parent;
886 /*Go a level deeper*/
887 parent = lv_obj_get_parent(parent);
888 }
889 }
890
layer_get_area(lv_draw_ctx_t * draw_ctx,lv_obj_t * obj,lv_layer_type_t layer_type,lv_area_t * layer_area_out)891 static lv_res_t layer_get_area(lv_draw_ctx_t * draw_ctx, lv_obj_t * obj, lv_layer_type_t layer_type,
892 lv_area_t * layer_area_out)
893 {
894 lv_coord_t ext_draw_size = _lv_obj_get_ext_draw_size(obj);
895 lv_area_t obj_coords_ext;
896 lv_obj_get_coords(obj, &obj_coords_ext);
897 lv_area_increase(&obj_coords_ext, ext_draw_size, ext_draw_size);
898
899 if(layer_type == LV_LAYER_TYPE_TRANSFORM) {
900 /*Get the transformed area and clip it to the current clip area.
901 *This area needs to be updated on the screen.*/
902 lv_area_t clip_coords_for_obj;
903 lv_area_t tranf_coords = obj_coords_ext;
904 lv_obj_get_transformed_area(obj, &tranf_coords, false, false);
905 if(!_lv_area_intersect(&clip_coords_for_obj, draw_ctx->clip_area, &tranf_coords)) {
906 return LV_RES_INV;
907 }
908
909 /*Transform back (inverse) the transformed area.
910 *It will tell which area of the non-transformed widget needs to be redrawn
911 *in order to cover transformed area after transformation.*/
912 lv_area_t inverse_clip_coords_for_obj = clip_coords_for_obj;
913 lv_obj_get_transformed_area(obj, &inverse_clip_coords_for_obj, false, true);
914 if(!_lv_area_intersect(&inverse_clip_coords_for_obj, &inverse_clip_coords_for_obj, &obj_coords_ext)) {
915 return LV_RES_INV;
916 }
917
918 *layer_area_out = inverse_clip_coords_for_obj;
919 }
920 else if(layer_type == LV_LAYER_TYPE_SIMPLE) {
921 lv_area_t clip_coords_for_obj;
922 if(!_lv_area_intersect(&clip_coords_for_obj, draw_ctx->clip_area, &obj_coords_ext)) {
923 return LV_RES_INV;
924 }
925 *layer_area_out = clip_coords_for_obj;
926 }
927 else {
928 LV_LOG_WARN("Unhandled intermediate layer type");
929 return LV_RES_INV;
930 }
931
932 return LV_RES_OK;
933 }
934
layer_alpha_test(lv_obj_t * obj,lv_draw_ctx_t * draw_ctx,lv_draw_layer_ctx_t * layer_ctx,lv_draw_layer_flags_t flags)935 static void layer_alpha_test(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, lv_draw_layer_ctx_t * layer_ctx,
936 lv_draw_layer_flags_t flags)
937 {
938 bool has_alpha;
939 /*If globally the layer has alpha maybe this smaller section has not (e.g. not on a rounded corner)
940 *If turns out that this section has no alpha renderer can choose faster algorithms*/
941 if(flags & LV_DRAW_LAYER_FLAG_HAS_ALPHA) {
942 /*Test for alpha by assuming there is no alpha. If it fails, fall back to rendering with alpha*/
943 has_alpha = true;
944 if(_lv_area_is_in(&layer_ctx->area_act, &obj->coords, 0)) {
945 lv_cover_check_info_t info;
946 info.res = LV_COVER_RES_COVER;
947 info.area = &layer_ctx->area_act;
948 lv_event_send(obj, LV_EVENT_COVER_CHECK, &info);
949 if(info.res == LV_COVER_RES_COVER) has_alpha = false;
950 }
951
952 if(has_alpha) {
953 layer_ctx->area_act.y2 = layer_ctx->area_act.y1 + layer_ctx->max_row_with_alpha - 1;
954 }
955 }
956 else {
957 has_alpha = false;
958 }
959
960 if(layer_ctx->area_act.y2 > layer_ctx->area_full.y2) layer_ctx->area_act.y2 = layer_ctx->area_full.y2;
961 lv_draw_layer_adjust(draw_ctx, layer_ctx, has_alpha ? LV_DRAW_LAYER_FLAG_HAS_ALPHA : LV_DRAW_LAYER_FLAG_NONE);
962 }
963
refr_obj(lv_draw_ctx_t * draw_ctx,lv_obj_t * obj)964 void refr_obj(lv_draw_ctx_t * draw_ctx, lv_obj_t * obj)
965 {
966 /*Do not refresh hidden objects*/
967 if(lv_obj_has_flag(obj, LV_OBJ_FLAG_HIDDEN)) return;
968 lv_layer_type_t layer_type = _lv_obj_get_layer_type(obj);
969 if(layer_type == LV_LAYER_TYPE_NONE) {
970 lv_obj_redraw(draw_ctx, obj);
971 }
972 else {
973 lv_opa_t opa = lv_obj_get_style_opa_layered(obj, 0);
974 if(opa < LV_OPA_MIN) return;
975
976 lv_area_t layer_area_full;
977 lv_res_t res = layer_get_area(draw_ctx, obj, layer_type, &layer_area_full);
978 if(res != LV_RES_OK) return;
979
980 lv_draw_layer_flags_t flags = LV_DRAW_LAYER_FLAG_HAS_ALPHA;
981
982 if(_lv_area_is_in(&layer_area_full, &obj->coords, 0)) {
983 lv_cover_check_info_t info;
984 info.res = LV_COVER_RES_COVER;
985 info.area = &layer_area_full;
986 lv_event_send(obj, LV_EVENT_COVER_CHECK, &info);
987 if(info.res == LV_COVER_RES_COVER) flags &= ~LV_DRAW_LAYER_FLAG_HAS_ALPHA;
988 }
989
990 if(layer_type == LV_LAYER_TYPE_SIMPLE) flags |= LV_DRAW_LAYER_FLAG_CAN_SUBDIVIDE;
991
992 lv_draw_layer_ctx_t * layer_ctx = lv_draw_layer_create(draw_ctx, &layer_area_full, flags);
993 if(layer_ctx == NULL) {
994 LV_LOG_WARN("Couldn't create a new layer context");
995 return;
996 }
997 lv_point_t pivot = {
998 .x = lv_obj_get_style_transform_pivot_x(obj, 0),
999 .y = lv_obj_get_style_transform_pivot_y(obj, 0)
1000 };
1001
1002 if(LV_COORD_IS_PCT(pivot.x)) {
1003 pivot.x = (LV_COORD_GET_PCT(pivot.x) * lv_area_get_width(&obj->coords)) / 100;
1004 }
1005 if(LV_COORD_IS_PCT(pivot.y)) {
1006 pivot.y = (LV_COORD_GET_PCT(pivot.y) * lv_area_get_height(&obj->coords)) / 100;
1007 }
1008
1009 lv_draw_img_dsc_t draw_dsc;
1010 lv_draw_img_dsc_init(&draw_dsc);
1011 draw_dsc.opa = opa;
1012 draw_dsc.angle = lv_obj_get_style_transform_angle(obj, 0);
1013 if(draw_dsc.angle > 3600) draw_dsc.angle -= 3600;
1014 else if(draw_dsc.angle < 0) draw_dsc.angle += 3600;
1015
1016 draw_dsc.zoom = lv_obj_get_style_transform_zoom(obj, 0);
1017 draw_dsc.blend_mode = lv_obj_get_style_blend_mode(obj, 0);
1018 draw_dsc.antialias = disp_refr->driver->antialiasing;
1019
1020 if(flags & LV_DRAW_LAYER_FLAG_CAN_SUBDIVIDE) {
1021 layer_ctx->area_act = layer_ctx->area_full;
1022 layer_ctx->area_act.y2 = layer_ctx->area_act.y1 + layer_ctx->max_row_with_no_alpha - 1;
1023 if(layer_ctx->area_act.y2 > layer_ctx->area_full.y2) layer_ctx->area_act.y2 = layer_ctx->area_full.y2;
1024 }
1025
1026 while(layer_ctx->area_act.y1 <= layer_area_full.y2) {
1027 if(flags & LV_DRAW_LAYER_FLAG_CAN_SUBDIVIDE) {
1028 layer_alpha_test(obj, draw_ctx, layer_ctx, flags);
1029 }
1030
1031 lv_obj_redraw(draw_ctx, obj);
1032
1033 draw_dsc.pivot.x = obj->coords.x1 + pivot.x - draw_ctx->buf_area->x1;
1034 draw_dsc.pivot.y = obj->coords.y1 + pivot.y - draw_ctx->buf_area->y1;
1035
1036 /*With LV_DRAW_LAYER_FLAG_CAN_SUBDIVIDE it should also go the next chunk*/
1037 lv_draw_layer_blend(draw_ctx, layer_ctx, &draw_dsc);
1038
1039 if((flags & LV_DRAW_LAYER_FLAG_CAN_SUBDIVIDE) == 0) break;
1040
1041 layer_ctx->area_act.y1 = layer_ctx->area_act.y2 + 1;
1042 layer_ctx->area_act.y2 = layer_ctx->area_act.y1 + layer_ctx->max_row_with_no_alpha - 1;
1043 }
1044
1045 lv_draw_layer_destroy(draw_ctx, layer_ctx);
1046 }
1047 }
1048
get_max_row(lv_disp_t * disp,lv_coord_t area_w,lv_coord_t area_h)1049 static uint32_t get_max_row(lv_disp_t * disp, lv_coord_t area_w, lv_coord_t area_h)
1050 {
1051 int32_t max_row = (uint32_t)disp->driver->draw_buf->size / area_w;
1052
1053 if(max_row > area_h) max_row = area_h;
1054
1055 /*Round down the lines of draw_buf if rounding is added*/
1056 if(disp_refr->driver->rounder_cb) {
1057 lv_area_t tmp;
1058 tmp.x1 = 0;
1059 tmp.x2 = 0;
1060 tmp.y1 = 0;
1061
1062 lv_coord_t h_tmp = max_row;
1063 do {
1064 tmp.y2 = h_tmp - 1;
1065 disp_refr->driver->rounder_cb(disp_refr->driver, &tmp);
1066
1067 /*If this height fits into `max_row` then fine*/
1068 if(lv_area_get_height(&tmp) <= max_row) break;
1069
1070 /*Decrement the height of the area until it fits into `max_row` after rounding*/
1071 h_tmp--;
1072 } while(h_tmp > 0);
1073
1074 if(h_tmp <= 0) {
1075 LV_LOG_WARN("Can't set draw_buf height using the round function. (Wrong round_cb or to "
1076 "small draw_buf)");
1077 return 0;
1078 }
1079 else {
1080 max_row = tmp.y2 + 1;
1081 }
1082 }
1083
1084 return max_row;
1085 }
1086
draw_buf_rotate_180(lv_disp_drv_t * drv,lv_area_t * area,lv_color_t * color_p)1087 static void draw_buf_rotate_180(lv_disp_drv_t * drv, lv_area_t * area, lv_color_t * color_p)
1088 {
1089 lv_coord_t area_w = lv_area_get_width(area);
1090 lv_coord_t area_h = lv_area_get_height(area);
1091 uint32_t total = area_w * area_h;
1092 /*Swap the beginning and end values*/
1093 lv_color_t tmp;
1094 uint32_t i = total - 1, j = 0;
1095 while(i > j) {
1096 tmp = color_p[i];
1097 color_p[i] = color_p[j];
1098 color_p[j] = tmp;
1099 i--;
1100 j++;
1101 }
1102 lv_coord_t tmp_coord;
1103 tmp_coord = area->y2;
1104 area->y2 = drv->ver_res - area->y1 - 1;
1105 area->y1 = drv->ver_res - tmp_coord - 1;
1106 tmp_coord = area->x2;
1107 area->x2 = drv->hor_res - area->x1 - 1;
1108 area->x1 = drv->hor_res - tmp_coord - 1;
1109 }
1110
draw_buf_rotate_90(bool invert_i,lv_coord_t area_w,lv_coord_t area_h,lv_color_t * orig_color_p,lv_color_t * rot_buf)1111 static void LV_ATTRIBUTE_FAST_MEM draw_buf_rotate_90(bool invert_i, lv_coord_t area_w, lv_coord_t area_h,
1112 lv_color_t * orig_color_p, lv_color_t * rot_buf)
1113 {
1114
1115 uint32_t invert = (area_w * area_h) - 1;
1116 uint32_t initial_i = ((area_w - 1) * area_h);
1117 for(lv_coord_t y = 0; y < area_h; y++) {
1118 uint32_t i = initial_i + y;
1119 if(invert_i)
1120 i = invert - i;
1121 for(lv_coord_t x = 0; x < area_w; x++) {
1122 rot_buf[i] = *(orig_color_p++);
1123 if(invert_i)
1124 i += area_h;
1125 else
1126 i -= area_h;
1127 }
1128 }
1129 }
1130
1131 /**
1132 * Helper function for draw_buf_rotate_90_sqr. Given a list of four numbers, rotate the entire list to the left.
1133 */
draw_buf_rotate4(lv_color_t * a,lv_color_t * b,lv_color_t * c,lv_color_t * d)1134 static inline void draw_buf_rotate4(lv_color_t * a, lv_color_t * b, lv_color_t * c, lv_color_t * d)
1135 {
1136 lv_color_t tmp;
1137 tmp = *a;
1138 *a = *b;
1139 *b = *c;
1140 *c = *d;
1141 *d = tmp;
1142 }
1143
1144 /**
1145 * Rotate a square image 90/270 degrees in place.
1146 * @note inspired by https://stackoverflow.com/a/43694906
1147 */
draw_buf_rotate_90_sqr(bool is_270,lv_coord_t w,lv_color_t * color_p)1148 static void draw_buf_rotate_90_sqr(bool is_270, lv_coord_t w, lv_color_t * color_p)
1149 {
1150 for(lv_coord_t i = 0; i < w / 2; i++) {
1151 for(lv_coord_t j = 0; j < (w + 1) / 2; j++) {
1152 lv_coord_t inv_i = (w - 1) - i;
1153 lv_coord_t inv_j = (w - 1) - j;
1154 if(is_270) {
1155 draw_buf_rotate4(
1156 &color_p[i * w + j],
1157 &color_p[inv_j * w + i],
1158 &color_p[inv_i * w + inv_j],
1159 &color_p[j * w + inv_i]
1160 );
1161 }
1162 else {
1163 draw_buf_rotate4(
1164 &color_p[i * w + j],
1165 &color_p[j * w + inv_i],
1166 &color_p[inv_i * w + inv_j],
1167 &color_p[inv_j * w + i]
1168 );
1169 }
1170
1171 }
1172 }
1173 }
1174
1175 /**
1176 * Rotate the draw_buf to the display's native orientation.
1177 */
draw_buf_rotate(lv_area_t * area,lv_color_t * color_p)1178 static void draw_buf_rotate(lv_area_t * area, lv_color_t * color_p)
1179 {
1180 lv_disp_drv_t * drv = disp_refr->driver;
1181 if(disp_refr->driver->full_refresh && drv->sw_rotate) {
1182 LV_LOG_ERROR("cannot rotate a full refreshed display!");
1183 return;
1184 }
1185 if(drv->rotated == LV_DISP_ROT_180) {
1186 draw_buf_rotate_180(drv, area, color_p);
1187 call_flush_cb(drv, area, color_p);
1188 }
1189 else if(drv->rotated == LV_DISP_ROT_90 || drv->rotated == LV_DISP_ROT_270) {
1190 /*Allocate a temporary buffer to store rotated image*/
1191 lv_color_t * rot_buf = NULL;
1192 lv_disp_draw_buf_t * draw_buf = lv_disp_get_draw_buf(disp_refr);
1193 lv_coord_t area_w = lv_area_get_width(area);
1194 lv_coord_t area_h = lv_area_get_height(area);
1195 /*Determine the maximum number of rows that can be rotated at a time*/
1196 lv_coord_t max_row = LV_MIN((lv_coord_t)((LV_DISP_ROT_MAX_BUF / sizeof(lv_color_t)) / area_w), area_h);
1197 lv_coord_t init_y_off;
1198 init_y_off = area->y1;
1199 if(drv->rotated == LV_DISP_ROT_90) {
1200 area->y2 = drv->ver_res - area->x1 - 1;
1201 area->y1 = area->y2 - area_w + 1;
1202 }
1203 else {
1204 area->y1 = area->x1;
1205 area->y2 = area->y1 + area_w - 1;
1206 }
1207
1208 /*Rotate the screen in chunks, flushing after each one*/
1209 lv_coord_t row = 0;
1210 while(row < area_h) {
1211 lv_coord_t height = LV_MIN(max_row, area_h - row);
1212 draw_buf->flushing = 1;
1213 if((row == 0) && (area_h >= area_w)) {
1214 /*Rotate the initial area as a square*/
1215 height = area_w;
1216 draw_buf_rotate_90_sqr(drv->rotated == LV_DISP_ROT_270, area_w, color_p);
1217 if(drv->rotated == LV_DISP_ROT_90) {
1218 area->x1 = init_y_off;
1219 area->x2 = init_y_off + area_w - 1;
1220 }
1221 else {
1222 area->x2 = drv->hor_res - 1 - init_y_off;
1223 area->x1 = area->x2 - area_w + 1;
1224 }
1225 }
1226 else {
1227 /*Rotate other areas using a maximum buffer size*/
1228 if(rot_buf == NULL) rot_buf = lv_mem_buf_get(LV_DISP_ROT_MAX_BUF);
1229 draw_buf_rotate_90(drv->rotated == LV_DISP_ROT_270, area_w, height, color_p, rot_buf);
1230
1231 if(drv->rotated == LV_DISP_ROT_90) {
1232 area->x1 = init_y_off + row;
1233 area->x2 = init_y_off + row + height - 1;
1234 }
1235 else {
1236 area->x2 = drv->hor_res - 1 - init_y_off - row;
1237 area->x1 = area->x2 - height + 1;
1238 }
1239 }
1240
1241 /* The original part (chunk of the current area) were split into more parts here.
1242 * Set the original last_part flag on the last part of rotation. */
1243 if(row + height >= area_h && draw_buf->last_area && draw_buf->last_part) {
1244 draw_buf->flushing_last = 1;
1245 }
1246 else {
1247 draw_buf->flushing_last = 0;
1248 }
1249
1250 /*Flush the completed area to the display*/
1251 call_flush_cb(drv, area, rot_buf == NULL ? color_p : rot_buf);
1252 /*FIXME: Rotation forces legacy behavior where rendering and flushing are done serially*/
1253 while(draw_buf->flushing) {
1254 if(drv->wait_cb) drv->wait_cb(drv);
1255 }
1256 color_p += area_w * height;
1257 row += height;
1258 }
1259 /*Free the allocated buffer at the end if necessary*/
1260 if(rot_buf != NULL) lv_mem_buf_release(rot_buf);
1261 }
1262 }
1263
1264 /**
1265 * Flush the content of the draw buffer
1266 */
draw_buf_flush(lv_disp_t * disp)1267 static void draw_buf_flush(lv_disp_t * disp)
1268 {
1269 lv_disp_draw_buf_t * draw_buf = lv_disp_get_draw_buf(disp_refr);
1270
1271 /*Flush the rendered content to the display*/
1272 lv_draw_ctx_t * draw_ctx = disp->driver->draw_ctx;
1273 if(draw_ctx->wait_for_finish) draw_ctx->wait_for_finish(draw_ctx);
1274
1275 /* In partial double buffered mode wait until the other buffer is freed
1276 * and driver is ready to receive the new buffer */
1277 bool full_sized = draw_buf->size == (uint32_t)disp_refr->driver->hor_res * disp_refr->driver->ver_res;
1278 if(draw_buf->buf1 && draw_buf->buf2 && !full_sized) {
1279 while(draw_buf->flushing) {
1280 if(disp_refr->driver->wait_cb) disp_refr->driver->wait_cb(disp_refr->driver);
1281 }
1282 }
1283
1284 draw_buf->flushing = 1;
1285
1286 if(disp_refr->driver->draw_buf->last_area && disp_refr->driver->draw_buf->last_part) draw_buf->flushing_last = 1;
1287 else draw_buf->flushing_last = 0;
1288
1289 bool flushing_last = draw_buf->flushing_last;
1290
1291 if(disp->driver->flush_cb) {
1292 /*Rotate the buffer to the display's native orientation if necessary*/
1293 if(disp->driver->rotated != LV_DISP_ROT_NONE && disp->driver->sw_rotate) {
1294 draw_buf_rotate(draw_ctx->buf_area, draw_ctx->buf);
1295 }
1296 else {
1297 call_flush_cb(disp->driver, draw_ctx->buf_area, draw_ctx->buf);
1298 }
1299 }
1300
1301 /*If there are 2 buffers swap them. With direct mode swap only on the last area*/
1302 if(draw_buf->buf1 && draw_buf->buf2 && (!disp->driver->direct_mode || flushing_last)) {
1303 if(draw_buf->buf_act == draw_buf->buf1)
1304 draw_buf->buf_act = draw_buf->buf2;
1305 else
1306 draw_buf->buf_act = draw_buf->buf1;
1307 }
1308 }
1309
call_flush_cb(lv_disp_drv_t * drv,const lv_area_t * area,lv_color_t * color_p)1310 static void call_flush_cb(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_p)
1311 {
1312 REFR_TRACE("Calling flush_cb on (%d;%d)(%d;%d) area with %p image pointer", area->x1, area->y1, area->x2, area->y2,
1313 (void *)color_p);
1314
1315 lv_area_t offset_area = {
1316 .x1 = area->x1 + drv->offset_x,
1317 .y1 = area->y1 + drv->offset_y,
1318 .x2 = area->x2 + drv->offset_x,
1319 .y2 = area->y2 + drv->offset_y
1320 };
1321
1322 drv->flush_cb(drv, &offset_area, color_p);
1323 }
1324
1325 #if LV_USE_PERF_MONITOR
perf_monitor_init(perf_monitor_t * _perf_monitor)1326 static void perf_monitor_init(perf_monitor_t * _perf_monitor)
1327 {
1328 LV_ASSERT_NULL(_perf_monitor);
1329 _perf_monitor->elaps_sum = 0;
1330 _perf_monitor->fps_sum_all = 0;
1331 _perf_monitor->fps_sum_cnt = 0;
1332 _perf_monitor->frame_cnt = 0;
1333 _perf_monitor->perf_last_time = 0;
1334 _perf_monitor->perf_label = NULL;
1335 }
1336 #endif
1337
1338 #if LV_USE_MEM_MONITOR
mem_monitor_init(mem_monitor_t * _mem_monitor)1339 static void mem_monitor_init(mem_monitor_t * _mem_monitor)
1340 {
1341 LV_ASSERT_NULL(_mem_monitor);
1342 _mem_monitor->mem_last_time = 0;
1343 _mem_monitor->mem_label = NULL;
1344 }
1345 #endif
1346