From 01f35afa383e2840d1b7775f681935b22c5274c1 Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Wed, 30 Sep 2026 23:21:26 +0200 Subject: [PATCH] perf(l2): nothing drawn under the map tiles - The map screen's own fill is only painted where the map area doesn't cover it (the strip it uncovers as it drifts in); the area and the opaque status bar hide the rest. ~2 ms a frame. - With every visible cell holding a tile, the map area drops its fill and grid, which lie under the tiles anyway, and still reports that it covers what's below (coversOwnArea, shared with the frozen popup image). The Home minimap drops them the same way. ~5 ms a frame. Full map frame ~50 -> 46-48 ms on the L2. Co-Authored-By: Claude Opus 5.5 --- examples/companion_radio/ui-lvgl/HomeScreen.h | 8 +++- examples/companion_radio/ui-lvgl/MapScreen.h | 42 ++++++++++++++++++- examples/companion_radio/ui-lvgl/UITask.cpp | 33 +++++++++------ 3 files changed, 69 insertions(+), 14 deletions(-) diff --git a/examples/companion_radio/ui-lvgl/HomeScreen.h b/examples/companion_radio/ui-lvgl/HomeScreen.h index 98214d71..753d24c9 100644 --- a/examples/companion_radio/ui-lvgl/HomeScreen.h +++ b/examples/companion_radio/ui-lvgl/HomeScreen.h @@ -723,6 +723,7 @@ void UITask::homeMapLayout() { double lat = atan(sinh(M_PI * (1 - 2 * s_cy / (double)n))) * 180.0 / M_PI; s_grid = { left, top, mapview::gridStep(s_z, lat, _prefs && _prefs->units_imperial, 48, sl, sizeof(sl)) }; lv_obj_invalidate(s_area); + bool tiled = true; for (int j = 0; j < ROWS; j++) { for (int i = 0; i < COLS; i++) { lv_obj_t* cell = s_cells[j * COLS + i]; @@ -743,7 +744,11 @@ void UITask::homeMapLayout() { show = mapview::cachedAncestor(s_z, wx, ty, k); } } - if (!show) { lv_obj_add_flag(cell, LV_OBJ_FLAG_HIDDEN); continue; } + if (!show) { + lv_obj_add_flag(cell, LV_OBJ_FLAG_HIDDEN); + if (px < w && py < h) tiled = false; + continue; + } if (lv_image_get_src(img) != &show->dsc) lv_image_set_src(img, &show->dsc); lv_image_set_scale(img, LV_SCALE_NONE << k); int m = (1 << k) - 1; @@ -753,6 +758,7 @@ void UITask::homeMapLayout() { shown++; } } + mapview::showTiled(s_area, s_grid, tiled); for (int i = 0; i < s_npts; i++) { double x = mapview::lonToTileX(s_pts[i].lon / 1e6, s_z) * mapview::TILE_PX - left; double y = mapview::latToTileY(s_pts[i].lat / 1e6, s_z) * mapview::TILE_PX - top; diff --git a/examples/companion_radio/ui-lvgl/MapScreen.h b/examples/companion_radio/ui-lvgl/MapScreen.h index 04912558..b25bea45 100644 --- a/examples/companion_radio/ui-lvgl/MapScreen.h +++ b/examples/companion_radio/ui-lvgl/MapScreen.h @@ -69,6 +69,7 @@ static double latToTileY(double lat, int z) { struct Grid { double left = 0, top = 0; // world px of the view's top-left double step_px = 0; // 0: none + bool tiled = false; // every visible cell holds a tile: grid and fill are under them }; static Grid s_grid; // the main map's static lv_obj_t* s_scale_bar = nullptr; @@ -96,7 +97,7 @@ static double gridStep(int z, double lat, bool imperial, int min_px, char* lbl, // user data is the Grid. static void drawGrid(lv_event_t* e) { const Grid* g = (const Grid*)lv_event_get_user_data(e); - if (!g || g->step_px < 8) return; + if (!g || g->step_px < 8 || g->tiled) return; lv_obj_t* o = (lv_obj_t*)lv_event_get_target(e); lv_area_t a; lv_obj_get_coords(o, &a); @@ -118,6 +119,38 @@ static void drawGrid(lv_event_t* e) { } } +// A map area whose visible cells all hold a tile: its fill and grid lie +// under them (~5 ms a frame on the main map), so it drops both -- and with +// tiledCovers still tells LVGL it hides what's below, so that isn't drawn +// either. +static void showTiled(lv_obj_t* area, Grid& g, bool tiled) { + g.tiled = tiled; + lv_opa_t opa = tiled ? LV_OPA_TRANSP : LV_OPA_COVER; + if (lv_obj_get_style_bg_opa(area, LV_PART_MAIN) != opa) lv_obj_set_style_bg_opa(area, opa, 0); +} +static void tiledCovers(lv_event_t* e) { // LV_EVENT_COVER_CHECK, user data the Grid + if (((const Grid*)lv_event_get_user_data(e))->tiled) coversOwnArea(e); +} + +// The screen under the main map: the map area and the status bar hide all of +// it but a strip the area uncovers as it drifts in (Anim.h screenIn). Filled +// whole it cost ~2 ms a frame; now only what shows is. User data: the area. +static void fillAroundMap(lv_event_t* e) { + lv_obj_t* scr = (lv_obj_t*)lv_event_get_target(e); + lv_area_t s, m; + lv_obj_get_coords(scr, &s); + lv_obj_get_coords((lv_obj_t*)lv_event_get_user_data(e), &m); + s.y1 = belowStatusBar(); + const lv_area_t strips[] = { { s.x1, s.y1, s.x2, m.y1 - 1 }, { s.x1, m.y2 + 1, s.x2, s.y2 }, + { s.x1, m.y1, m.x1 - 1, m.y2 }, { m.x2 + 1, m.y1, s.x2, m.y2 } }; + lv_draw_rect_dsc_t d; + lv_draw_rect_dsc_init(&d); + d.bg_color = lv_obj_get_style_bg_color(scr, LV_PART_MAIN); + lv_layer_t* layer = lv_event_get_layer(e); + for (const lv_area_t& a : strips) + if (a.x1 <= a.x2 && a.y1 <= a.y2) lv_draw_rect(layer, &d, &a); +} + // Markers, remembered so layout can reposition them. `idx` is the Nearby row // (MK_NODE), the waypoint index (MK_WAYPOINT) or the live-share slot (MK_LIVE). enum : uint8_t { MK_NODE, MK_WAYPOINT, MK_LIVE }; @@ -347,6 +380,10 @@ void UITask::buildMap() { lv_obj_set_style_bg_color(body, lv_color_hex(0x1A1A1E), 0); // unloaded / missing tiles lv_obj_set_style_bg_opa(body, LV_OPA_COVER, 0); lv_obj_add_event_cb(body, mapview::drawGrid, LV_EVENT_DRAW_MAIN_END, &mapview::s_grid); + lv_obj_add_event_cb(body, mapview::tiledCovers, LV_EVENT_COVER_CHECK, &mapview::s_grid); + lv_obj_t* scr = lv_obj_get_screen(body); + lv_obj_set_style_bg_opa(scr, LV_OPA_TRANSP, 0); + lv_obj_add_event_cb(scr, mapview::fillAroundMap, LV_EVENT_DRAW_MAIN, body); lv_obj_add_flag(body, LV_OBJ_FLAG_CLICKABLE); lv_obj_add_event_cb(body, onMapPress, LV_EVENT_PRESSED, NULL); lv_obj_add_event_cb(body, onMapPress, LV_EVENT_PRESSING, NULL); @@ -479,6 +516,7 @@ void UITask::layoutMap() { int n = 1 << _map_z; bool have_provider = mapview::s_available; int shown = 0, missing = 0, over = 0; + bool tiled = true; _map_pending = false; for (int j = 0; j < mapview::GRID_ROWS; j++) { @@ -518,9 +556,11 @@ void UITask::layoutMap() { if (k > over && !stand_in) over = k; } else { lv_obj_add_flag(cell, LV_OBJ_FLAG_HIDDEN); + if (on_screen) tiled = false; } } } + mapview::showTiled(_map_area, mapview::s_grid, tiled); mapview::labels::layout(left, top, w, h, _map_z, have_provider && mapview::s_provider == &mapview::s_vector && mapview::s_vector.hasData(), _map_nav ? navmap::BAR_H : 0); diff --git a/examples/companion_radio/ui-lvgl/UITask.cpp b/examples/companion_radio/ui-lvgl/UITask.cpp index eaa797f5..5d13240c 100644 --- a/examples/companion_radio/ui-lvgl/UITask.cpp +++ b/examples/companion_radio/ui-lvgl/UITask.cpp @@ -11,7 +11,7 @@ SET_LOOP_TASK_STACK_SIZE(16 * 1024); #endif #include -#include // lv_cover_check_info_t (freeze::imgCovers) +#include // lv_cover_check_info_t (coversOwnArea) #include #include @@ -221,6 +221,24 @@ static lv_obj_t* dimOverlay(lv_obj_t* parent) { return o; } +// LV_EVENT_COVER_CHECK: an opaque thing LVGL can't tell is (an image, a box +// drawn by its children) hides all under its own area, and LVGL then starts +// drawing from it. +static void coversOwnArea(lv_event_t* e) { + lv_cover_check_info_t* info = (lv_cover_check_info_t*)lv_event_get_param(e); + lv_area_t a; + lv_obj_get_coords((lv_obj_t*)lv_event_get_current_target(e), &a); + const lv_area_t* r = info->area; + if (info->res != LV_COVER_RES_MASKED && r->x1 >= a.x1 && r->y1 >= a.y1 && r->x2 <= a.x2 && r->y2 <= a.y2) + info->res = LV_COVER_RES_COVER; +} + +static lv_obj_t* s_status_bar = nullptr; // on the top layer, opaque +// Where the screen shows below the status bar (its top when the bar is hidden). +static int32_t belowStatusBar() { + return s_status_bar && !lv_obj_has_flag(s_status_bar, LV_OBJ_FLAG_HIDDEN) ? lv_obj_get_y2(s_status_bar) + 1 : 0; +} + // The screen under an open popup, frozen: once the dim is up, the screen is // drawn once into an image, darkened as the dim layer darkens it, and shown // opaque on top of the screen with the dim layer cleared -- LVGL then draws @@ -248,16 +266,6 @@ namespace freeze { s_owner = s_strip = nullptr; if (s_img) lv_obj_delete(s_img); } - // The image hides all under it: LVGL then starts drawing from it. Its own - // check says no, an image widget having no background. - static void imgCovers(lv_event_t* e) { - lv_cover_check_info_t* info = (lv_cover_check_info_t*)lv_event_get_param(e); - lv_area_t a; - lv_obj_get_coords((lv_obj_t*)lv_event_get_current_target(e), &a); - const lv_area_t* r = info->area; - if (info->res != LV_COVER_RES_MASKED && r->x1 >= a.x1 && r->y1 >= a.y1 && r->x2 <= a.x2 && r->y2 <= a.y2) - info->res = LV_COVER_RES_COVER; - } static bool moving(lv_obj_t* scr) { // a screen still sliding / fading in if (lv_display_get_screen_prev(NULL)) return true; for (uint32_t i = 0; i < lv_obj_get_child_count(scr); i++) @@ -298,7 +306,7 @@ namespace freeze { lv_obj_remove_flag(s_img, LV_OBJ_FLAG_CLICKABLE); lv_obj_set_pos(s_img, 0, 0); lv_obj_add_event_cb(s_img, imgDeleted, LV_EVENT_DELETE, buf); - lv_obj_add_event_cb(s_img, imgCovers, LV_EVENT_COVER_CHECK, NULL); + lv_obj_add_event_cb(s_img, coversOwnArea, LV_EVENT_COVER_CHECK, NULL); // no background: its own check says no if (!top) lv_obj_move_foreground(overlay); // the image under the popup, over the rest s_owner = overlay; lv_obj_set_style_bg_opa(overlay, LV_OPA_TRANSP, 0); @@ -1815,6 +1823,7 @@ void UITask::buildStatusBar() { lv_obj_set_style_text_align(tl, LV_TEXT_ALIGN_CENTER, 0); lv_obj_add_flag(_toast, LV_OBJ_FLAG_HIDDEN); lv_obj_t* bar = lv_obj_create(lv_layer_top()); + s_status_bar = bar; styleOpaque(bar, theme::BG); lv_obj_remove_flag(bar, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(bar, LV_OBJ_FLAG_CLICKABLE);