acid-drop- Hacking the planet from a LilyGo T-Deck using custom firmware |
git clone git://git.acid.vegas/acid-drop.git |
Log | Files | Refs | Archive | README | LICENSE |
lv_refr.c (35915B)
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 21 #if LV_USE_PERF_MONITOR || LV_USE_MEM_MONITOR 22 #include "../widgets/lv_label.h" 23 #endif 24 25 /********************* 26 * DEFINES 27 *********************/ 28 29 /********************** 30 * TYPEDEFS 31 **********************/ 32 typedef struct { 33 uint32_t perf_last_time; 34 uint32_t elaps_sum; 35 uint32_t frame_cnt; 36 uint32_t fps_sum_cnt; 37 uint32_t fps_sum_all; 38 #if LV_USE_LABEL 39 lv_obj_t * perf_label; 40 #endif 41 } perf_monitor_t; 42 43 typedef struct { 44 uint32_t mem_last_time; 45 #if LV_USE_LABEL 46 lv_obj_t * mem_label; 47 #endif 48 } mem_monitor_t; 49 50 /********************** 51 * STATIC PROTOTYPES 52 **********************/ 53 static void lv_refr_join_area(void); 54 static void lv_refr_areas(void); 55 static void lv_refr_area(const lv_area_t * area_p); 56 static void lv_refr_area_part(lv_draw_ctx_t * draw_ctx); 57 static lv_obj_t * lv_refr_get_top_obj(const lv_area_t * area_p, lv_obj_t * obj); 58 static void lv_refr_obj_and_children(lv_draw_ctx_t * draw_ctx, lv_obj_t * top_obj); 59 static uint32_t get_max_row(lv_disp_t * disp, lv_coord_t area_w, lv_coord_t area_h); 60 static void draw_buf_flush(lv_disp_t * disp); 61 static void call_flush_cb(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_p); 62 63 #if LV_USE_PERF_MONITOR 64 static void perf_monitor_init(perf_monitor_t * perf_monitor); 65 #endif 66 #if LV_USE_MEM_MONITOR 67 static void mem_monitor_init(mem_monitor_t * mem_monitor); 68 #endif 69 70 /********************** 71 * STATIC VARIABLES 72 **********************/ 73 static uint32_t px_num; 74 static lv_disp_t * disp_refr; /*Display being refreshed*/ 75 76 #if LV_USE_PERF_MONITOR 77 static perf_monitor_t perf_monitor; 78 #endif 79 80 #if LV_USE_MEM_MONITOR 81 static mem_monitor_t mem_monitor; 82 #endif 83 84 /********************** 85 * MACROS 86 **********************/ 87 #if LV_LOG_TRACE_DISP_REFR 88 #define REFR_TRACE(...) LV_LOG_TRACE(__VA_ARGS__) 89 #else 90 #define REFR_TRACE(...) 91 #endif 92 93 /********************** 94 * GLOBAL FUNCTIONS 95 **********************/ 96 97 /** 98 * Initialize the screen refresh subsystem 99 */ 100 void _lv_refr_init(void) 101 { 102 #if LV_USE_PERF_MONITOR 103 perf_monitor_init(&perf_monitor); 104 #endif 105 #if LV_USE_MEM_MONITOR 106 mem_monitor_init(&mem_monitor); 107 #endif 108 } 109 110 void lv_refr_now(lv_disp_t * disp) 111 { 112 lv_anim_refr_now(); 113 114 if(disp) { 115 if(disp->refr_timer) _lv_disp_refr_timer(disp->refr_timer); 116 } 117 else { 118 lv_disp_t * d; 119 d = lv_disp_get_next(NULL); 120 while(d) { 121 if(d->refr_timer) _lv_disp_refr_timer(d->refr_timer); 122 d = lv_disp_get_next(d); 123 } 124 } 125 } 126 127 void lv_refr_obj(lv_draw_ctx_t * draw_ctx, lv_obj_t * obj) 128 { 129 /*Do not refresh hidden objects*/ 130 if(lv_obj_has_flag(obj, LV_OBJ_FLAG_HIDDEN)) return; 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 142 /*If the object is visible on the current clip area OR has overflow visible draw it. 143 *With overflow visible drawing should happen to apply the masks which might affect children */ 144 bool should_draw = com_clip_res || lv_obj_has_flag(obj, LV_OBJ_FLAG_OVERFLOW_VISIBLE); 145 if(should_draw) { 146 draw_ctx->clip_area = &clip_coords_for_obj; 147 148 /*Draw the object*/ 149 lv_event_send(obj, LV_EVENT_DRAW_MAIN_BEGIN, draw_ctx); 150 lv_event_send(obj, LV_EVENT_DRAW_MAIN, draw_ctx); 151 lv_event_send(obj, LV_EVENT_DRAW_MAIN_END, draw_ctx); 152 153 #if LV_USE_REFR_DEBUG 154 lv_color_t debug_color = lv_color_make(lv_rand(0, 0xFF), lv_rand(0, 0xFF), lv_rand(0, 0xFF)); 155 lv_draw_rect_dsc_t draw_dsc; 156 lv_draw_rect_dsc_init(&draw_dsc); 157 draw_dsc.bg_color.full = debug_color.full; 158 draw_dsc.bg_opa = LV_OPA_20; 159 draw_dsc.border_width = 1; 160 draw_dsc.border_opa = LV_OPA_30; 161 draw_dsc.border_color = debug_color; 162 lv_draw_rect(draw_ctx, &draw_dsc, &obj_coords_ext); 163 #endif 164 } 165 166 /*With overflow visible keep the previous clip area to let the children visible out of this object too 167 *With not overflow visible limit the clip are to the object's coordinates to clip the children*/ 168 bool refr_children = true; 169 lv_area_t clip_coords_for_children; 170 if(lv_obj_has_flag(obj, LV_OBJ_FLAG_OVERFLOW_VISIBLE)) { 171 clip_coords_for_children = *clip_area_ori; 172 } 173 else { 174 if(!_lv_area_intersect(&clip_coords_for_children, clip_area_ori, &obj->coords)) { 175 refr_children = false; 176 } 177 } 178 179 if(refr_children) { 180 draw_ctx->clip_area = &clip_coords_for_children; 181 uint32_t i; 182 uint32_t child_cnt = lv_obj_get_child_cnt(obj); 183 for(i = 0; i < child_cnt; i++) { 184 lv_obj_t * child = obj->spec_attr->children[i]; 185 lv_refr_obj(draw_ctx, child); 186 } 187 } 188 189 /*If the object was visible on the clip area call the post draw events too*/ 190 if(should_draw) { 191 draw_ctx->clip_area = &clip_coords_for_obj; 192 193 /*If all the children are redrawn make 'post draw' draw*/ 194 lv_event_send(obj, LV_EVENT_DRAW_POST_BEGIN, draw_ctx); 195 lv_event_send(obj, LV_EVENT_DRAW_POST, draw_ctx); 196 lv_event_send(obj, LV_EVENT_DRAW_POST_END, draw_ctx); 197 } 198 199 draw_ctx->clip_area = clip_area_ori; 200 } 201 202 /** 203 * Invalidate an area on display to redraw it 204 * @param area_p pointer to area which should be invalidated (NULL: delete the invalidated areas) 205 * @param disp pointer to display where the area should be invalidated (NULL can be used if there is 206 * only one display) 207 */ 208 void _lv_inv_area(lv_disp_t * disp, const lv_area_t * area_p) 209 { 210 if(!disp) disp = lv_disp_get_default(); 211 if(!disp) return; 212 213 if(disp->rendering_in_progress) { 214 LV_LOG_ERROR("detected modifying dirty areas in render"); 215 return; 216 } 217 218 /*Clear the invalidate buffer if the parameter is NULL*/ 219 if(area_p == NULL) { 220 disp->inv_p = 0; 221 return; 222 } 223 224 lv_area_t scr_area; 225 scr_area.x1 = 0; 226 scr_area.y1 = 0; 227 scr_area.x2 = lv_disp_get_hor_res(disp) - 1; 228 scr_area.y2 = lv_disp_get_ver_res(disp) - 1; 229 230 lv_area_t com_area; 231 bool suc; 232 233 suc = _lv_area_intersect(&com_area, area_p, &scr_area); 234 if(suc == false) return; /*Out of the screen*/ 235 236 /*If there were at least 1 invalid area in full refresh mode, redraw the whole screen*/ 237 if(disp->driver->full_refresh) { 238 disp->inv_areas[0] = scr_area; 239 disp->inv_p = 1; 240 if(disp->refr_timer) lv_timer_resume(disp->refr_timer); 241 return; 242 } 243 244 if(disp->driver->rounder_cb) disp->driver->rounder_cb(disp->driver, &com_area); 245 246 /*Save only if this area is not in one of the saved areas*/ 247 uint16_t i; 248 for(i = 0; i < disp->inv_p; i++) { 249 if(_lv_area_is_in(&com_area, &disp->inv_areas[i], 0) != false) return; 250 } 251 252 /*Save the area*/ 253 if(disp->inv_p < LV_INV_BUF_SIZE) { 254 lv_area_copy(&disp->inv_areas[disp->inv_p], &com_area); 255 } 256 else { /*If no place for the area add the screen*/ 257 disp->inv_p = 0; 258 lv_area_copy(&disp->inv_areas[disp->inv_p], &scr_area); 259 } 260 disp->inv_p++; 261 if(disp->refr_timer) lv_timer_resume(disp->refr_timer); 262 } 263 264 /** 265 * Get the display which is being refreshed 266 * @return the display being refreshed 267 */ 268 lv_disp_t * _lv_refr_get_disp_refreshing(void) 269 { 270 return disp_refr; 271 } 272 273 /** 274 * Set the display which is being refreshed. 275 * It shouldn't be used directly by the user. 276 * It can be used to trick the drawing functions about there is an active display. 277 * @param the display being refreshed 278 */ 279 void _lv_refr_set_disp_refreshing(lv_disp_t * disp) 280 { 281 disp_refr = disp; 282 } 283 284 /** 285 * Called periodically to handle the refreshing 286 * @param tmr pointer to the timer itself 287 */ 288 void _lv_disp_refr_timer(lv_timer_t * tmr) 289 { 290 REFR_TRACE("begin"); 291 292 uint32_t start = lv_tick_get(); 293 volatile uint32_t elaps = 0; 294 295 if(tmr) { 296 disp_refr = tmr->user_data; 297 #if LV_USE_PERF_MONITOR == 0 && LV_USE_MEM_MONITOR == 0 298 /** 299 * Ensure the timer does not run again automatically. 300 * This is done before refreshing in case refreshing invalidates something else. 301 */ 302 lv_timer_pause(tmr); 303 #endif 304 } 305 else { 306 disp_refr = lv_disp_get_default(); 307 } 308 309 /*Refresh the screen's layout if required*/ 310 lv_obj_update_layout(disp_refr->act_scr); 311 if(disp_refr->prev_scr) lv_obj_update_layout(disp_refr->prev_scr); 312 313 lv_obj_update_layout(disp_refr->top_layer); 314 lv_obj_update_layout(disp_refr->sys_layer); 315 316 /*Do nothing if there is no active screen*/ 317 if(disp_refr->act_scr == NULL) { 318 disp_refr->inv_p = 0; 319 LV_LOG_WARN("there is no active screen"); 320 REFR_TRACE("finished"); 321 return; 322 } 323 324 lv_refr_join_area(); 325 326 lv_refr_areas(); 327 328 /*If refresh happened ...*/ 329 if(disp_refr->inv_p != 0) { 330 if(disp_refr->driver->full_refresh) { 331 lv_area_t disp_area; 332 lv_area_set(&disp_area, 0, 0, lv_disp_get_hor_res(disp_refr) - 1, lv_disp_get_ver_res(disp_refr) - 1); 333 disp_refr->driver->draw_ctx->buf_area = &disp_area; 334 draw_buf_flush(disp_refr); 335 } 336 337 /*Clean up*/ 338 lv_memset_00(disp_refr->inv_areas, sizeof(disp_refr->inv_areas)); 339 lv_memset_00(disp_refr->inv_area_joined, sizeof(disp_refr->inv_area_joined)); 340 disp_refr->inv_p = 0; 341 342 elaps = lv_tick_elaps(start); 343 /*Call monitor cb if present*/ 344 if(disp_refr->driver->monitor_cb) { 345 disp_refr->driver->monitor_cb(disp_refr->driver, elaps, px_num); 346 } 347 } 348 349 lv_mem_buf_free_all(); 350 _lv_font_clean_up_fmt_txt(); 351 352 #if LV_DRAW_COMPLEX 353 _lv_draw_mask_cleanup(); 354 #endif 355 356 #if LV_USE_PERF_MONITOR && LV_USE_LABEL 357 lv_obj_t * perf_label = perf_monitor.perf_label; 358 if(perf_label == NULL) { 359 perf_label = lv_label_create(lv_layer_sys()); 360 lv_obj_set_style_bg_opa(perf_label, LV_OPA_50, 0); 361 lv_obj_set_style_bg_color(perf_label, lv_color_black(), 0); 362 lv_obj_set_style_text_color(perf_label, lv_color_white(), 0); 363 lv_obj_set_style_pad_top(perf_label, 3, 0); 364 lv_obj_set_style_pad_bottom(perf_label, 3, 0); 365 lv_obj_set_style_pad_left(perf_label, 3, 0); 366 lv_obj_set_style_pad_right(perf_label, 3, 0); 367 lv_obj_set_style_text_align(perf_label, LV_TEXT_ALIGN_RIGHT, 0); 368 lv_label_set_text(perf_label, "?"); 369 lv_obj_align(perf_label, LV_USE_PERF_MONITOR_POS, 0, 0); 370 perf_monitor.perf_label = perf_label; 371 } 372 373 if(lv_tick_elaps(perf_monitor.perf_last_time) < 300) { 374 if(px_num > 5000) { 375 perf_monitor.elaps_sum += elaps; 376 perf_monitor.frame_cnt ++; 377 } 378 } 379 else { 380 perf_monitor.perf_last_time = lv_tick_get(); 381 uint32_t fps_limit; 382 uint32_t fps; 383 384 if(disp_refr->refr_timer) { 385 fps_limit = 1000 / disp_refr->refr_timer->period; 386 } 387 else { 388 fps_limit = 1000 / LV_DISP_DEF_REFR_PERIOD; 389 } 390 391 if(perf_monitor.elaps_sum == 0) { 392 perf_monitor.elaps_sum = 1; 393 } 394 if(perf_monitor.frame_cnt == 0) { 395 fps = fps_limit; 396 } 397 else { 398 fps = (1000 * perf_monitor.frame_cnt) / perf_monitor.elaps_sum; 399 } 400 perf_monitor.elaps_sum = 0; 401 perf_monitor.frame_cnt = 0; 402 if(fps > fps_limit) { 403 fps = fps_limit; 404 } 405 406 perf_monitor.fps_sum_all += fps; 407 perf_monitor.fps_sum_cnt ++; 408 uint32_t cpu = 100 - lv_timer_get_idle(); 409 lv_label_set_text_fmt(perf_label, "%"LV_PRIu32" FPS\n%"LV_PRIu32"%% CPU", fps, cpu); 410 } 411 #endif 412 413 #if LV_USE_MEM_MONITOR && LV_MEM_CUSTOM == 0 && LV_USE_LABEL 414 lv_obj_t * mem_label = mem_monitor.mem_label; 415 if(mem_label == NULL) { 416 mem_label = lv_label_create(lv_layer_sys()); 417 lv_obj_set_style_bg_opa(mem_label, LV_OPA_50, 0); 418 lv_obj_set_style_bg_color(mem_label, lv_color_black(), 0); 419 lv_obj_set_style_text_color(mem_label, lv_color_white(), 0); 420 lv_obj_set_style_pad_top(mem_label, 3, 0); 421 lv_obj_set_style_pad_bottom(mem_label, 3, 0); 422 lv_obj_set_style_pad_left(mem_label, 3, 0); 423 lv_obj_set_style_pad_right(mem_label, 3, 0); 424 lv_label_set_text(mem_label, "?"); 425 lv_obj_align(mem_label, LV_USE_MEM_MONITOR_POS, 0, 0); 426 mem_monitor.mem_label = mem_label; 427 } 428 429 if(lv_tick_elaps(mem_monitor.mem_last_time) > 300) { 430 mem_monitor.mem_last_time = lv_tick_get(); 431 lv_mem_monitor_t mon; 432 lv_mem_monitor(&mon); 433 uint32_t used_size = mon.total_size - mon.free_size;; 434 uint32_t used_kb = used_size / 1024; 435 uint32_t used_kb_tenth = (used_size - (used_kb * 1024)) / 102; 436 lv_label_set_text_fmt(mem_label, "%" LV_PRIu32 ".%" LV_PRIu32 " kB used (%d %%)\n" \ 437 "%d%% frag.", used_kb, used_kb_tenth, mon.used_pct, 438 mon.frag_pct); 439 } 440 #endif 441 442 REFR_TRACE("finished"); 443 } 444 445 #if LV_USE_PERF_MONITOR 446 void lv_refr_reset_fps_counter(void) 447 { 448 perf_monitor.fps_sum_all = 0; 449 perf_monitor.fps_sum_cnt = 0; 450 } 451 452 uint32_t lv_refr_get_fps_avg(void) 453 { 454 if(perf_monitor.fps_sum_cnt == 0) { 455 return 0; 456 } 457 return perf_monitor.fps_sum_all / perf_monitor.fps_sum_cnt; 458 } 459 #endif 460 461 462 /********************** 463 * STATIC FUNCTIONS 464 **********************/ 465 466 /** 467 * Join the areas which has got common parts 468 */ 469 static void lv_refr_join_area(void) 470 { 471 uint32_t join_from; 472 uint32_t join_in; 473 lv_area_t joined_area; 474 for(join_in = 0; join_in < disp_refr->inv_p; join_in++) { 475 if(disp_refr->inv_area_joined[join_in] != 0) continue; 476 477 /*Check all areas to join them in 'join_in'*/ 478 for(join_from = 0; join_from < disp_refr->inv_p; join_from++) { 479 /*Handle only unjoined areas and ignore itself*/ 480 if(disp_refr->inv_area_joined[join_from] != 0 || join_in == join_from) { 481 continue; 482 } 483 484 /*Check if the areas are on each other*/ 485 if(_lv_area_is_on(&disp_refr->inv_areas[join_in], &disp_refr->inv_areas[join_from]) == false) { 486 continue; 487 } 488 489 _lv_area_join(&joined_area, &disp_refr->inv_areas[join_in], &disp_refr->inv_areas[join_from]); 490 491 /*Join two area only if the joined area size is smaller*/ 492 if(lv_area_get_size(&joined_area) < (lv_area_get_size(&disp_refr->inv_areas[join_in]) + 493 lv_area_get_size(&disp_refr->inv_areas[join_from]))) { 494 lv_area_copy(&disp_refr->inv_areas[join_in], &joined_area); 495 496 /*Mark 'join_form' is joined into 'join_in'*/ 497 disp_refr->inv_area_joined[join_from] = 1; 498 } 499 } 500 } 501 } 502 503 /** 504 * Refresh the joined areas 505 */ 506 static void lv_refr_areas(void) 507 { 508 px_num = 0; 509 510 if(disp_refr->inv_p == 0) return; 511 512 /*Find the last area which will be drawn*/ 513 int32_t i; 514 int32_t last_i = 0; 515 for(i = disp_refr->inv_p - 1; i >= 0; i--) { 516 if(disp_refr->inv_area_joined[i] == 0) { 517 last_i = i; 518 break; 519 } 520 } 521 522 /*Notify the display driven rendering has started*/ 523 if(disp_refr->driver->render_start_cb) { 524 disp_refr->driver->render_start_cb(disp_refr->driver); 525 } 526 527 disp_refr->driver->draw_buf->last_area = 0; 528 disp_refr->driver->draw_buf->last_part = 0; 529 disp_refr->rendering_in_progress = true; 530 531 for(i = 0; i < disp_refr->inv_p; i++) { 532 /*Refresh the unjoined areas*/ 533 if(disp_refr->inv_area_joined[i] == 0) { 534 535 if(i == last_i) disp_refr->driver->draw_buf->last_area = 1; 536 disp_refr->driver->draw_buf->last_part = 0; 537 lv_refr_area(&disp_refr->inv_areas[i]); 538 539 px_num += lv_area_get_size(&disp_refr->inv_areas[i]); 540 } 541 } 542 543 disp_refr->rendering_in_progress = false; 544 } 545 546 /** 547 * Refresh an area if there is Virtual Display Buffer 548 * @param area_p pointer to an area to refresh 549 */ 550 static void lv_refr_area(const lv_area_t * area_p) 551 { 552 lv_draw_ctx_t * draw_ctx = disp_refr->driver->draw_ctx; 553 draw_ctx->buf = disp_refr->driver->draw_buf->buf_act; 554 555 /*With full refresh just redraw directly into the buffer*/ 556 /*In direct mode draw directly on the absolute coordinates of the buffer*/ 557 if(disp_refr->driver->full_refresh || disp_refr->driver->direct_mode) { 558 lv_area_t disp_area; 559 lv_area_set(&disp_area, 0, 0, lv_disp_get_hor_res(disp_refr) - 1, lv_disp_get_ver_res(disp_refr) - 1); 560 draw_ctx->buf_area = &disp_area; 561 562 if(disp_refr->driver->full_refresh) { 563 disp_refr->driver->draw_buf->last_part = 1; 564 draw_ctx->clip_area = &disp_area; 565 lv_refr_area_part(draw_ctx); 566 } 567 else { 568 disp_refr->driver->draw_buf->last_part = disp_refr->driver->draw_buf->last_area; 569 draw_ctx->clip_area = area_p; 570 lv_refr_area_part(draw_ctx); 571 } 572 return; 573 } 574 575 /*Normal refresh: draw the area in parts*/ 576 /*Calculate the max row num*/ 577 lv_coord_t w = lv_area_get_width(area_p); 578 lv_coord_t h = lv_area_get_height(area_p); 579 lv_coord_t y2 = area_p->y2 >= lv_disp_get_ver_res(disp_refr) ? 580 lv_disp_get_ver_res(disp_refr) - 1 : area_p->y2; 581 582 int32_t max_row = get_max_row(disp_refr, w, h); 583 584 lv_coord_t row; 585 lv_coord_t row_last = 0; 586 lv_area_t sub_area; 587 for(row = area_p->y1; row + max_row - 1 <= y2; row += max_row) { 588 /*Calc. the next y coordinates of draw_buf*/ 589 sub_area.x1 = area_p->x1; 590 sub_area.x2 = area_p->x2; 591 sub_area.y1 = row; 592 sub_area.y2 = row + max_row - 1; 593 draw_ctx->buf_area = &sub_area; 594 draw_ctx->clip_area = &sub_area; 595 draw_ctx->buf = disp_refr->driver->draw_buf->buf_act; 596 if(sub_area.y2 > y2) sub_area.y2 = y2; 597 row_last = sub_area.y2; 598 if(y2 == row_last) disp_refr->driver->draw_buf->last_part = 1; 599 lv_refr_area_part(draw_ctx); 600 } 601 602 /*If the last y coordinates are not handled yet ...*/ 603 if(y2 != row_last) { 604 /*Calc. the next y coordinates of draw_buf*/ 605 sub_area.x1 = area_p->x1; 606 sub_area.x2 = area_p->x2; 607 sub_area.y1 = row; 608 sub_area.y2 = y2; 609 draw_ctx->buf_area = &sub_area; 610 draw_ctx->clip_area = &sub_area; 611 draw_ctx->buf = disp_refr->driver->draw_buf->buf_act; 612 disp_refr->driver->draw_buf->last_part = 1; 613 lv_refr_area_part(draw_ctx); 614 } 615 } 616 617 static void lv_refr_area_part(lv_draw_ctx_t * draw_ctx) 618 { 619 lv_disp_draw_buf_t * draw_buf = lv_disp_get_draw_buf(disp_refr); 620 621 /* Below the `area_p` area will be redrawn into the draw buffer. 622 * In single buffered mode wait here until the buffer is freed.*/ 623 if(draw_buf->buf1 && !draw_buf->buf2) { 624 while(draw_buf->flushing) { 625 if(disp_refr->driver->wait_cb) disp_refr->driver->wait_cb(disp_refr->driver); 626 } 627 } 628 629 lv_obj_t * top_act_scr = NULL; 630 lv_obj_t * top_prev_scr = NULL; 631 632 /*Get the most top object which is not covered by others*/ 633 top_act_scr = lv_refr_get_top_obj(draw_ctx->buf_area, lv_disp_get_scr_act(disp_refr)); 634 if(disp_refr->prev_scr) { 635 top_prev_scr = lv_refr_get_top_obj(draw_ctx->buf_area, disp_refr->prev_scr); 636 } 637 638 /*Draw a display background if there is no top object*/ 639 if(top_act_scr == NULL && top_prev_scr == NULL) { 640 lv_area_t a; 641 lv_area_set(&a, 0, 0, 642 lv_disp_get_hor_res(disp_refr) - 1, lv_disp_get_ver_res(disp_refr) - 1); 643 if(draw_ctx->draw_bg) { 644 lv_draw_rect_dsc_t dsc; 645 lv_draw_rect_dsc_init(&dsc); 646 dsc.bg_img_src = disp_refr->bg_img; 647 dsc.bg_img_opa = disp_refr->bg_opa; 648 dsc.bg_color = disp_refr->bg_color; 649 dsc.bg_opa = disp_refr->bg_opa; 650 draw_ctx->draw_bg(draw_ctx, &dsc, &a); 651 } 652 else if(disp_refr->bg_img) { 653 lv_img_header_t header; 654 lv_res_t res = lv_img_decoder_get_info(disp_refr->bg_img, &header); 655 if(res == LV_RES_OK) { 656 lv_draw_img_dsc_t dsc; 657 lv_draw_img_dsc_init(&dsc); 658 dsc.opa = disp_refr->bg_opa; 659 lv_draw_img(draw_ctx, &dsc, &a, disp_refr->bg_img); 660 } 661 else { 662 LV_LOG_WARN("Can't draw the background image"); 663 } 664 } 665 else { 666 lv_draw_rect_dsc_t dsc; 667 lv_draw_rect_dsc_init(&dsc); 668 dsc.bg_color = disp_refr->bg_color; 669 dsc.bg_opa = disp_refr->bg_opa; 670 lv_draw_rect(draw_ctx, &dsc, draw_ctx->buf_area); 671 } 672 } 673 674 if(disp_refr->draw_prev_over_act) { 675 if(top_act_scr == NULL) top_act_scr = disp_refr->act_scr; 676 lv_refr_obj_and_children(draw_ctx, top_act_scr); 677 678 /*Refresh the previous screen if any*/ 679 if(disp_refr->prev_scr) { 680 if(top_prev_scr == NULL) top_prev_scr = disp_refr->prev_scr; 681 lv_refr_obj_and_children(draw_ctx, top_prev_scr); 682 } 683 } 684 else { 685 /*Refresh the previous screen if any*/ 686 if(disp_refr->prev_scr) { 687 if(top_prev_scr == NULL) top_prev_scr = disp_refr->prev_scr; 688 lv_refr_obj_and_children(draw_ctx, top_prev_scr); 689 } 690 691 if(top_act_scr == NULL) top_act_scr = disp_refr->act_scr; 692 lv_refr_obj_and_children(draw_ctx, top_act_scr); 693 } 694 695 /*Also refresh top and sys layer unconditionally*/ 696 lv_refr_obj_and_children(draw_ctx, lv_disp_get_layer_top(disp_refr)); 697 lv_refr_obj_and_children(draw_ctx, lv_disp_get_layer_sys(disp_refr)); 698 699 /*In true double buffered mode flush only once when all areas were rendered. 700 *In normal mode flush after every area*/ 701 if(disp_refr->driver->full_refresh == false) { 702 draw_buf_flush(disp_refr); 703 } 704 } 705 706 /** 707 * Search the most top object which fully covers an area 708 * @param area_p pointer to an area 709 * @param obj the first object to start the searching (typically a screen) 710 * @return 711 */ 712 static lv_obj_t * lv_refr_get_top_obj(const lv_area_t * area_p, lv_obj_t * obj) 713 { 714 lv_obj_t * found_p = NULL; 715 716 /*If this object is fully cover the draw area then check the children too*/ 717 if(_lv_area_is_in(area_p, &obj->coords, 0) && lv_obj_has_flag(obj, LV_OBJ_FLAG_HIDDEN) == false) { 718 lv_cover_check_info_t info; 719 info.res = LV_COVER_RES_COVER; 720 info.area = area_p; 721 lv_event_send(obj, LV_EVENT_COVER_CHECK, &info); 722 if(info.res == LV_COVER_RES_MASKED) return NULL; 723 724 uint32_t i; 725 uint32_t child_cnt = lv_obj_get_child_cnt(obj); 726 for(i = 0; i < child_cnt; i++) { 727 lv_obj_t * child = obj->spec_attr->children[i]; 728 found_p = lv_refr_get_top_obj(area_p, child); 729 730 /*If a children is ok then break*/ 731 if(found_p != NULL) { 732 break; 733 } 734 } 735 736 /*If no better children use this object*/ 737 if(found_p == NULL) { 738 if(info.res == LV_COVER_RES_COVER) { 739 found_p = obj; 740 } 741 } 742 } 743 744 return found_p; 745 } 746 747 /** 748 * Make the refreshing from an object. Draw all its children and the youngers too. 749 * @param top_p pointer to an objects. Start the drawing from it. 750 * @param mask_p pointer to an area, the objects will be drawn only here 751 */ 752 static void lv_refr_obj_and_children(lv_draw_ctx_t * draw_ctx, lv_obj_t * top_obj) 753 { 754 /*Normally always will be a top_obj (at least the screen) 755 *but in special cases (e.g. if the screen has alpha) it won't. 756 *In this case use the screen directly*/ 757 if(top_obj == NULL) top_obj = lv_disp_get_scr_act(disp_refr); 758 if(top_obj == NULL) return; /*Shouldn't happen*/ 759 760 /*Refresh the top object and its children*/ 761 lv_refr_obj(draw_ctx, top_obj); 762 763 /*Draw the 'younger' sibling objects because they can be on top_obj*/ 764 lv_obj_t * parent; 765 lv_obj_t * border_p = top_obj; 766 767 parent = lv_obj_get_parent(top_obj); 768 769 /*Do until not reach the screen*/ 770 while(parent != NULL) { 771 bool go = false; 772 uint32_t i; 773 uint32_t child_cnt = lv_obj_get_child_cnt(parent); 774 for(i = 0; i < child_cnt; i++) { 775 lv_obj_t * child = parent->spec_attr->children[i]; 776 if(!go) { 777 if(child == border_p) go = true; 778 } 779 else { 780 /*Refresh the objects*/ 781 lv_refr_obj(draw_ctx, child); 782 } 783 } 784 785 /*Call the post draw draw function of the parents of the to object*/ 786 lv_event_send(parent, LV_EVENT_DRAW_POST_BEGIN, (void *)draw_ctx); 787 lv_event_send(parent, LV_EVENT_DRAW_POST, (void *)draw_ctx); 788 lv_event_send(parent, LV_EVENT_DRAW_POST_END, (void *)draw_ctx); 789 790 /*The new border will be the last parents, 791 *so the 'younger' brothers of parent will be refreshed*/ 792 border_p = parent; 793 /*Go a level deeper*/ 794 parent = lv_obj_get_parent(parent); 795 } 796 } 797 798 static uint32_t get_max_row(lv_disp_t * disp, lv_coord_t area_w, lv_coord_t area_h) 799 { 800 int32_t max_row = (uint32_t)disp->driver->draw_buf->size / area_w; 801 802 if(max_row > area_h) max_row = area_h; 803 804 /*Round down the lines of draw_buf if rounding is added*/ 805 if(disp_refr->driver->rounder_cb) { 806 lv_area_t tmp; 807 tmp.x1 = 0; 808 tmp.x2 = 0; 809 tmp.y1 = 0; 810 811 lv_coord_t h_tmp = max_row; 812 do { 813 tmp.y2 = h_tmp - 1; 814 disp_refr->driver->rounder_cb(disp_refr->driver, &tmp); 815 816 /*If this height fits into `max_row` then fine*/ 817 if(lv_area_get_height(&tmp) <= max_row) break; 818 819 /*Decrement the height of the area until it fits into `max_row` after rounding*/ 820 h_tmp--; 821 } while(h_tmp > 0); 822 823 if(h_tmp <= 0) { 824 LV_LOG_WARN("Can't set draw_buf height using the round function. (Wrong round_cb or to " 825 "small draw_buf)"); 826 return 0; 827 } 828 else { 829 max_row = tmp.y2 + 1; 830 } 831 } 832 833 return max_row; 834 } 835 836 static void draw_buf_rotate_180(lv_disp_drv_t * drv, lv_area_t * area, lv_color_t * color_p) 837 { 838 lv_coord_t area_w = lv_area_get_width(area); 839 lv_coord_t area_h = lv_area_get_height(area); 840 uint32_t total = area_w * area_h; 841 /*Swap the beginning and end values*/ 842 lv_color_t tmp; 843 uint32_t i = total - 1, j = 0; 844 while(i > j) { 845 tmp = color_p[i]; 846 color_p[i] = color_p[j]; 847 color_p[j] = tmp; 848 i--; 849 j++; 850 } 851 lv_coord_t tmp_coord; 852 tmp_coord = area->y2; 853 area->y2 = drv->ver_res - area->y1 - 1; 854 area->y1 = drv->ver_res - tmp_coord - 1; 855 tmp_coord = area->x2; 856 area->x2 = drv->hor_res - area->x1 - 1; 857 area->x1 = drv->hor_res - tmp_coord - 1; 858 } 859 860 static LV_ATTRIBUTE_FAST_MEM void draw_buf_rotate_90(bool invert_i, lv_coord_t area_w, lv_coord_t area_h, 861 lv_color_t * orig_color_p, lv_color_t * rot_buf) 862 { 863 864 uint32_t invert = (area_w * area_h) - 1; 865 uint32_t initial_i = ((area_w - 1) * area_h); 866 for(lv_coord_t y = 0; y < area_h; y++) { 867 uint32_t i = initial_i + y; 868 if(invert_i) 869 i = invert - i; 870 for(lv_coord_t x = 0; x < area_w; x++) { 871 rot_buf[i] = *(orig_color_p++); 872 if(invert_i) 873 i += area_h; 874 else 875 i -= area_h; 876 } 877 } 878 } 879 880 /** 881 * Helper function for draw_buf_rotate_90_sqr. Given a list of four numbers, rotate the entire list to the left. 882 */ 883 static inline void draw_buf_rotate4(lv_color_t * a, lv_color_t * b, lv_color_t * c, lv_color_t * d) 884 { 885 lv_color_t tmp; 886 tmp = *a; 887 *a = *b; 888 *b = *c; 889 *c = *d; 890 *d = tmp; 891 } 892 893 /** 894 * Rotate a square image 90/270 degrees in place. 895 * @note inspired by https://stackoverflow.com/a/43694906 896 */ 897 static void draw_buf_rotate_90_sqr(bool is_270, lv_coord_t w, lv_color_t * color_p) 898 { 899 for(lv_coord_t i = 0; i < w / 2; i++) { 900 for(lv_coord_t j = 0; j < (w + 1) / 2; j++) { 901 lv_coord_t inv_i = (w - 1) - i; 902 lv_coord_t inv_j = (w - 1) - j; 903 if(is_270) { 904 draw_buf_rotate4( 905 &color_p[i * w + j], 906 &color_p[inv_j * w + i], 907 &color_p[inv_i * w + inv_j], 908 &color_p[j * w + inv_i] 909 ); 910 } 911 else { 912 draw_buf_rotate4( 913 &color_p[i * w + j], 914 &color_p[j * w + inv_i], 915 &color_p[inv_i * w + inv_j], 916 &color_p[inv_j * w + i] 917 ); 918 } 919 920 } 921 } 922 } 923 924 /** 925 * Rotate the draw_buf to the display's native orientation. 926 */ 927 static void draw_buf_rotate(lv_area_t * area, lv_color_t * color_p) 928 { 929 lv_disp_drv_t * drv = disp_refr->driver; 930 if(disp_refr->driver->full_refresh && drv->sw_rotate) { 931 LV_LOG_ERROR("cannot rotate a full refreshed display!"); 932 return; 933 } 934 if(drv->rotated == LV_DISP_ROT_180) { 935 draw_buf_rotate_180(drv, area, color_p); 936 call_flush_cb(drv, area, color_p); 937 } 938 else if(drv->rotated == LV_DISP_ROT_90 || drv->rotated == LV_DISP_ROT_270) { 939 /*Allocate a temporary buffer to store rotated image*/ 940 lv_color_t * rot_buf = NULL; 941 lv_disp_draw_buf_t * draw_buf = lv_disp_get_draw_buf(disp_refr); 942 lv_coord_t area_w = lv_area_get_width(area); 943 lv_coord_t area_h = lv_area_get_height(area); 944 /*Determine the maximum number of rows that can be rotated at a time*/ 945 lv_coord_t max_row = LV_MIN((lv_coord_t)((LV_DISP_ROT_MAX_BUF / sizeof(lv_color_t)) / area_w), area_h); 946 lv_coord_t init_y_off; 947 init_y_off = area->y1; 948 if(drv->rotated == LV_DISP_ROT_90) { 949 area->y2 = drv->ver_res - area->x1 - 1; 950 area->y1 = area->y2 - area_w + 1; 951 } 952 else { 953 area->y1 = area->x1; 954 area->y2 = area->y1 + area_w - 1; 955 } 956 957 /*Rotate the screen in chunks, flushing after each one*/ 958 lv_coord_t row = 0; 959 while(row < area_h) { 960 lv_coord_t height = LV_MIN(max_row, area_h - row); 961 draw_buf->flushing = 1; 962 if((row == 0) && (area_h >= area_w)) { 963 /*Rotate the initial area as a square*/ 964 height = area_w; 965 draw_buf_rotate_90_sqr(drv->rotated == LV_DISP_ROT_270, area_w, color_p); 966 if(drv->rotated == LV_DISP_ROT_90) { 967 area->x1 = init_y_off; 968 area->x2 = init_y_off + area_w - 1; 969 } 970 else { 971 area->x2 = drv->hor_res - 1 - init_y_off; 972 area->x1 = area->x2 - area_w + 1; 973 } 974 } 975 else { 976 /*Rotate other areas using a maximum buffer size*/ 977 if(rot_buf == NULL) rot_buf = lv_mem_buf_get(LV_DISP_ROT_MAX_BUF); 978 draw_buf_rotate_90(drv->rotated == LV_DISP_ROT_270, area_w, height, color_p, rot_buf); 979 980 if(drv->rotated == LV_DISP_ROT_90) { 981 area->x1 = init_y_off + row; 982 area->x2 = init_y_off + row + height - 1; 983 } 984 else { 985 area->x2 = drv->hor_res - 1 - init_y_off - row; 986 area->x1 = area->x2 - height + 1; 987 } 988 } 989 990 /* The original part (chunk of the current area) were split into more parts here. 991 * Set the original last_part flag on the last part of rotation. */ 992 if(row + height >= area_h && draw_buf->last_area && draw_buf->last_part) { 993 draw_buf->flushing_last = 1; 994 } 995 else { 996 draw_buf->flushing_last = 0; 997 } 998 999 /*Flush the completed area to the display*/ 1000 call_flush_cb(drv, area, rot_buf == NULL ? color_p : rot_buf); 1001 /*FIXME: Rotation forces legacy behavior where rendering and flushing are done serially*/ 1002 while(draw_buf->flushing) { 1003 if(drv->wait_cb) drv->wait_cb(drv); 1004 } 1005 color_p += area_w * height; 1006 row += height; 1007 } 1008 /*Free the allocated buffer at the end if necessary*/ 1009 if(rot_buf != NULL) lv_mem_buf_release(rot_buf); 1010 } 1011 } 1012 1013 /** 1014 * Flush the content of the draw buffer 1015 */ 1016 static void draw_buf_flush(lv_disp_t * disp) 1017 { 1018 lv_disp_draw_buf_t * draw_buf = lv_disp_get_draw_buf(disp_refr); 1019 1020 /*Flush the rendered content to the display*/ 1021 lv_draw_ctx_t * draw_ctx = disp->driver->draw_ctx; 1022 if(draw_ctx->wait_for_finish) draw_ctx->wait_for_finish(draw_ctx); 1023 1024 /* In double buffered mode wait until the other buffer is freed 1025 * and driver is ready to receive the new buffer */ 1026 if(draw_buf->buf1 && draw_buf->buf2) { 1027 while(draw_buf->flushing) { 1028 if(disp_refr->driver->wait_cb) disp_refr->driver->wait_cb(disp_refr->driver); 1029 } 1030 } 1031 1032 draw_buf->flushing = 1; 1033 1034 if(disp_refr->driver->draw_buf->last_area && disp_refr->driver->draw_buf->last_part) draw_buf->flushing_last = 1; 1035 else draw_buf->flushing_last = 0; 1036 1037 bool flushing_last = draw_buf->flushing_last; 1038 1039 if(disp->driver->flush_cb) { 1040 /*Rotate the buffer to the display's native orientation if necessary*/ 1041 if(disp->driver->rotated != LV_DISP_ROT_NONE && disp->driver->sw_rotate) { 1042 draw_buf_rotate(draw_ctx->buf_area, draw_ctx->buf); 1043 } 1044 else { 1045 call_flush_cb(disp->driver, draw_ctx->buf_area, draw_ctx->buf); 1046 } 1047 } 1048 /*If there are 2 buffers swap them. With direct mode swap only on the last area*/ 1049 if(draw_buf->buf1 && draw_buf->buf2 && (!disp->driver->direct_mode || flushing_last)) { 1050 if(draw_buf->buf_act == draw_buf->buf1) 1051 draw_buf->buf_act = draw_buf->buf2; 1052 else 1053 draw_buf->buf_act = draw_buf->buf1; 1054 } 1055 } 1056 1057 static void call_flush_cb(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_p) 1058 { 1059 REFR_TRACE("Calling flush_cb on (%d;%d)(%d;%d) area with %p image pointer", area->x1, area->y1, area->x2, area->y2, 1060 (void *)color_p); 1061 1062 lv_area_t offset_area = { 1063 .x1 = area->x1 + drv->offset_x, 1064 .y1 = area->y1 + drv->offset_y, 1065 .x2 = area->x2 + drv->offset_x, 1066 .y2 = area->y2 + drv->offset_y 1067 }; 1068 1069 drv->flush_cb(drv, &offset_area, color_p); 1070 } 1071 1072 #if LV_USE_PERF_MONITOR 1073 static void perf_monitor_init(perf_monitor_t * _perf_monitor) 1074 { 1075 LV_ASSERT_NULL(_perf_monitor); 1076 _perf_monitor->elaps_sum = 0; 1077 _perf_monitor->fps_sum_all = 0; 1078 _perf_monitor->fps_sum_cnt = 0; 1079 _perf_monitor->frame_cnt = 0; 1080 _perf_monitor->perf_last_time = 0; 1081 _perf_monitor->perf_label = NULL; 1082 } 1083 #endif 1084 1085 #if LV_USE_MEM_MONITOR 1086 static void mem_monitor_init(mem_monitor_t * _mem_monitor) 1087 { 1088 LV_ASSERT_NULL(_mem_monitor); 1089 _mem_monitor->mem_last_time = 0; 1090 _mem_monitor->mem_label = NULL; 1091 } 1092 #endif 1093