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 "../lv_hal/lv_hal_tick.h"
13 #include "../lv_hal/lv_hal_disp.h"
14 #include "../lv_misc/lv_task.h"
15 #include "../lv_misc/lv_mem.h"
16 #include "../lv_misc/lv_gc.h"
17 #include "../lv_draw/lv_draw.h"
18 
19 #if defined(LV_GC_INCLUDE)
20 #include LV_GC_INCLUDE
21 #endif /* LV_ENABLE_GC */
22 
23 /*********************
24  *      DEFINES
25  *********************/
26 /* Draw translucent random colored areas on the invalidated (redrawn) areas*/
27 #define MASK_AREA_DEBUG 0
28 
29 /**********************
30  *      TYPEDEFS
31  **********************/
32 
33 /**********************
34  *  STATIC PROTOTYPES
35  **********************/
36 static void lv_refr_join_area(void);
37 static void lv_refr_areas(void);
38 static void lv_refr_area(const lv_area_t * area_p);
39 static void lv_refr_area_part(const lv_area_t * area_p);
40 static lv_obj_t * lv_refr_get_top_obj(const lv_area_t * area_p, lv_obj_t * obj);
41 static void lv_refr_obj_and_children(lv_obj_t * top_p, const lv_area_t * mask_p);
42 static void lv_refr_obj(lv_obj_t * obj, const lv_area_t * mask_ori_p);
43 static void lv_refr_vdb_flush(void);
44 
45 /**********************
46  *  STATIC VARIABLES
47  **********************/
48 static uint32_t px_num;
49 static lv_disp_t * disp_refr; /*Display being refreshed*/
50 
51 /**********************
52  *      MACROS
53  **********************/
54 
55 /**********************
56  *   GLOBAL FUNCTIONS
57  **********************/
58 
59 /**
60  * Initialize the screen refresh subsystem
61  */
lv_refr_init(void)62 void lv_refr_init(void)
63 {
64     /*Nothing to do*/
65 }
66 
67 /**
68  * Redraw the invalidated areas now.
69  * Normally the redrawing is periodically executed in `lv_task_handler` but a long blocking process
70  * can prevent the call of `lv_task_handler`. In this case if the the GUI is updated in the process
71  * (e.g. progress bar) this function can be called when the screen should be updated.
72  * @param disp pointer to display to refresh. NULL to refresh all displays.
73  */
lv_refr_now(lv_disp_t * disp)74 void lv_refr_now(lv_disp_t * disp)
75 {
76     if(disp) {
77         lv_disp_refr_task(disp->refr_task);
78     } else {
79         lv_disp_t * d;
80         d = lv_disp_get_next(NULL);
81         while(d) {
82             lv_disp_refr_task(d->refr_task);
83             d = lv_disp_get_next(d);
84         }
85     }
86 }
87 
88 /**
89  * Invalidate an area on display to redraw it
90  * @param area_p pointer to area which should be invalidated (NULL: delete the invalidated areas)
91  * @param disp pointer to display where the area should be invalidated (NULL can be used if there is
92  * only one display)
93  */
lv_inv_area(lv_disp_t * disp,const lv_area_t * area_p)94 void lv_inv_area(lv_disp_t * disp, const lv_area_t * area_p)
95 {
96     if(!disp) disp = lv_disp_get_default();
97     if(!disp) return;
98 
99     /*Clear the invalidate buffer if the parameter is NULL*/
100     if(area_p == NULL) {
101         disp->inv_p = 0;
102         return;
103     }
104 
105     lv_area_t scr_area;
106     scr_area.x1 = 0;
107     scr_area.y1 = 0;
108     scr_area.x2 = lv_disp_get_hor_res(disp) - 1;
109     scr_area.y2 = lv_disp_get_ver_res(disp) - 1;
110 
111     lv_area_t com_area;
112     bool suc;
113 
114     suc = lv_area_intersect(&com_area, area_p, &scr_area);
115 
116     /*The area is truncated to the screen*/
117     if(suc != false) {
118         if(disp->driver.rounder_cb) disp->driver.rounder_cb(&disp->driver, &com_area);
119 
120         /*Save only if this area is not in one of the saved areas*/
121         uint16_t i;
122         for(i = 0; i < disp->inv_p; i++) {
123             if(lv_area_is_in(&com_area, &disp->inv_areas[i]) != false) return;
124         }
125 
126         /*Save the area*/
127         if(disp->inv_p < LV_INV_BUF_SIZE) {
128             lv_area_copy(&disp->inv_areas[disp->inv_p], &com_area);
129         } else { /*If no place for the area add the screen*/
130             disp->inv_p = 0;
131             lv_area_copy(&disp->inv_areas[disp->inv_p], &scr_area);
132         }
133         disp->inv_p++;
134     }
135 }
136 
137 /**
138  * Get the display which is being refreshed
139  * @return the display being refreshed
140  */
lv_refr_get_disp_refreshing(void)141 lv_disp_t * lv_refr_get_disp_refreshing(void)
142 {
143     return disp_refr;
144 }
145 
146 /**
147  * Set the display which is being refreshed.
148  * It shouldn1t be used directly by the user.
149  * It can be used to trick the drawing functions about there is an active display.
150  * @param the display being refreshed
151  */
lv_refr_set_disp_refreshing(lv_disp_t * disp)152 void lv_refr_set_disp_refreshing(lv_disp_t * disp)
153 {
154     disp_refr = disp;
155 }
156 
157 /**
158  * Called periodically to handle the refreshing
159  * @param task pointer to the task itself
160  */
lv_disp_refr_task(lv_task_t * task)161 void lv_disp_refr_task(lv_task_t * task)
162 {
163     LV_LOG_TRACE("lv_refr_task: started");
164 
165     uint32_t start = lv_tick_get();
166 
167     disp_refr = task->user_data;
168 
169     lv_refr_join_area();
170 
171     lv_refr_areas();
172 
173     /*If refresh happened ...*/
174     if(disp_refr->inv_p != 0) {
175         /*In true double buffered mode copy the refreshed areas to the new VDB to keep it up to
176          * date*/
177         if(lv_disp_is_true_double_buf(disp_refr)) {
178             lv_disp_buf_t * vdb = lv_disp_get_buf(disp_refr);
179 
180             /*Flush the content of the VDB*/
181             lv_refr_vdb_flush();
182 
183             /* With true double buffering the flushing should be only the address change of the
184              * current frame buffer. Wait until the address change is ready and copy the changed
185              * content to the other frame buffer (new active VDB) to keep the buffers synchronized*/
186             while(vdb->flushing)
187                 ;
188 
189             uint8_t * buf_act = (uint8_t *)vdb->buf_act;
190             uint8_t * buf_ina = (uint8_t *)vdb->buf_act == vdb->buf1 ? vdb->buf2 : vdb->buf1;
191 
192             lv_coord_t hres = lv_disp_get_hor_res(disp_refr);
193             uint16_t a;
194             for(a = 0; a < disp_refr->inv_p; a++) {
195                 if(disp_refr->inv_area_joined[a] == 0) {
196                     lv_coord_t y;
197                     uint32_t start_offs =
198                         (hres * disp_refr->inv_areas[a].y1 + disp_refr->inv_areas[a].x1) * sizeof(lv_color_t);
199                     uint32_t line_length = lv_area_get_width(&disp_refr->inv_areas[a]) * sizeof(lv_color_t);
200 
201                     for(y = disp_refr->inv_areas[a].y1; y <= disp_refr->inv_areas[a].y2; y++) {
202                         memcpy(buf_act + start_offs, buf_ina + start_offs, line_length);
203                         start_offs += hres * sizeof(lv_color_t);
204                     }
205                 }
206             }
207         } /*End of true double buffer handling*/
208 
209         /*Clean up*/
210         memset(disp_refr->inv_areas, 0, sizeof(disp_refr->inv_areas));
211         memset(disp_refr->inv_area_joined, 0, sizeof(disp_refr->inv_area_joined));
212         disp_refr->inv_p = 0;
213 
214         /*Call monitor cb if present*/
215         if(disp_refr->driver.monitor_cb) {
216             disp_refr->driver.monitor_cb(&disp_refr->driver, lv_tick_elaps(start), px_num);
217         }
218     }
219 
220     lv_draw_free_buf();
221 
222     LV_LOG_TRACE("lv_refr_task: ready");
223 }
224 
225 /**********************
226  *   STATIC FUNCTIONS
227  **********************/
228 
229 /**
230  * Join the areas which has got common parts
231  */
lv_refr_join_area(void)232 static void lv_refr_join_area(void)
233 {
234     uint32_t join_from;
235     uint32_t join_in;
236     lv_area_t joined_area;
237     for(join_in = 0; join_in < disp_refr->inv_p; join_in++) {
238         if(disp_refr->inv_area_joined[join_in] != 0) continue;
239 
240         /*Check all areas to join them in 'join_in'*/
241         for(join_from = 0; join_from < disp_refr->inv_p; join_from++) {
242             /*Handle only unjoined areas and ignore itself*/
243             if(disp_refr->inv_area_joined[join_from] != 0 || join_in == join_from) {
244                 continue;
245             }
246 
247             /*Check if the areas are on each other*/
248             if(lv_area_is_on(&disp_refr->inv_areas[join_in], &disp_refr->inv_areas[join_from]) == false) {
249                 continue;
250             }
251 
252             lv_area_join(&joined_area, &disp_refr->inv_areas[join_in], &disp_refr->inv_areas[join_from]);
253 
254             /*Join two area only if the joined area size is smaller*/
255             if(lv_area_get_size(&joined_area) < (lv_area_get_size(&disp_refr->inv_areas[join_in]) +
256                                                  lv_area_get_size(&disp_refr->inv_areas[join_from]))) {
257                 lv_area_copy(&disp_refr->inv_areas[join_in], &joined_area);
258 
259                 /*Mark 'join_form' is joined into 'join_in'*/
260                 disp_refr->inv_area_joined[join_from] = 1;
261             }
262         }
263     }
264 }
265 
266 /**
267  * Refresh the joined areas
268  */
lv_refr_areas(void)269 static void lv_refr_areas(void)
270 {
271     px_num = 0;
272     uint32_t i;
273 
274     for(i = 0; i < disp_refr->inv_p; i++) {
275         /*Refresh the unjoined areas*/
276         if(disp_refr->inv_area_joined[i] == 0) {
277 
278             lv_refr_area(&disp_refr->inv_areas[i]);
279 
280             if(disp_refr->driver.monitor_cb) px_num += lv_area_get_size(&disp_refr->inv_areas[i]);
281         }
282     }
283 }
284 
285 /**
286  * Refresh an area if there is Virtual Display Buffer
287  * @param area_p  pointer to an area to refresh
288  */
lv_refr_area(const lv_area_t * area_p)289 static void lv_refr_area(const lv_area_t * area_p)
290 {
291     /*True double buffering: there are two screen sized buffers. Just redraw directly into a
292      * buffer*/
293     if(lv_disp_is_true_double_buf(disp_refr)) {
294         lv_disp_buf_t * vdb = lv_disp_get_buf(disp_refr);
295         vdb->area.x1        = 0;
296         vdb->area.x2        = lv_disp_get_hor_res(disp_refr) - 1;
297         vdb->area.y1        = 0;
298         vdb->area.y2        = lv_disp_get_ver_res(disp_refr) - 1;
299         lv_refr_area_part(area_p);
300     }
301     /*The buffer is smaller: refresh the area in parts*/
302     else {
303         lv_disp_buf_t * vdb = lv_disp_get_buf(disp_refr);
304         /*Calculate the max row num*/
305         lv_coord_t w = lv_area_get_width(area_p);
306         lv_coord_t h = lv_area_get_height(area_p);
307         lv_coord_t y2 =
308             area_p->y2 >= lv_disp_get_ver_res(disp_refr) ? y2 = lv_disp_get_ver_res(disp_refr) - 1 : area_p->y2;
309 
310         int32_t max_row = (uint32_t)vdb->size / w;
311 
312         if(max_row > h) max_row = h;
313 
314         /*Round down the lines of VDB if rounding is added*/
315         if(disp_refr->driver.rounder_cb) {
316             lv_area_t tmp;
317             tmp.x1 = 0;
318             tmp.x2 = 0;
319             tmp.y1 = 0;
320 
321             lv_coord_t y_tmp = max_row - 1;
322             do {
323                 tmp.y2 = y_tmp;
324                 disp_refr->driver.rounder_cb(&disp_refr->driver, &tmp);
325 
326                 /*If this height fits into `max_row` then fine*/
327                 if(lv_area_get_height(&tmp) <= max_row) break;
328 
329                 /*Decrement the height of the area until it fits into `max_row` after rounding*/
330                 y_tmp--;
331             } while(y_tmp != 0);
332 
333             if(y_tmp == 0) {
334                 LV_LOG_WARN("Can't set VDB height using the round function. (Wrong round_cb or to "
335                             "small VDB)");
336                 return;
337             } else {
338                 max_row = tmp.y2 + 1;
339             }
340         }
341 
342         /*Always use the full row*/
343         lv_coord_t row;
344         lv_coord_t row_last = 0;
345         for(row = area_p->y1; row + max_row - 1 <= y2; row += max_row) {
346             /*Calc. the next y coordinates of VDB*/
347             vdb->area.x1 = area_p->x1;
348             vdb->area.x2 = area_p->x2;
349             vdb->area.y1 = row;
350             vdb->area.y2 = row + max_row - 1;
351             if(vdb->area.y2 > y2) vdb->area.y2 = y2;
352             row_last = vdb->area.y2;
353             lv_refr_area_part(area_p);
354         }
355 
356         /*If the last y coordinates are not handled yet ...*/
357         if(y2 != row_last) {
358             /*Calc. the next y coordinates of VDB*/
359             vdb->area.x1 = area_p->x1;
360             vdb->area.x2 = area_p->x2;
361             vdb->area.y1 = row;
362             vdb->area.y2 = y2;
363 
364             /*Refresh this part too*/
365             lv_refr_area_part(area_p);
366         }
367     }
368 }
369 
370 /**
371  * Refresh a part of an area which is on the actual Virtual Display Buffer
372  * @param area_p pointer to an area to refresh
373  */
lv_refr_area_part(const lv_area_t * area_p)374 static void lv_refr_area_part(const lv_area_t * area_p)
375 {
376 
377     lv_disp_buf_t * vdb = lv_disp_get_buf(disp_refr);
378 
379     /*In non double buffered mode, before rendering the next part wait until the previous image is
380      * flushed*/
381     if(lv_disp_is_double_buf(disp_refr) == false) {
382         while(vdb->flushing)
383             ;
384     }
385 
386     lv_obj_t * top_p;
387 
388     /*Get the new mask from the original area and the act. VDB
389      It will be a part of 'area_p'*/
390     lv_area_t start_mask;
391     lv_area_intersect(&start_mask, area_p, &vdb->area);
392 
393     /*Get the most top object which is not covered by others*/
394     top_p = lv_refr_get_top_obj(&start_mask, lv_disp_get_scr_act(disp_refr));
395 
396     /*Do the refreshing from the top object*/
397     lv_refr_obj_and_children(top_p, &start_mask);
398 
399     /*Also refresh top and sys layer unconditionally*/
400     lv_refr_obj_and_children(lv_disp_get_layer_top(disp_refr), &start_mask);
401     lv_refr_obj_and_children(lv_disp_get_layer_sys(disp_refr), &start_mask);
402 
403     /* In true double buffered mode flush only once when all areas were rendered.
404      * In normal mode flush after every area */
405     if(lv_disp_is_true_double_buf(disp_refr) == false) {
406         lv_refr_vdb_flush();
407     }
408 }
409 
410 /**
411  * Search the most top object which fully covers an area
412  * @param area_p pointer to an area
413  * @param obj the first object to start the searching (typically a screen)
414  * @return
415  */
lv_refr_get_top_obj(const lv_area_t * area_p,lv_obj_t * obj)416 static lv_obj_t * lv_refr_get_top_obj(const lv_area_t * area_p, lv_obj_t * obj)
417 {
418     lv_obj_t * found_p = NULL;
419 
420     /*If this object is fully cover the draw area check the children too */
421     if(lv_area_is_in(area_p, &obj->coords) && obj->hidden == 0) {
422         lv_obj_t * i;
423         LV_LL_READ(obj->child_ll, i)
424         {
425             found_p = lv_refr_get_top_obj(area_p, i);
426 
427             /*If a children is ok then break*/
428             if(found_p != NULL) {
429                 break;
430             }
431         }
432 
433         /*If no better children check this object*/
434         if(found_p == NULL) {
435             const lv_style_t * style = lv_obj_get_style(obj);
436             if(style->body.opa == LV_OPA_COVER && obj->design_cb(obj, area_p, LV_DESIGN_COVER_CHK) != false &&
437                lv_obj_get_opa_scale(obj) == LV_OPA_COVER) {
438                 found_p = obj;
439             }
440         }
441     }
442 
443     return found_p;
444 }
445 
446 /**
447  * Make the refreshing from an object. Draw all its children and the youngers too.
448  * @param top_p pointer to an objects. Start the drawing from it.
449  * @param mask_p pointer to an area, the objects will be drawn only here
450  */
lv_refr_obj_and_children(lv_obj_t * top_p,const lv_area_t * mask_p)451 static void lv_refr_obj_and_children(lv_obj_t * top_p, const lv_area_t * mask_p)
452 {
453     /* Normally always will be a top_obj (at least the screen)
454      * but in special cases (e.g. if the screen has alpha) it won't.
455      * In this case use the screen directly */
456     if(top_p == NULL) top_p = lv_disp_get_scr_act(disp_refr);
457 
458     /*Refresh the top object and its children*/
459     lv_refr_obj(top_p, mask_p);
460 
461     /*Draw the 'younger' sibling objects because they can be on top_obj */
462     lv_obj_t * par;
463     lv_obj_t * border_p = top_p;
464 
465     par = lv_obj_get_parent(top_p);
466 
467     /*Do until not reach the screen*/
468     while(par != NULL) {
469         /*object before border_p has to be redrawn*/
470         lv_obj_t * i = lv_ll_get_prev(&(par->child_ll), border_p);
471 
472         while(i != NULL) {
473             /*Refresh the objects*/
474             lv_refr_obj(i, mask_p);
475             i = lv_ll_get_prev(&(par->child_ll), i);
476         }
477 
478         /*Call the post draw design function of the parents of the to object*/
479         par->design_cb(par, mask_p, LV_DESIGN_DRAW_POST);
480 
481         /*The new border will be there last parents,
482          *so the 'younger' brothers of parent will be refreshed*/
483         border_p = par;
484         /*Go a level deeper*/
485         par = lv_obj_get_parent(par);
486     }
487 }
488 
489 /**
490  * Refresh an object an all of its children. (Called recursively)
491  * @param obj pointer to an object to refresh
492  * @param mask_ori_p pointer to an area, the objects will be drawn only here
493  */
lv_refr_obj(lv_obj_t * obj,const lv_area_t * mask_ori_p)494 static void lv_refr_obj(lv_obj_t * obj, const lv_area_t * mask_ori_p)
495 {
496     /*Do not refresh hidden objects*/
497     if(obj->hidden != 0) return;
498 
499     bool union_ok; /* Store the return value of area_union */
500     /* Truncate the original mask to the coordinates of the parent
501      * because the parent and its children are visible only here */
502     lv_area_t obj_mask;
503     lv_area_t obj_ext_mask;
504     lv_area_t obj_area;
505     lv_coord_t ext_size = obj->ext_draw_pad;
506     lv_obj_get_coords(obj, &obj_area);
507     obj_area.x1 -= ext_size;
508     obj_area.y1 -= ext_size;
509     obj_area.x2 += ext_size;
510     obj_area.y2 += ext_size;
511     union_ok = lv_area_intersect(&obj_ext_mask, mask_ori_p, &obj_area);
512 
513     /*Draw the parent and its children only if they ore on 'mask_parent'*/
514     if(union_ok != false) {
515 
516         /* Redraw the object */
517         obj->design_cb(obj, &obj_ext_mask, LV_DESIGN_DRAW_MAIN);
518 
519 #if MASK_AREA_DEBUG
520         static lv_color_t debug_color = LV_COLOR_RED;
521         lv_draw_fill(&obj_ext_mask, &obj_ext_mask, debug_color, LV_OPA_50);
522         debug_color.full *= 17;
523         debug_color.full += 0xA1;
524 #endif
525         /*Create a new 'obj_mask' without 'ext_size' because the children can't be visible there*/
526         lv_obj_get_coords(obj, &obj_area);
527         union_ok = lv_area_intersect(&obj_mask, mask_ori_p, &obj_area);
528         if(union_ok != false) {
529             lv_area_t mask_child; /*Mask from obj and its child*/
530             lv_obj_t * child_p;
531             lv_area_t child_area;
532             LV_LL_READ_BACK(obj->child_ll, child_p)
533             {
534                 lv_obj_get_coords(child_p, &child_area);
535                 ext_size = child_p->ext_draw_pad;
536                 child_area.x1 -= ext_size;
537                 child_area.y1 -= ext_size;
538                 child_area.x2 += ext_size;
539                 child_area.y2 += ext_size;
540                 /* Get the union (common parts) of original mask (from obj)
541                  * and its child */
542                 union_ok = lv_area_intersect(&mask_child, &obj_mask, &child_area);
543 
544                 /*If the parent and the child has common area then refresh the child */
545                 if(union_ok) {
546                     /*Refresh the next children*/
547                     lv_refr_obj(child_p, &mask_child);
548                 }
549             }
550         }
551 
552         /* If all the children are redrawn make 'post draw' design */
553         obj->design_cb(obj, &obj_ext_mask, LV_DESIGN_DRAW_POST);
554     }
555 }
556 
557 /**
558  * Flush the content of the VDB
559  */
lv_refr_vdb_flush(void)560 static void lv_refr_vdb_flush(void)
561 {
562     lv_disp_buf_t * vdb = lv_disp_get_buf(disp_refr);
563 
564     /*In double buffered mode wait until the other buffer is flushed before flushing the current
565      * one*/
566     if(lv_disp_is_double_buf(disp_refr)) {
567         while(vdb->flushing)
568             ;
569     }
570 
571     vdb->flushing = 1;
572 
573     /*Flush the rendered content to the display*/
574     lv_disp_t * disp = lv_refr_get_disp_refreshing();
575     if(disp->driver.flush_cb) disp->driver.flush_cb(&disp->driver, &vdb->area, vdb->buf_act);
576 
577     if(vdb->buf1 && vdb->buf2) {
578         if(vdb->buf_act == vdb->buf1)
579             vdb->buf_act = vdb->buf2;
580         else
581             vdb->buf_act = vdb->buf1;
582     }
583 }
584