mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-01 10:21:59 +00:00
perf(l2): screen sent by DMA while the next part is drawn
The panel takes each 24-line band by DMA while LVGL draws the next one into the other buffer (two 15 KB buffers in internal RAM; LovyanGFX's 4092-byte DMA descriptors leave PSRAM unaligned for the S3's DMA, which showed as shifted and smeared bands). LVGL draws in the panel's byte order, and so do map tiles and the frozen screen under a popup, so nothing is swapped on the way out. Full frames ~10-25% faster (page1 19.9 -> 14.7 ms, Home 21.4 -> 18.2 ms). Without the internal RAM the screen is sent as before. The arduino2 env keeps the old way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -43,7 +43,12 @@ static void shotCopy(const lv_area_t* a, const uint8_t* px) {
|
||||
const int32_t W = lv_display_get_horizontal_resolution(NULL);
|
||||
const int32_t w = a->x2 - a->x1 + 1;
|
||||
const uint16_t* src = (const uint16_t*)px;
|
||||
for (int32_t y = a->y1; y <= a->y2; y++, src += w) memcpy(s_shot + y * W + a->x1, src, w * 2);
|
||||
for (int32_t y = a->y1; y <= a->y2; y++, src += w) {
|
||||
memcpy(s_shot + y * W + a->x1, src, w * 2);
|
||||
#ifdef UI_FLUSH_DMA // drawn big-endian for the panel
|
||||
lv_draw_sw_rgb565_swap(s_shot + y * W + a->x1, w);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Saved WiFi networks (Settings > WiFi), most recently saved first. Kept in RAM
|
||||
@@ -99,7 +104,16 @@ static const int WIFI_SCAN_MAX = 20;
|
||||
|
||||
static lgfx::LGFX_Device* s_gfx = nullptr;
|
||||
static bool s_swallow = false; // ignore the touch that woke the display until it lifts
|
||||
static uint32_t s_flush_us = 0; // UI_PERF_TEST: time spent in flushCb
|
||||
static uint32_t s_flush_us = 0; // UI_PERF_TEST: time spent in flushCb (and waiting for it)
|
||||
// -D UI_FLUSH_DMA: the panel fed by DMA in the background from buffers in
|
||||
// internal RAM, everything drawn in its byte order.
|
||||
#ifdef UI_FLUSH_DMA
|
||||
#define FLUSH_DMA 1
|
||||
#else
|
||||
#define FLUSH_DMA 0
|
||||
#endif
|
||||
static bool s_dma = false; // the buffers are DMA-able: sent in the background
|
||||
static bool s_flushing = false; // a DMA transfer under way
|
||||
|
||||
// The CPU clock down to 80 MHz (LoRa, BLE and WiFi all run at that) with the
|
||||
// screen off (powerSave()), and with it on while nobody touches it and nothing
|
||||
@@ -121,16 +135,44 @@ static void flushCb(lv_display_t* disp, const lv_area_t* area, uint8_t* px_map)
|
||||
shotCopy(area, px_map);
|
||||
int w = area->x2 - area->x1 + 1;
|
||||
int h = area->y2 - area->y1 + 1;
|
||||
#if FLUSH_DMA
|
||||
if (s_dma) {
|
||||
// Sent by DMA while LVGL draws the next area into the other buffer (drawn
|
||||
// in the panel's byte order, RGB565_SWAPPED). flushWait() (below) ends it.
|
||||
s_gfx->startWrite();
|
||||
display.panel().flushStart(area->x1, area->y1, w, h, px_map, (uint32_t)w * h * 2);
|
||||
s_flushing = true;
|
||||
return; // (UI_PERF_TEST: the wait is timed instead)
|
||||
}
|
||||
#endif
|
||||
s_gfx->startWrite();
|
||||
s_gfx->setAddrWindow(area->x1, area->y1, w, h);
|
||||
s_gfx->pushPixels((uint16_t*)px_map, (uint32_t)w * h, true /* LVGL RGB565 is little-endian */);
|
||||
s_gfx->pushPixels((uint16_t*)px_map, (uint32_t)w * h, !FLUSH_DMA /* LVGL RGB565 is little-endian; _SWAPPED as sent */);
|
||||
s_gfx->endWrite();
|
||||
lv_display_flush_ready(disp);
|
||||
#ifdef UI_PERF_TEST
|
||||
s_flush_us += micros() - t;
|
||||
#endif
|
||||
lv_display_flush_ready(disp);
|
||||
}
|
||||
|
||||
#if FLUSH_DMA
|
||||
// The area sent last, finished: before LVGL draws into its buffer again, and
|
||||
// at the end of every frame -- nothing else on the bus meets a transfer.
|
||||
static void flushWait(lv_display_t* disp) {
|
||||
(void)disp;
|
||||
if (!s_flushing) return;
|
||||
#ifdef UI_PERF_TEST
|
||||
uint32_t t = micros();
|
||||
#endif
|
||||
display.panel().flushWait();
|
||||
s_gfx->endWrite();
|
||||
s_flushing = false;
|
||||
#ifdef UI_PERF_TEST
|
||||
s_flush_us += micros() - t;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
static void touchCb(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||
(void)indev;
|
||||
lgfx::touch_point_t tp;
|
||||
@@ -158,16 +200,45 @@ static bool begin() {
|
||||
lv_display_t* disp = lv_display_create(s_gfx->width(), s_gfx->height());
|
||||
lv_display_set_flush_cb(disp, flushCb);
|
||||
|
||||
// Two half-screen buffers (75 KB each) in PSRAM: a frame renders in two
|
||||
// passes instead of six (every pass walks the whole tree and lays text out
|
||||
// again) -- measured ~8% faster than 40 lines; internal RAM / DMA / -O2
|
||||
// made no difference (UI_PERF_TEST).
|
||||
const uint32_t buf_sz = (uint32_t)s_gfx->width() * 120 * 2;
|
||||
uint8_t* buf1 = (uint8_t*)heap_caps_malloc(buf_sz, MALLOC_CAP_SPIRAM);
|
||||
uint8_t* buf2 = (uint8_t*)heap_caps_malloc(buf_sz, MALLOC_CAP_SPIRAM);
|
||||
if (!buf1) { buf1 = (uint8_t*)heap_caps_malloc(buf_sz, MALLOC_CAP_8BIT); buf2 = nullptr; }
|
||||
// Sent by DMA (FLUSH_DMA): two 24-line buffers (15 KB each) in internal
|
||||
// RAM, ten passes a frame, each drawn while the last goes out -- 7-11%
|
||||
// faster screens than 48 lines in PSRAM, the map the same. Not PSRAM:
|
||||
// LovyanGFX cuts a transfer into 4092-byte DMA descriptors, and from the
|
||||
// second one on PSRAM isn't 16-byte aligned as the S3's DMA needs it
|
||||
// there (bands shifted, or of stale pixels).
|
||||
// Otherwise two half-screen buffers (75 KB each) in PSRAM: two passes
|
||||
// instead of six (every pass walks the whole tree and lays text out again)
|
||||
// -- measured ~8% faster than 40 lines; internal RAM / -O2 made no
|
||||
// difference (UI_PERF_TEST).
|
||||
uint32_t buf_sz = 0;
|
||||
uint8_t *buf1 = nullptr, *buf2 = nullptr;
|
||||
#if FLUSH_DMA
|
||||
buf_sz = (uint32_t)s_gfx->width() * 24 * 2;
|
||||
buf1 = (uint8_t*)heap_caps_malloc(buf_sz, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
|
||||
buf2 = buf1 ? (uint8_t*)heap_caps_malloc(buf_sz, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA) : nullptr;
|
||||
s_dma = buf2 != nullptr;
|
||||
if (!s_dma && buf1) { heap_caps_free(buf1); buf1 = nullptr; } // no room: sent as it's drawn, as below
|
||||
#endif
|
||||
if (!s_dma) {
|
||||
buf_sz = (uint32_t)s_gfx->width() * 120 * 2;
|
||||
buf1 = (uint8_t*)heap_caps_malloc(buf_sz, MALLOC_CAP_SPIRAM);
|
||||
buf2 = (uint8_t*)heap_caps_malloc(buf_sz, MALLOC_CAP_SPIRAM);
|
||||
if (!buf1) { buf1 = (uint8_t*)heap_caps_malloc(buf_sz, MALLOC_CAP_8BIT); buf2 = nullptr; }
|
||||
}
|
||||
if (!buf1) return false;
|
||||
lv_display_set_buffers(disp, buf1, buf2, buf_sz, LV_DISPLAY_RENDER_MODE_PARTIAL);
|
||||
#if FLUSH_DMA
|
||||
// Drawn big-endian, as the panel takes it: swapping a frame's bytes
|
||||
// before sending cost ~6 ms. Pictures are made so too (map tiles, the
|
||||
// frozen screen): an RGB565 one is swapped pixel by pixel as it's drawn.
|
||||
lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565_SWAPPED);
|
||||
if (s_dma) lv_display_set_flush_wait_cb(disp, flushWait);
|
||||
lv_display_add_event_cb(disp, [](lv_event_t* e) { // the frame's last area, done before anything else runs
|
||||
if (!s_flushing) return;
|
||||
flushWait(nullptr);
|
||||
lv_display_flush_ready((lv_display_t*)lv_event_get_current_target(e));
|
||||
}, LV_EVENT_REFR_READY, NULL);
|
||||
#endif
|
||||
|
||||
lv_indev_t* indev = lv_indev_create();
|
||||
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
|
||||
|
||||
@@ -322,16 +322,26 @@ namespace freeze {
|
||||
s_retry = nullptr;
|
||||
take((lv_obj_t*)lv_timer_get_user_data(t));
|
||||
}
|
||||
// Darkened into the display's own byte order (UI_FLUSH_DMA: big-endian;
|
||||
// lv_snapshot can't make that one itself): drawn then as a plain copy.
|
||||
#ifdef UI_FLUSH_DMA
|
||||
static inline uint16_t out565(uint16_t c) { return __builtin_bswap16(c); }
|
||||
#else
|
||||
static inline uint16_t out565(uint16_t c) { return c; }
|
||||
#endif
|
||||
static void darken(lv_draw_buf_t* buf) { // as the dim layer over it would
|
||||
const uint32_t keep = 256 - s_dim; // what's left under the black
|
||||
for (uint32_t y = 0; y < buf->header.h; y++) {
|
||||
uint16_t* p = (uint16_t*)(buf->data + y * buf->header.stride);
|
||||
for (uint32_t x = 0; x < buf->header.w; x++) {
|
||||
uint32_t c = p[x];
|
||||
p[x] = (uint16_t)(((((c >> 11) * keep) >> 8) << 11) | (((((c >> 5) & 0x3F) * keep) >> 8) << 5) |
|
||||
(((c & 0x1F) * keep) >> 8));
|
||||
p[x] = out565((uint16_t)(((((c >> 11) * keep) >> 8) << 11) | (((((c >> 5) & 0x3F) * keep) >> 8) << 5) |
|
||||
(((c & 0x1F) * keep) >> 8)));
|
||||
}
|
||||
}
|
||||
#ifdef UI_FLUSH_DMA
|
||||
buf->header.cf = LV_COLOR_FORMAT_RGB565_SWAPPED;
|
||||
#endif
|
||||
}
|
||||
static lv_draw_buf_t* snap(lv_obj_t* overlay, lv_obj_t* scr, bool top) {
|
||||
if (!top) lv_obj_add_flag(overlay, LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
@@ -90,7 +90,11 @@ public:
|
||||
s->present = true;
|
||||
memset(&s->dsc, 0, sizeof(s->dsc));
|
||||
s->dsc.header.magic = LV_IMAGE_HEADER_MAGIC;
|
||||
#ifdef UI_FLUSH_DMA
|
||||
s->dsc.header.cf = LV_COLOR_FORMAT_RGB565_SWAPPED;
|
||||
#else
|
||||
s->dsc.header.cf = LV_COLOR_FORMAT_RGB565;
|
||||
#endif
|
||||
s->dsc.header.w = TILE_PX;
|
||||
s->dsc.header.h = TILE_PX;
|
||||
s->dsc.header.stride = TILE_PX * 2;
|
||||
@@ -151,6 +155,9 @@ private:
|
||||
uint32_t t0 = micros();
|
||||
#endif
|
||||
_job.present = _spare && _job.src->renderTile(_job.z, _job.x, _job.y, _spare);
|
||||
#ifdef UI_FLUSH_DMA // in the display's byte order: drawn as a plain copy (LvglPort.h)
|
||||
if (_job.present) lv_draw_sw_rgb565_swap(_spare, TILE_PX * TILE_PX);
|
||||
#endif
|
||||
#ifdef UI_PERF_TEST
|
||||
uint32_t dt = micros() - t0;
|
||||
if (_job.present) { s_perf_tile_us += dt; s_perf_tile_n++; if (dt > s_perf_tile_max) s_perf_tile_max = dt; }
|
||||
|
||||
Reference in New Issue
Block a user