From 2ea0ac4416cef23b3d28bb3dc6681d974e4d64c9 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 07:27:54 +0000 Subject: [PATCH] refactor(ui-lvgl): the perf tools move out of UITask.cpp into PerfTest.h Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01VeBrxKyHHxWsmttVkUdBrj --- examples/companion_radio/ui-lvgl/PerfTest.h | 466 ++++++++++++++++++++ examples/companion_radio/ui-lvgl/UITask.cpp | 459 +------------------ examples/companion_radio/ui-lvgl/UITask.h | 1 + 3 files changed, 469 insertions(+), 457 deletions(-) create mode 100644 examples/companion_radio/ui-lvgl/PerfTest.h diff --git a/examples/companion_radio/ui-lvgl/PerfTest.h b/examples/companion_radio/ui-lvgl/PerfTest.h new file mode 100644 index 00000000..cffcbb95 --- /dev/null +++ b/examples/companion_radio/ui-lvgl/PerfTest.h @@ -0,0 +1,466 @@ +#pragma once +// Perf tools for ui-lvgl (-D UI_PERF_TEST): draw profiles, per-control timing and +// screen walks. A single-TU fragment, included by UITask.cpp where it used to sit. + +#if defined(UI_PERF_TEST) +// Perf tools (-D UI_PERF_TEST, output lines start "PERF"): each settings page +// and most screens built and drawn once, draw profiles, a popup test, then a +// walk through the screens. -D PERF_LISTS times the long lists (opened until +// all is in, scrolled, refreshed); -D PERF_LIST_MUL fills New message's +// with the few contacts there are, repeated up to 256. +// -D PERF_KEEP_USB leaves the USB popup up; +// -D PERF_WRAP_TEXT plus -Wl,--wrap= of lv_text_get_size_attributes, +// lv_layout_apply, lv_obj_get_style_prop_internal and lv_obj_send_event adds +// what layout costs per screen (text measured, flex runs, style reads, events). +// +// A full redraw of the active screen (and the top layer), timed per +// object -- its own drawing, not its children's -- the costliest listed with +// what they are, and totals by kind. +static uint32_t s_dp_own[160], s_dp_t0; +static lv_obj_t* s_dp_obj[160]; +static int s_dp_n; +static void dpCb(lv_event_t* e) { + lv_event_code_t c = lv_event_get_code(e); + int k = (int)(intptr_t)lv_event_get_user_data(e); + if (c == LV_EVENT_DRAW_MAIN_BEGIN || c == LV_EVENT_DRAW_POST_BEGIN) s_dp_t0 = micros(); + else s_dp_own[k] += micros() - s_dp_t0; +} +static const char* dpKind(lv_obj_t* o) { + const lv_obj_class_t* k = lv_obj_get_class(o); + return k == &lv_label_class ? "label" : k == &lv_button_class ? "button" : k == &lv_switch_class ? "switch" + : k == &lv_image_class ? "image" : k == &lv_slider_class ? "slider" : k == &lv_obj_class ? "obj" : "other"; +} +static void perfDrawProfile(const char* tag) { + { // the screen's entrance first: its frames timed, then done + uint32_t n = 0, us = 0, t0 = millis(); + while (lv_anim_count_running() && millis() - t0 < 1000) { + uint32_t u = micros(); + lv_timer_handler(); + u = micros() - u; + if (u > 5000) { n++; us += u; } // a pass that drew + delay(1); + } + if (n) Serial.printf("PERF draw %-8s entrance %lu frames, avg %5.1f ms\n", tag, (unsigned long)n, us / 1000.0f / n); + } + s_dp_n = 0; + memset(s_dp_own, 0, sizeof(s_dp_own)); + auto hook = [](lv_obj_t* o, void*) { + if (s_dp_n >= 160) return LV_OBJ_TREE_WALK_END; + s_dp_obj[s_dp_n] = o; + for (lv_event_code_t c : { LV_EVENT_DRAW_MAIN_BEGIN, LV_EVENT_DRAW_MAIN_END, LV_EVENT_DRAW_POST_BEGIN, LV_EVENT_DRAW_POST_END }) + lv_obj_add_event_cb(o, dpCb, c, (void*)(intptr_t)s_dp_n); + s_dp_n++; + return LV_OBJ_TREE_WALK_NEXT; + }; + lv_obj_tree_walk(lv_screen_active(), hook, nullptr); + lv_obj_tree_walk(lv_layer_top(), hook, nullptr); + const int N = 5; + uint32_t fl0 = lvport::s_flush_us, t = micros(); + for (int i = 0; i < N; i++) { lv_obj_invalidate(lv_screen_active()); lv_refr_now(NULL); } + t = micros() - t; + uint32_t fl = lvport::s_flush_us - fl0, sum = 0; + for (int i = 0; i < s_dp_n; i++) sum += s_dp_own[i]; + Serial.printf("PERF draw %-8s full %5.1f ms = flush %4.1f + objects %4.1f + the rest %4.1f (%d objects)\n", tag, + t / 1000.0f / N, fl / 1000.0f / N, sum / 1000.0f / N, (t - fl - sum) / 1000.0f / N, s_dp_n); + struct Tot { const char* k; uint32_t us; int n; } tot[8] = {}; + for (int i = 0; i < s_dp_n; i++) { + const char* k = dpKind(s_dp_obj[i]); + for (Tot& x : tot) { if (!x.k) x.k = k; if (x.k == k) { x.us += s_dp_own[i]; x.n++; break; } } + } + for (Tot& x : tot) if (x.k) Serial.printf("PERF by kind %-7s %3d objects %5.1f ms\n", x.k, x.n, x.us / 1000.0f / N); + for (int r = 0; r < 12; r++) { // the costliest, one by one + int best = -1; + for (int i = 0; i < s_dp_n; i++) if (s_dp_own[i] && (best < 0 || s_dp_own[i] > s_dp_own[best])) best = i; + if (best < 0 || s_dp_own[best] < 300 * N) break; + lv_obj_t* o = s_dp_obj[best]; + Serial.printf("PERF %5.2f ms %-6s %3ldx%-3ld radius %2ld bg_opa %3d border %ld shadow %ld clip_corner %d %.30s\n", + s_dp_own[best] / 1000.0f / N, dpKind(o), (long)lv_obj_get_width(o), (long)lv_obj_get_height(o), + (long)lv_obj_get_style_radius(o, LV_PART_MAIN), (int)lv_obj_get_style_bg_opa(o, LV_PART_MAIN), + (long)lv_obj_get_style_border_width(o, LV_PART_MAIN), (long)lv_obj_get_style_shadow_width(o, LV_PART_MAIN), + (int)lv_obj_get_style_clip_corner(o, LV_PART_MAIN), + lv_obj_check_type(o, &lv_label_class) ? lv_label_get_text(o) : ""); + s_dp_own[best] = 0; + } + for (int i = 0; i < s_dp_n; i++) lv_obj_remove_event_cb(s_dp_obj[i], dpCb); +} + +// Every switch, slider, segmented row and choice of the active screen used +// as a tap would (and put back): how long its handler took, then the frames +// until its animation settled -- count, average, longest. +struct PerfFrames { uint32_t n = 0, sum = 0, max = 0; }; +static void perfSettle(PerfFrames& f, uint32_t min_ms, uint32_t max_ms = 800) { + uint32_t t0 = millis(); + while (millis() - t0 < max_ms && (millis() - t0 < min_ms || lv_anim_count_running())) { + uint32_t u = micros(); + lv_timer_handler(); + u = micros() - u; + if (u > 3000) { f.n++; f.sum += u; if (u > f.max) f.max = u; } // a pass that drew + delay(1); + } +} +static const char* perfCtlName(lv_obj_t* ctl) { // the row's first text + struct W { lv_obj_t* skip; const char* t; } w = { ctl, "" }; + lv_obj_t* row = lv_obj_get_parent(ctl); + if (lv_obj_check_type(ctl, &lv_buttonmatrix_class) && lv_obj_get_child_count(row) < 2) return "(segmented)"; + lv_obj_tree_walk(row, [](lv_obj_t* o, void* u) { + W* w = (W*)u; + if (o == w->skip) return LV_OBJ_TREE_WALK_SKIP_CHILDREN; + if (lv_obj_check_type(o, &lv_label_class) && *lv_label_get_text(o)) { w->t = lv_label_get_text(o); return LV_OBJ_TREE_WALK_END; } + return LV_OBJ_TREE_WALK_NEXT; + }, &w); + return w.t; +} +static bool perfIsChoice(lv_obj_t* o) { + return lv_obj_get_child_count(o) == 2 && lv_obj_get_user_data(o) && lv_obj_check_type(lv_obj_get_child(o, 1), &lv_label_class) + && strcmp(lv_label_get_text(lv_obj_get_child(o, 1)), LV_SYMBOL_DOWN) == 0; +} +static int perfCollect(lv_obj_t** out, int max) { + struct C { lv_obj_t** out; int n, max; } c = { out, 0, max }; + lv_obj_tree_walk(lv_screen_active(), [](lv_obj_t* o, void* u) { + C* c = (C*)u; + if (c->n >= c->max) return LV_OBJ_TREE_WALK_END; + if (lv_obj_has_flag(o, LV_OBJ_FLAG_HIDDEN)) return LV_OBJ_TREE_WALK_SKIP_CHILDREN; + if (lv_obj_check_type(o, &lv_switch_class) || lv_obj_check_type(o, &lv_slider_class) + || lv_obj_check_type(o, &lv_buttonmatrix_class) || perfIsChoice(o)) { + c->out[c->n++] = o; + return LV_OBJ_TREE_WALK_SKIP_CHILDREN; + } + return LV_OBJ_TREE_WALK_NEXT; + }, &c); + return c.n; +} +static void perfControls(const char* tag, void (*fill_flush)()) { + static lv_obj_t* ctl[48]; + lv_obj_t* scr = lv_screen_active(); + int n = perfCollect(ctl, 48); + for (int k = 0; k < n; k++) { + lv_obj_t* o = ctl[k]; + if (!lv_obj_is_valid(o)) continue; + char name[28]; + snprintf(name, sizeof(name), "%s", perfCtlName(o)); + lv_obj_scroll_to_view_recursive(o, LV_ANIM_OFF); + PerfFrames idle; perfSettle(idle, 60); + PerfFrames f, back; + uint32_t cb = 0; + const char* kind = ""; + bool rb = false; // the screen was rebuilt: put back on the new one's control + auto rebuilt = [&]() { return lv_screen_active() != scr || !lv_obj_is_valid(o); }; + auto refind = [&]() { // the controls found again, in the same order + scr = lv_screen_active(); + fill_flush(); + layoutNow(scr); + n = perfCollect(ctl, 48); + o = k < n ? ctl[k] : nullptr; + }; + if (lv_obj_check_type(o, &lv_switch_class)) { + kind = "switch"; + auto tap = [&](PerfFrames& fr) { + lv_obj_send_event(o, LV_EVENT_PRESSED, NULL); + perfSettle(fr, 80); + uint32_t t = micros(); + lv_obj_send_event(o, LV_EVENT_RELEASED, NULL); // toggles, VALUE_CHANGED + t = micros() - t; + perfSettle(fr, 250); // past a rebuildSoon() + return t; + }; + cb = tap(f); + if (rebuilt()) { refind(); rb = true; } + if (o && lv_obj_check_type(o, &lv_switch_class)) tap(back); + } else if (lv_obj_check_type(o, &lv_slider_class)) { + kind = "slider"; + int32_t v0 = lv_slider_get_value(o), lo = lv_slider_get_min_value(o), hi = lv_slider_get_max_value(o); + lv_obj_send_event(o, LV_EVENT_PRESSED, NULL); + for (int s = 0; s <= 12; s++) { // dragged end to end, a step a frame + lv_slider_set_value(o, lo + (hi - lo) * s / 12, LV_ANIM_OFF); + uint32_t t = micros(); + lv_obj_send_event(o, LV_EVENT_VALUE_CHANGED, NULL); + t = micros() - t; + if (t > cb) cb = t; + perfSettle(f, 16, 16); + } + uint32_t t = micros(); + lv_obj_send_event(o, LV_EVENT_RELEASED, NULL); + t = micros() - t; + if (t > cb) cb = t; + perfSettle(f, 60); + lv_slider_set_value(o, v0, LV_ANIM_OFF); + lv_obj_send_event(o, LV_EVENT_VALUE_CHANGED, NULL); + lv_obj_send_event(o, LV_EVENT_RELEASED, NULL); + perfSettle(back, 60); + } else if (lv_obj_check_type(o, &lv_buttonmatrix_class)) { + kind = "segment"; + uint32_t cur = UINT32_MAX, other = UINT32_MAX, cnt = 0; + for (const char* const* m = lv_buttonmatrix_get_map(o); **m; m++) if (strcmp(*m, "\n")) cnt++; + for (uint32_t i = 0; i < cnt; i++) { + if (lv_buttonmatrix_has_button_ctrl(o, i, LV_BUTTONMATRIX_CTRL_CHECKED)) cur = i; + else if (other == UINT32_MAX) other = i; + } + auto pick = [&](uint32_t i, PerfFrames& fr) { + lv_buttonmatrix_set_selected_button(o, i); + lv_buttonmatrix_set_button_ctrl(o, i, LV_BUTTONMATRIX_CTRL_CHECKED); + uint32_t t = micros(); + lv_obj_send_event(o, LV_EVENT_VALUE_CHANGED, &i); + t = micros() - t; + perfSettle(fr, 250); + return t; + }; + if (other == UINT32_MAX) continue; + cb = pick(other, f); + if (rebuilt()) { refind(); rb = true; } + if (cur != UINT32_MAX && o && lv_obj_check_type(o, &lv_buttonmatrix_class)) pick(cur, back); + } else { + kind = "choice"; // the options popup: opened, then closed unchanged + uint32_t t = micros(); + lv_obj_send_event(o, LV_EVENT_CLICKED, NULL); + cb = micros() - t; + perfSettle(f, 120); + pickerClose(); + perfSettle(back, 120); + } + Serial.printf("PERF ctl %-7s %-7s %-24s handler %5.1f ms | frames %2lu avg %4.1f max %5.1f | back %2lu avg %4.1f max %5.1f%s\n", + tag, kind, name, cb / 1000.0f, (unsigned long)f.n, f.n ? f.sum / 1000.0f / f.n : 0.0f, f.max / 1000.0f, + (unsigned long)back.n, back.n ? back.sum / 1000.0f / back.n : 0.0f, back.max / 1000.0f, + rb ? " (screen rebuilt)" : ""); + if (o && rebuilt()) refind(); + } +} +#endif + +#if defined(UI_PERF_TEST) && defined(ESP32) +// From loop(): walks through the screens by itself and prints how long each +// display refresh took during the transition that followed (render+flush). +void UITask::perfWalk() { + { + static uint32_t next = 15000, sum_us = 0, max_us = 0, frames = 0, t0 = 0; + static uint32_t last_pass_us = 0, max_gap_us = 0; // the longest the loop was away (a blocked UI) + uint32_t now_us = micros(); + if (last_pass_us && now_us - last_pass_us > max_gap_us) max_gap_us = now_us - last_pass_us; + last_pass_us = now_us; + static int step = 0; + static const char* name = ""; + static bool hooked = false; + if (!hooked) { + hooked = true; + lv_display_add_event_cb(lv_display_get_default(), [](lv_event_t* e) { + if (lv_event_get_code(e) == LV_EVENT_REFR_START) t0 = micros(); + else { uint32_t d = micros() - t0; sum_us += d; if (d > max_us) max_us = d; frames++; } + }, LV_EVENT_REFR_START, NULL); + lv_display_add_event_cb(lv_display_get_default(), [](lv_event_t* e) { + uint32_t d = micros() - t0; sum_us += d; if (d > max_us) max_us = d; frames++; (void)e; + }, LV_EVENT_REFR_READY, NULL); + } + if (millis() >= next) { + if (*name) Serial.printf("PERF %-10s frames %2lu avg %5.1f ms max %5.1f ms\n", name, (unsigned long)frames, + frames ? sum_us / 1000.0f / frames : 0.0f, max_us / 1000.0f); + if (*name) Serial.printf("PERF flush %5.1f ms per frame, longest loop pass %5.1f ms\n", + frames ? lvport::s_flush_us / 1000.0f / frames : 0.0f, max_gap_us / 1000.0f); + sum_us = max_us = frames = 0; + lvport::s_flush_us = 0; + max_gap_us = 0; + lv_display_trigger_activity(NULL); // no sleeping mid-test + next = millis() + 1200; + if (step == 0) unlockScreen(); // a PIN lock would be drawn over everything +#ifndef PERF_KEEP_USB + usbTap(false); // nor the USB drive question ("Keep card") +#endif + if (step == 0) lv_timer_handler(); // the popup's delete is async: gone before the sweep below + if (step == 0) Serial.printf("PERF reset reason %d (4 panic, 5 int wdt, 6 task wdt, 7 wdt, 3 sw)\n", (int)esp_reset_reason()); +#if defined(ESP32) + if (step == 0) { + esp_core_dump_summary_t sum; + if (esp_core_dump_image_check() == ESP_OK && esp_core_dump_get_summary(&sum) == ESP_OK) { + Serial.printf("PERF crash task %s pc %08lx cause %lu vaddr %08lx bt:", sum.exc_task, (unsigned long)sum.exc_pc, + (unsigned long)sum.ex_info.exc_cause, (unsigned long)sum.ex_info.exc_vaddr); + for (uint32_t i = 0; i < sum.exc_bt_info.depth; i++) Serial.printf(" %08lx", (unsigned long)sum.exc_bt_info.bt[i]); + Serial.println(); + } + } +#endif + if (step == 0) { // once: every settings page and most screens, built and drawn once + auto one = [this](const char* what, int page, void (UITask::*fn)()) { + TM_RESET(); + uint32_t t = micros(); + if (fn) (this->*fn)(); else showSchemaSettings(page); + uint32_t b = micros() - t; + TM_PRINT("build"); TM_RESET(); + uint32_t tl = micros(); + layoutNow(lv_screen_active()); // layout apart from drawing + tl = micros() - tl; + TM_PRINT("layout"); TM_RESET(); + uint32_t fl0 = lvport::s_flush_us, td = micros(); + lv_refr_now(NULL); + td = micros() - td; + TM_PRINT("draw"); TM_RESET(); + uint32_t fl1 = lvport::s_flush_us - fl0; + uint32_t total = micros() - t; + uint32_t tr = micros(); // the same screen drawn again: what a first frame costs over a steady one + lv_obj_invalidate(lv_screen_active()); lv_refr_now(NULL); + tr = micros() - tr; + uint32_t n = 0; + lv_obj_tree_walk(lv_screen_active(), [](lv_obj_t*, void* u) { ++*(uint32_t*)u; return LV_OBJ_TREE_WALK_NEXT; }, &n); + uint32_t nf = 0; + lv_obj_tree_walk(lv_screen_active(), [](lv_obj_t* o, void* u) { + if (lv_obj_get_style_layout(o, LV_PART_MAIN) == LV_LAYOUT_FLEX) ++*(uint32_t*)u; + return LV_OBJ_TREE_WALK_NEXT; }, &nf); + Serial.printf("PERF flex containers %lu\n", (unsigned long)nf); + if ((!fn && (page == 7 || page == 1)) || fn == &UITask::showContacts || fn == &UITask::showNearby) { + char tag[12]; snprintf(tag, sizeof(tag), "%s%d", fn ? "list" : "page", page); + fillFlush(); // the whole page, not only its first section + perfDrawProfile(tag); + } + Serial.printf("PERF screen %-12s %2d build %5.1f ms, with first frame %5.1f ms, %3lu objects" + " | layout %5.1f, first draw %5.1f (flush %4.1f), redraw %5.1f\n", what, page, + b / 1000.0f, total / 1000.0f, (unsigned long)n, tl / 1000.0f, td / 1000.0f, fl1 / 1000.0f, tr / 1000.0f); + }; + for (int pg = 0; pg < PG_ALL; pg++) one("page", pg, nullptr); + one("radio", 0, &UITask::showRadio); + one("diag", 0, &UITask::showDiag); + one("clock", 0, &UITask::showClock); + one("compass", 0, &UITask::showCompass); + one("scopes", 0, &UITask::showScopes); + one("repeater", 0, &UITask::showRepeater); + one("bot", 0, &UITask::showBot); + one("quick", 0, &UITask::showQuickMsgs); + one("admin", 0, &UITask::showAdminPick); + one("contacts", 0, &UITask::showContacts); + one("chats", 0, &UITask::showChats); + one("nearby", 0, &UITask::showNearby); +#ifdef PERF_LISTS + { // -D PERF_LISTS: the long lists -- opened until all is built, scrolled, refreshed + auto list = [this](const char* tag, void (UITask::*fn)(), void (UITask::*again)()) { + showHome(); lv_refr_now(NULL); + size_t h0 = heap_caps_get_free_size(MALLOC_CAP_SPIRAM); + uint32_t t = micros(); + (this->*fn)(); + uint32_t b = micros() - t; + PerfFrames f; + uint32_t t0 = millis(); + while (millis() - t0 < 3000 && (fillPending() || lv_anim_count_running() || millis() - t0 < 100)) { + uint32_t u = micros(); + lv_timer_handler(); + fillTick(); + u = micros() - u; + if (u > 3000) { f.n++; f.sum += u; if (u > f.max) f.max = u; } + delay(1); + } + uint32_t ready = micros() - t; + uint32_t n = 0; + lv_obj_tree_walk(lv_screen_active(), [](lv_obj_t*, void* u) { ++*(uint32_t*)u; return LV_OBJ_TREE_WALK_NEXT; }, &n); + long kb = ((long)h0 - (long)heap_caps_get_free_size(MALLOC_CAP_SPIRAM)) / 1024; + struct S { lv_obj_t* o; int h; } s = { nullptr, 10 }; // the list: the most to scroll + lv_obj_tree_walk(lv_screen_active(), [](lv_obj_t* o, void* u) { + S* s = (S*)u; + int v = lv_obj_get_scroll_bottom(o) + lv_obj_get_scroll_y(o); + if (lv_obj_has_flag(o, LV_OBJ_FLAG_SCROLLABLE) && v > s->h) { s->h = v; s->o = o; } + return LV_OBJ_TREE_WALK_NEXT; + }, &s); + PerfFrames sc; + if (s.o) { + for (int i = 0; i < 60 && lv_obj_get_scroll_bottom(s.o) > 0; i++) { // a steady fling, 18 px a frame + lv_obj_scroll_by(s.o, 0, -18, LV_ANIM_OFF); + uint32_t u = micros(); + lv_refr_now(NULL); + u = micros() - u; + sc.n++; sc.sum += u; if (u > sc.max) sc.max = u; + } + lv_obj_scroll_to_y(s.o, 0, LV_ANIM_OFF); + } + uint32_t tr = 0; + if (again) { // what a new message / a changed node costs + lv_refr_now(NULL); + tr = micros(); + (this->*again)(); + lv_refr_now(NULL); + tr = micros() - tr; + } + Serial.printf("PERF list %-9s open %5.1f ms, all built %6.1f ms (%2lu frames avg %4.1f max %5.1f) | %4lu objects, %4ld KB" + " | scroll %2lu frames avg %4.1f max %5.1f (list %d px) | refresh %5.1f ms\n", + tag, b / 1000.0f, ready / 1000.0f, (unsigned long)f.n, f.n ? f.sum / 1000.0f / f.n : 0.0f, f.max / 1000.0f, + (unsigned long)n, kb, (unsigned long)sc.n, sc.n ? sc.sum / 1000.0f / sc.n : 0.0f, sc.max / 1000.0f, + s.h, tr / 1000.0f); + }; + bool all = _prefs->dm_show_all; + list("fav", &UITask::showContacts, nullptr); + _prefs->dm_show_all = true; // not saved + list("contacts", &UITask::showContacts, nullptr); + _prefs->dm_show_all = all; + list("chats", &UITask::showChats, &UITask::buildChats); + list("chats-new", &UITask::showChats, &UITask::refreshChats); // a new message, in place + list("nearby", &UITask::showNearby, &UITask::perfNearbyAgain); + list("admin", &UITask::showAdminPick, nullptr); + } +#endif +#ifdef PERF_CONTROLS + { // -D PERF_CONTROLS: every control on the settings screens used once (perfControls) + auto ctls = [this](const char* tag, int page, void (UITask::*fn)()) { + if (fn) (this->*fn)(); else if (page >= 0) showSchemaSettings(page); // -1: shown already + fillFlush(); + layoutNow(lv_screen_active()); + lv_refr_now(NULL); + perfControls(tag, []() { s_ui->fillFlush(); }); + }; + auto prefs = [this](const char* when) { + Serial.printf("PERF prefs %s: imperial %d repeat %d use_profile %d auto_power %d\n", when, _prefs->units_imperial, + _prefs->client_repeat, _prefs->repeater_use_profile, _prefs->tx_apc); + }; + prefs("before"); + char tag[8]; + for (int pg = 0; pg < PG_ALL; pg++) { snprintf(tag, sizeof(tag), "page%d", pg); ctls(tag, pg, nullptr); } + ctls("radio", 0, &UITask::showRadio); + ctls("rptr", 0, &UITask::showRepeater); + ctls("bot", 0, &UITask::showBot); + ctls("clock", 0, &UITask::showClock); + ctls("quick", 0, &UITask::showQuickMsgs); + showChannelEdit(0); ctls("channel", -1, nullptr); + prefs("after"); + } +#endif + showHome(); + lv_refr_now(NULL); + perfDrawProfile("home"); + { // a popup over Home, the screen under it frozen as it opened + lv_obj_t* ov = nullptr; + uint32_t t = micros(); + lv_obj_t* p = popupOpen(lv_layer_top(), POP_FIT, ov); + float took = (micros() - t) / 1000.0f; + for (int i = 0; i < 6; i++) label(p, "A popup row", THEME_FONT_BODY, theme::TEXT); + lv_anim_delete(p, NULL); + lv_obj_set_style_opa(p, LV_OPA_COVER, 0); + lv_obj_set_style_translate_y(p, 0, 0); + lv_refr_now(NULL); + uint32_t tf = micros(); + for (int i = 0; i < 5; i++) { lv_obj_invalidate(lv_screen_active()); lv_refr_now(NULL); } + float frozen = (micros() - tf) / 5000.0f; + perfDrawProfile("frozen"); + Serial.printf("PERF popup over home: full frame %.1f ms (opening with the freeze took %.1f ms, %s)\n", + frozen, took, freeze::s_img ? "frozen" : "NOT frozen"); + lv_obj_delete(ov); + Serial.printf("PERF popup closed: image %s\n", freeze::s_img ? "LEFT" : "gone"); + lv_refr_now(NULL); + } + } + if (step % 10 == 0 && step > 0) { // a static full-screen redraw of Home, for the baseline + uint32_t fl0 = lvport::s_flush_us, t = micros(); + for (int i = 0; i < 10; i++) { lv_obj_invalidate(lv_screen_active()); lv_refr_now(NULL); } + Serial.printf("PERF full redraw (Home) %5.1f ms, flush %5.1f ms\n", (micros() - t) / 10000.0f, + (lvport::s_flush_us - fl0) / 10000.0f); + } + if (step % 10 == 9) perfMap(); // the map: tiles decoded while it opened, panning, a full redraw + uint32_t tb = micros(); + switch (step++ % 10) { + case 8: name = "map"; perfMap(true); openMap(true); next = millis() + 8000; break; + case 9: name = "back-home"; back(); break; + case 0: name = "settings"; showSettings(); break; + case 1: name = "back-home"; back(); break; + case 2: name = "messages"; showChats(); break; + case 3: name = "back-home"; back(); break; + case 4: name = "nearby"; showNearby(); break; + case 5: name = "back-home"; back(); break; + case 6: name = "page-2"; setHomePage(1); break; + case 7: name = "page-1"; setHomePage(0); break; + } + Serial.printf("PERF build %-10s %5.1f ms\n", name, (micros() - tb) / 1000.0f); + } + } +} +#endif diff --git a/examples/companion_radio/ui-lvgl/UITask.cpp b/examples/companion_radio/ui-lvgl/UITask.cpp index 896d8cb2..825d7405 100644 --- a/examples/companion_radio/ui-lvgl/UITask.cpp +++ b/examples/companion_radio/ui-lvgl/UITask.cpp @@ -1388,229 +1388,7 @@ void UITask::toggleMute() { static constexpr uint32_t LOCK_OFF_MS = 30000; // the lock screen's own auto-off cap -#if defined(UI_PERF_TEST) -// Perf tools (-D UI_PERF_TEST, output lines start "PERF"): each settings page -// and most screens built and drawn once, draw profiles, a popup test, then a -// walk through the screens. -D PERF_LISTS times the long lists (opened until -// all is in, scrolled, refreshed); -D PERF_LIST_MUL fills New message's -// with the few contacts there are, repeated up to 256. -// -D PERF_KEEP_USB leaves the USB popup up; -// -D PERF_WRAP_TEXT plus -Wl,--wrap= of lv_text_get_size_attributes, -// lv_layout_apply, lv_obj_get_style_prop_internal and lv_obj_send_event adds -// what layout costs per screen (text measured, flex runs, style reads, events). -// -// A full redraw of the active screen (and the top layer), timed per -// object -- its own drawing, not its children's -- the costliest listed with -// what they are, and totals by kind. -static uint32_t s_dp_own[160], s_dp_t0; -static lv_obj_t* s_dp_obj[160]; -static int s_dp_n; -static void dpCb(lv_event_t* e) { - lv_event_code_t c = lv_event_get_code(e); - int k = (int)(intptr_t)lv_event_get_user_data(e); - if (c == LV_EVENT_DRAW_MAIN_BEGIN || c == LV_EVENT_DRAW_POST_BEGIN) s_dp_t0 = micros(); - else s_dp_own[k] += micros() - s_dp_t0; -} -static const char* dpKind(lv_obj_t* o) { - const lv_obj_class_t* k = lv_obj_get_class(o); - return k == &lv_label_class ? "label" : k == &lv_button_class ? "button" : k == &lv_switch_class ? "switch" - : k == &lv_image_class ? "image" : k == &lv_slider_class ? "slider" : k == &lv_obj_class ? "obj" : "other"; -} -static void perfDrawProfile(const char* tag) { - { // the screen's entrance first: its frames timed, then done - uint32_t n = 0, us = 0, t0 = millis(); - while (lv_anim_count_running() && millis() - t0 < 1000) { - uint32_t u = micros(); - lv_timer_handler(); - u = micros() - u; - if (u > 5000) { n++; us += u; } // a pass that drew - delay(1); - } - if (n) Serial.printf("PERF draw %-8s entrance %lu frames, avg %5.1f ms\n", tag, (unsigned long)n, us / 1000.0f / n); - } - s_dp_n = 0; - memset(s_dp_own, 0, sizeof(s_dp_own)); - auto hook = [](lv_obj_t* o, void*) { - if (s_dp_n >= 160) return LV_OBJ_TREE_WALK_END; - s_dp_obj[s_dp_n] = o; - for (lv_event_code_t c : { LV_EVENT_DRAW_MAIN_BEGIN, LV_EVENT_DRAW_MAIN_END, LV_EVENT_DRAW_POST_BEGIN, LV_EVENT_DRAW_POST_END }) - lv_obj_add_event_cb(o, dpCb, c, (void*)(intptr_t)s_dp_n); - s_dp_n++; - return LV_OBJ_TREE_WALK_NEXT; - }; - lv_obj_tree_walk(lv_screen_active(), hook, nullptr); - lv_obj_tree_walk(lv_layer_top(), hook, nullptr); - const int N = 5; - uint32_t fl0 = lvport::s_flush_us, t = micros(); - for (int i = 0; i < N; i++) { lv_obj_invalidate(lv_screen_active()); lv_refr_now(NULL); } - t = micros() - t; - uint32_t fl = lvport::s_flush_us - fl0, sum = 0; - for (int i = 0; i < s_dp_n; i++) sum += s_dp_own[i]; - Serial.printf("PERF draw %-8s full %5.1f ms = flush %4.1f + objects %4.1f + the rest %4.1f (%d objects)\n", tag, - t / 1000.0f / N, fl / 1000.0f / N, sum / 1000.0f / N, (t - fl - sum) / 1000.0f / N, s_dp_n); - struct Tot { const char* k; uint32_t us; int n; } tot[8] = {}; - for (int i = 0; i < s_dp_n; i++) { - const char* k = dpKind(s_dp_obj[i]); - for (Tot& x : tot) { if (!x.k) x.k = k; if (x.k == k) { x.us += s_dp_own[i]; x.n++; break; } } - } - for (Tot& x : tot) if (x.k) Serial.printf("PERF by kind %-7s %3d objects %5.1f ms\n", x.k, x.n, x.us / 1000.0f / N); - for (int r = 0; r < 12; r++) { // the costliest, one by one - int best = -1; - for (int i = 0; i < s_dp_n; i++) if (s_dp_own[i] && (best < 0 || s_dp_own[i] > s_dp_own[best])) best = i; - if (best < 0 || s_dp_own[best] < 300 * N) break; - lv_obj_t* o = s_dp_obj[best]; - Serial.printf("PERF %5.2f ms %-6s %3ldx%-3ld radius %2ld bg_opa %3d border %ld shadow %ld clip_corner %d %.30s\n", - s_dp_own[best] / 1000.0f / N, dpKind(o), (long)lv_obj_get_width(o), (long)lv_obj_get_height(o), - (long)lv_obj_get_style_radius(o, LV_PART_MAIN), (int)lv_obj_get_style_bg_opa(o, LV_PART_MAIN), - (long)lv_obj_get_style_border_width(o, LV_PART_MAIN), (long)lv_obj_get_style_shadow_width(o, LV_PART_MAIN), - (int)lv_obj_get_style_clip_corner(o, LV_PART_MAIN), - lv_obj_check_type(o, &lv_label_class) ? lv_label_get_text(o) : ""); - s_dp_own[best] = 0; - } - for (int i = 0; i < s_dp_n; i++) lv_obj_remove_event_cb(s_dp_obj[i], dpCb); -} - -// Every switch, slider, segmented row and choice of the active screen used -// as a tap would (and put back): how long its handler took, then the frames -// until its animation settled -- count, average, longest. -struct PerfFrames { uint32_t n = 0, sum = 0, max = 0; }; -static void perfSettle(PerfFrames& f, uint32_t min_ms, uint32_t max_ms = 800) { - uint32_t t0 = millis(); - while (millis() - t0 < max_ms && (millis() - t0 < min_ms || lv_anim_count_running())) { - uint32_t u = micros(); - lv_timer_handler(); - u = micros() - u; - if (u > 3000) { f.n++; f.sum += u; if (u > f.max) f.max = u; } // a pass that drew - delay(1); - } -} -static const char* perfCtlName(lv_obj_t* ctl) { // the row's first text - struct W { lv_obj_t* skip; const char* t; } w = { ctl, "" }; - lv_obj_t* row = lv_obj_get_parent(ctl); - if (lv_obj_check_type(ctl, &lv_buttonmatrix_class) && lv_obj_get_child_count(row) < 2) return "(segmented)"; - lv_obj_tree_walk(row, [](lv_obj_t* o, void* u) { - W* w = (W*)u; - if (o == w->skip) return LV_OBJ_TREE_WALK_SKIP_CHILDREN; - if (lv_obj_check_type(o, &lv_label_class) && *lv_label_get_text(o)) { w->t = lv_label_get_text(o); return LV_OBJ_TREE_WALK_END; } - return LV_OBJ_TREE_WALK_NEXT; - }, &w); - return w.t; -} -static bool perfIsChoice(lv_obj_t* o) { - return lv_obj_get_child_count(o) == 2 && lv_obj_get_user_data(o) && lv_obj_check_type(lv_obj_get_child(o, 1), &lv_label_class) - && strcmp(lv_label_get_text(lv_obj_get_child(o, 1)), LV_SYMBOL_DOWN) == 0; -} -static int perfCollect(lv_obj_t** out, int max) { - struct C { lv_obj_t** out; int n, max; } c = { out, 0, max }; - lv_obj_tree_walk(lv_screen_active(), [](lv_obj_t* o, void* u) { - C* c = (C*)u; - if (c->n >= c->max) return LV_OBJ_TREE_WALK_END; - if (lv_obj_has_flag(o, LV_OBJ_FLAG_HIDDEN)) return LV_OBJ_TREE_WALK_SKIP_CHILDREN; - if (lv_obj_check_type(o, &lv_switch_class) || lv_obj_check_type(o, &lv_slider_class) - || lv_obj_check_type(o, &lv_buttonmatrix_class) || perfIsChoice(o)) { - c->out[c->n++] = o; - return LV_OBJ_TREE_WALK_SKIP_CHILDREN; - } - return LV_OBJ_TREE_WALK_NEXT; - }, &c); - return c.n; -} -static void perfControls(const char* tag, void (*fill_flush)()) { - static lv_obj_t* ctl[48]; - lv_obj_t* scr = lv_screen_active(); - int n = perfCollect(ctl, 48); - for (int k = 0; k < n; k++) { - lv_obj_t* o = ctl[k]; - if (!lv_obj_is_valid(o)) continue; - char name[28]; - snprintf(name, sizeof(name), "%s", perfCtlName(o)); - lv_obj_scroll_to_view_recursive(o, LV_ANIM_OFF); - PerfFrames idle; perfSettle(idle, 60); - PerfFrames f, back; - uint32_t cb = 0; - const char* kind = ""; - bool rb = false; // the screen was rebuilt: put back on the new one's control - auto rebuilt = [&]() { return lv_screen_active() != scr || !lv_obj_is_valid(o); }; - auto refind = [&]() { // the controls found again, in the same order - scr = lv_screen_active(); - fill_flush(); - layoutNow(scr); - n = perfCollect(ctl, 48); - o = k < n ? ctl[k] : nullptr; - }; - if (lv_obj_check_type(o, &lv_switch_class)) { - kind = "switch"; - auto tap = [&](PerfFrames& fr) { - lv_obj_send_event(o, LV_EVENT_PRESSED, NULL); - perfSettle(fr, 80); - uint32_t t = micros(); - lv_obj_send_event(o, LV_EVENT_RELEASED, NULL); // toggles, VALUE_CHANGED - t = micros() - t; - perfSettle(fr, 250); // past a rebuildSoon() - return t; - }; - cb = tap(f); - if (rebuilt()) { refind(); rb = true; } - if (o && lv_obj_check_type(o, &lv_switch_class)) tap(back); - } else if (lv_obj_check_type(o, &lv_slider_class)) { - kind = "slider"; - int32_t v0 = lv_slider_get_value(o), lo = lv_slider_get_min_value(o), hi = lv_slider_get_max_value(o); - lv_obj_send_event(o, LV_EVENT_PRESSED, NULL); - for (int s = 0; s <= 12; s++) { // dragged end to end, a step a frame - lv_slider_set_value(o, lo + (hi - lo) * s / 12, LV_ANIM_OFF); - uint32_t t = micros(); - lv_obj_send_event(o, LV_EVENT_VALUE_CHANGED, NULL); - t = micros() - t; - if (t > cb) cb = t; - perfSettle(f, 16, 16); - } - uint32_t t = micros(); - lv_obj_send_event(o, LV_EVENT_RELEASED, NULL); - t = micros() - t; - if (t > cb) cb = t; - perfSettle(f, 60); - lv_slider_set_value(o, v0, LV_ANIM_OFF); - lv_obj_send_event(o, LV_EVENT_VALUE_CHANGED, NULL); - lv_obj_send_event(o, LV_EVENT_RELEASED, NULL); - perfSettle(back, 60); - } else if (lv_obj_check_type(o, &lv_buttonmatrix_class)) { - kind = "segment"; - uint32_t cur = UINT32_MAX, other = UINT32_MAX, cnt = 0; - for (const char* const* m = lv_buttonmatrix_get_map(o); **m; m++) if (strcmp(*m, "\n")) cnt++; - for (uint32_t i = 0; i < cnt; i++) { - if (lv_buttonmatrix_has_button_ctrl(o, i, LV_BUTTONMATRIX_CTRL_CHECKED)) cur = i; - else if (other == UINT32_MAX) other = i; - } - auto pick = [&](uint32_t i, PerfFrames& fr) { - lv_buttonmatrix_set_selected_button(o, i); - lv_buttonmatrix_set_button_ctrl(o, i, LV_BUTTONMATRIX_CTRL_CHECKED); - uint32_t t = micros(); - lv_obj_send_event(o, LV_EVENT_VALUE_CHANGED, &i); - t = micros() - t; - perfSettle(fr, 250); - return t; - }; - if (other == UINT32_MAX) continue; - cb = pick(other, f); - if (rebuilt()) { refind(); rb = true; } - if (cur != UINT32_MAX && o && lv_obj_check_type(o, &lv_buttonmatrix_class)) pick(cur, back); - } else { - kind = "choice"; // the options popup: opened, then closed unchanged - uint32_t t = micros(); - lv_obj_send_event(o, LV_EVENT_CLICKED, NULL); - cb = micros() - t; - perfSettle(f, 120); - pickerClose(); - perfSettle(back, 120); - } - Serial.printf("PERF ctl %-7s %-7s %-24s handler %5.1f ms | frames %2lu avg %4.1f max %5.1f | back %2lu avg %4.1f max %5.1f%s\n", - tag, kind, name, cb / 1000.0f, (unsigned long)f.n, f.n ? f.sum / 1000.0f / f.n : 0.0f, f.max / 1000.0f, - (unsigned long)back.n, back.n ? back.sum / 1000.0f / back.n : 0.0f, back.max / 1000.0f, - rb ? " (screen rebuilt)" : ""); - if (o && rebuilt()) refind(); - } -} -#endif +#include "PerfTest.h" // -D UI_PERF_TEST only: draw profiles and control timing #ifdef PERF_WRAP_TEXT #include @@ -1744,240 +1522,7 @@ void UITask::loop() { } #endif #if defined(UI_PERF_TEST) && defined(ESP32) - // -D UI_PERF_TEST: walks through screens by itself and prints how long each - // display refresh took during the transition that followed (render+flush). - { - static uint32_t next = 15000, sum_us = 0, max_us = 0, frames = 0, t0 = 0; - static uint32_t last_pass_us = 0, max_gap_us = 0; // the longest the loop was away (a blocked UI) - uint32_t now_us = micros(); - if (last_pass_us && now_us - last_pass_us > max_gap_us) max_gap_us = now_us - last_pass_us; - last_pass_us = now_us; - static int step = 0; - static const char* name = ""; - static bool hooked = false; - if (!hooked) { - hooked = true; - lv_display_add_event_cb(lv_display_get_default(), [](lv_event_t* e) { - if (lv_event_get_code(e) == LV_EVENT_REFR_START) t0 = micros(); - else { uint32_t d = micros() - t0; sum_us += d; if (d > max_us) max_us = d; frames++; } - }, LV_EVENT_REFR_START, NULL); - lv_display_add_event_cb(lv_display_get_default(), [](lv_event_t* e) { - uint32_t d = micros() - t0; sum_us += d; if (d > max_us) max_us = d; frames++; (void)e; - }, LV_EVENT_REFR_READY, NULL); - } - if (millis() >= next) { - if (*name) Serial.printf("PERF %-10s frames %2lu avg %5.1f ms max %5.1f ms\n", name, (unsigned long)frames, - frames ? sum_us / 1000.0f / frames : 0.0f, max_us / 1000.0f); - if (*name) Serial.printf("PERF flush %5.1f ms per frame, longest loop pass %5.1f ms\n", - frames ? lvport::s_flush_us / 1000.0f / frames : 0.0f, max_gap_us / 1000.0f); - sum_us = max_us = frames = 0; - lvport::s_flush_us = 0; - max_gap_us = 0; - lv_display_trigger_activity(NULL); // no sleeping mid-test - next = millis() + 1200; - if (step == 0) unlockScreen(); // a PIN lock would be drawn over everything -#ifndef PERF_KEEP_USB - usbTap(false); // nor the USB drive question ("Keep card") -#endif - if (step == 0) lv_timer_handler(); // the popup's delete is async: gone before the sweep below - if (step == 0) Serial.printf("PERF reset reason %d (4 panic, 5 int wdt, 6 task wdt, 7 wdt, 3 sw)\n", (int)esp_reset_reason()); -#if defined(ESP32) - if (step == 0) { - esp_core_dump_summary_t sum; - if (esp_core_dump_image_check() == ESP_OK && esp_core_dump_get_summary(&sum) == ESP_OK) { - Serial.printf("PERF crash task %s pc %08lx cause %lu vaddr %08lx bt:", sum.exc_task, (unsigned long)sum.exc_pc, - (unsigned long)sum.ex_info.exc_cause, (unsigned long)sum.ex_info.exc_vaddr); - for (uint32_t i = 0; i < sum.exc_bt_info.depth; i++) Serial.printf(" %08lx", (unsigned long)sum.exc_bt_info.bt[i]); - Serial.println(); - } - } -#endif - if (step == 0) { // once: every settings page and most screens, built and drawn once - auto one = [this](const char* what, int page, void (UITask::*fn)()) { - TM_RESET(); - uint32_t t = micros(); - if (fn) (this->*fn)(); else showSchemaSettings(page); - uint32_t b = micros() - t; - TM_PRINT("build"); TM_RESET(); - uint32_t tl = micros(); - layoutNow(lv_screen_active()); // layout apart from drawing - tl = micros() - tl; - TM_PRINT("layout"); TM_RESET(); - uint32_t fl0 = lvport::s_flush_us, td = micros(); - lv_refr_now(NULL); - td = micros() - td; - TM_PRINT("draw"); TM_RESET(); - uint32_t fl1 = lvport::s_flush_us - fl0; - uint32_t total = micros() - t; - uint32_t tr = micros(); // the same screen drawn again: what a first frame costs over a steady one - lv_obj_invalidate(lv_screen_active()); lv_refr_now(NULL); - tr = micros() - tr; - uint32_t n = 0; - lv_obj_tree_walk(lv_screen_active(), [](lv_obj_t*, void* u) { ++*(uint32_t*)u; return LV_OBJ_TREE_WALK_NEXT; }, &n); - uint32_t nf = 0; - lv_obj_tree_walk(lv_screen_active(), [](lv_obj_t* o, void* u) { - if (lv_obj_get_style_layout(o, LV_PART_MAIN) == LV_LAYOUT_FLEX) ++*(uint32_t*)u; - return LV_OBJ_TREE_WALK_NEXT; }, &nf); - Serial.printf("PERF flex containers %lu\n", (unsigned long)nf); - if ((!fn && (page == 7 || page == 1)) || fn == &UITask::showContacts || fn == &UITask::showNearby) { - char tag[12]; snprintf(tag, sizeof(tag), "%s%d", fn ? "list" : "page", page); - fillFlush(); // the whole page, not only its first section - perfDrawProfile(tag); - } - Serial.printf("PERF screen %-12s %2d build %5.1f ms, with first frame %5.1f ms, %3lu objects" - " | layout %5.1f, first draw %5.1f (flush %4.1f), redraw %5.1f\n", what, page, - b / 1000.0f, total / 1000.0f, (unsigned long)n, tl / 1000.0f, td / 1000.0f, fl1 / 1000.0f, tr / 1000.0f); - }; - for (int pg = 0; pg < PG_ALL; pg++) one("page", pg, nullptr); - one("radio", 0, &UITask::showRadio); - one("diag", 0, &UITask::showDiag); - one("clock", 0, &UITask::showClock); - one("compass", 0, &UITask::showCompass); - one("scopes", 0, &UITask::showScopes); - one("repeater", 0, &UITask::showRepeater); - one("bot", 0, &UITask::showBot); - one("quick", 0, &UITask::showQuickMsgs); - one("admin", 0, &UITask::showAdminPick); - one("contacts", 0, &UITask::showContacts); - one("chats", 0, &UITask::showChats); - one("nearby", 0, &UITask::showNearby); -#ifdef PERF_LISTS - { // -D PERF_LISTS: the long lists -- opened until all is built, scrolled, refreshed - auto list = [this](const char* tag, void (UITask::*fn)(), void (UITask::*again)()) { - showHome(); lv_refr_now(NULL); - size_t h0 = heap_caps_get_free_size(MALLOC_CAP_SPIRAM); - uint32_t t = micros(); - (this->*fn)(); - uint32_t b = micros() - t; - PerfFrames f; - uint32_t t0 = millis(); - while (millis() - t0 < 3000 && (fillPending() || lv_anim_count_running() || millis() - t0 < 100)) { - uint32_t u = micros(); - lv_timer_handler(); - fillTick(); - u = micros() - u; - if (u > 3000) { f.n++; f.sum += u; if (u > f.max) f.max = u; } - delay(1); - } - uint32_t ready = micros() - t; - uint32_t n = 0; - lv_obj_tree_walk(lv_screen_active(), [](lv_obj_t*, void* u) { ++*(uint32_t*)u; return LV_OBJ_TREE_WALK_NEXT; }, &n); - long kb = ((long)h0 - (long)heap_caps_get_free_size(MALLOC_CAP_SPIRAM)) / 1024; - struct S { lv_obj_t* o; int h; } s = { nullptr, 10 }; // the list: the most to scroll - lv_obj_tree_walk(lv_screen_active(), [](lv_obj_t* o, void* u) { - S* s = (S*)u; - int v = lv_obj_get_scroll_bottom(o) + lv_obj_get_scroll_y(o); - if (lv_obj_has_flag(o, LV_OBJ_FLAG_SCROLLABLE) && v > s->h) { s->h = v; s->o = o; } - return LV_OBJ_TREE_WALK_NEXT; - }, &s); - PerfFrames sc; - if (s.o) { - for (int i = 0; i < 60 && lv_obj_get_scroll_bottom(s.o) > 0; i++) { // a steady fling, 18 px a frame - lv_obj_scroll_by(s.o, 0, -18, LV_ANIM_OFF); - uint32_t u = micros(); - lv_refr_now(NULL); - u = micros() - u; - sc.n++; sc.sum += u; if (u > sc.max) sc.max = u; - } - lv_obj_scroll_to_y(s.o, 0, LV_ANIM_OFF); - } - uint32_t tr = 0; - if (again) { // what a new message / a changed node costs - lv_refr_now(NULL); - tr = micros(); - (this->*again)(); - lv_refr_now(NULL); - tr = micros() - tr; - } - Serial.printf("PERF list %-9s open %5.1f ms, all built %6.1f ms (%2lu frames avg %4.1f max %5.1f) | %4lu objects, %4ld KB" - " | scroll %2lu frames avg %4.1f max %5.1f (list %d px) | refresh %5.1f ms\n", - tag, b / 1000.0f, ready / 1000.0f, (unsigned long)f.n, f.n ? f.sum / 1000.0f / f.n : 0.0f, f.max / 1000.0f, - (unsigned long)n, kb, (unsigned long)sc.n, sc.n ? sc.sum / 1000.0f / sc.n : 0.0f, sc.max / 1000.0f, - s.h, tr / 1000.0f); - }; - bool all = _prefs->dm_show_all; - list("fav", &UITask::showContacts, nullptr); - _prefs->dm_show_all = true; // not saved - list("contacts", &UITask::showContacts, nullptr); - _prefs->dm_show_all = all; - list("chats", &UITask::showChats, &UITask::buildChats); - list("chats-new", &UITask::showChats, &UITask::refreshChats); // a new message, in place - list("nearby", &UITask::showNearby, &UITask::perfNearbyAgain); - list("admin", &UITask::showAdminPick, nullptr); - } -#endif -#ifdef PERF_CONTROLS - { // -D PERF_CONTROLS: every control on the settings screens used once (perfControls) - auto ctls = [this](const char* tag, int page, void (UITask::*fn)()) { - if (fn) (this->*fn)(); else if (page >= 0) showSchemaSettings(page); // -1: shown already - fillFlush(); - layoutNow(lv_screen_active()); - lv_refr_now(NULL); - perfControls(tag, []() { s_ui->fillFlush(); }); - }; - auto prefs = [this](const char* when) { - Serial.printf("PERF prefs %s: imperial %d repeat %d use_profile %d auto_power %d\n", when, _prefs->units_imperial, - _prefs->client_repeat, _prefs->repeater_use_profile, _prefs->tx_apc); - }; - prefs("before"); - char tag[8]; - for (int pg = 0; pg < PG_ALL; pg++) { snprintf(tag, sizeof(tag), "page%d", pg); ctls(tag, pg, nullptr); } - ctls("radio", 0, &UITask::showRadio); - ctls("rptr", 0, &UITask::showRepeater); - ctls("bot", 0, &UITask::showBot); - ctls("clock", 0, &UITask::showClock); - ctls("quick", 0, &UITask::showQuickMsgs); - showChannelEdit(0); ctls("channel", -1, nullptr); - prefs("after"); - } -#endif - showHome(); - lv_refr_now(NULL); - perfDrawProfile("home"); - { // a popup over Home, the screen under it frozen as it opened - lv_obj_t* ov = nullptr; - uint32_t t = micros(); - lv_obj_t* p = popupOpen(lv_layer_top(), POP_FIT, ov); - float took = (micros() - t) / 1000.0f; - for (int i = 0; i < 6; i++) label(p, "A popup row", THEME_FONT_BODY, theme::TEXT); - lv_anim_delete(p, NULL); - lv_obj_set_style_opa(p, LV_OPA_COVER, 0); - lv_obj_set_style_translate_y(p, 0, 0); - lv_refr_now(NULL); - uint32_t tf = micros(); - for (int i = 0; i < 5; i++) { lv_obj_invalidate(lv_screen_active()); lv_refr_now(NULL); } - float frozen = (micros() - tf) / 5000.0f; - perfDrawProfile("frozen"); - Serial.printf("PERF popup over home: full frame %.1f ms (opening with the freeze took %.1f ms, %s)\n", - frozen, took, freeze::s_img ? "frozen" : "NOT frozen"); - lv_obj_delete(ov); - Serial.printf("PERF popup closed: image %s\n", freeze::s_img ? "LEFT" : "gone"); - lv_refr_now(NULL); - } - } - if (step % 10 == 0 && step > 0) { // a static full-screen redraw of Home, for the baseline - uint32_t fl0 = lvport::s_flush_us, t = micros(); - for (int i = 0; i < 10; i++) { lv_obj_invalidate(lv_screen_active()); lv_refr_now(NULL); } - Serial.printf("PERF full redraw (Home) %5.1f ms, flush %5.1f ms\n", (micros() - t) / 10000.0f, - (lvport::s_flush_us - fl0) / 10000.0f); - } - if (step % 10 == 9) perfMap(); // the map: tiles decoded while it opened, panning, a full redraw - uint32_t tb = micros(); - switch (step++ % 10) { - case 8: name = "map"; perfMap(true); openMap(true); next = millis() + 8000; break; - case 9: name = "back-home"; back(); break; - case 0: name = "settings"; showSettings(); break; - case 1: name = "back-home"; back(); break; - case 2: name = "messages"; showChats(); break; - case 3: name = "back-home"; back(); break; - case 4: name = "nearby"; showNearby(); break; - case 5: name = "back-home"; back(); break; - case 6: name = "page-2"; setHomePage(1); break; - case 7: name = "page-1"; setHomePage(0); break; - } - Serial.printf("PERF build %-10s %5.1f ms\n", name, (micros() - tb) / 1000.0f); - } - } + perfWalk(); // PerfTest.h #endif #ifdef PIN_BUZZER _buzzer.loop(); diff --git a/examples/companion_radio/ui-lvgl/UITask.h b/examples/companion_radio/ui-lvgl/UITask.h index 86f99d25..0dc98558 100644 --- a/examples/companion_radio/ui-lvgl/UITask.h +++ b/examples/companion_radio/ui-lvgl/UITask.h @@ -341,6 +341,7 @@ private: bool homeMapFit(); #ifdef UI_PERF_TEST void perfMap(bool reset = false); + void perfWalk(); // PerfTest.h: loop()'s self-driving walk through the screens void perfNearbyAgain() { _nearby_sig = 0; refreshNearbyList(); } #endif void homeMapLayout();