diff --git a/examples/companion_radio/ui-lvgl/MapScreen.h b/examples/companion_radio/ui-lvgl/MapScreen.h index c3fa4c2d..12b231a5 100644 --- a/examples/companion_radio/ui-lvgl/MapScreen.h +++ b/examples/companion_radio/ui-lvgl/MapScreen.h @@ -293,9 +293,10 @@ void UITask::perfMap(bool reset) { while (s_cache.busy()) { delay(5); pollTiles(); } // a prefetch still decoding would share PSRAM with the drawing Serial.printf("PERF tiles %lu decoded, avg %5.1f ms, max %5.1f ms (pending %d)\n", (unsigned long)s_perf_tile_n, s_perf_tile_n ? s_perf_tile_us / 1000.0f / s_perf_tile_n : 0.0f, s_perf_tile_max / 1000.0f, (int)_map_pending); - if (s_perf_tile_n) Serial.printf("PERF of which read %5.1f ms (%lu B), decode %5.1f ms\n", s_raster.perf_read_us / 1000.0f / s_perf_tile_n, - (unsigned long)(s_raster.perf_bytes / s_perf_tile_n), s_raster.perf_dec_us / 1000.0f / s_perf_tile_n); - s_raster.perf_read_us = s_raster.perf_dec_us = s_raster.perf_bytes = 0; + if (s_perf_tile_n) Serial.printf("PERF of which read %5.1f ms (%lu B), decode %5.1f ms, trails %5.1f ms\n", s_raster.perf_read_us / 1000.0f / s_perf_tile_n, + (unsigned long)(s_raster.perf_bytes / s_perf_tile_n), s_raster.perf_dec_us / 1000.0f / s_perf_tile_n, + s_raster.perf_ovl_us / 1000.0f / s_perf_tile_n); + s_raster.perf_read_us = s_raster.perf_dec_us = s_raster.perf_bytes = s_raster.perf_ovl_us = 0; uint32_t fl0 = lvport::s_flush_us, t = micros(); for (int i = 0; i < 20; i++) { // a drag: 4 px a frame, tiles already decoded _map_cx += (i < 10 ? 4.0 : -4.0) / TILE_PX; diff --git a/examples/companion_radio/ui-lvgl/TilePng.cpp b/examples/companion_radio/ui-lvgl/TilePng.cpp new file mode 100644 index 00000000..7a863f37 --- /dev/null +++ b/examples/companion_radio/ui-lvgl/TilePng.cpp @@ -0,0 +1,78 @@ +// PNGdec for the map tiles (map/TileProvider.h): a row at a time straight +// into the RGB565 tile -- no 256 KB RGBA picture in between as with lodepng, +// and about a third faster. In a file of its own because PNGdec's zlib +// headers put names like DONE and EXTRA in the global namespace, which the +// single-TU UI would trip over. Its state (~37 KB, mostly the inflate window) +// sits in PSRAM: one copy, for the tile task only (TileCache). + +#if defined(ESP32) +#include +#include +#include + +static PNG* s_png = nullptr; + +static bool openPng(const uint8_t* png, size_t len, int side, PNG_DRAW_CALLBACK* row) { + if (!s_png) { + void* m = heap_caps_malloc(sizeof(PNG), MALLOC_CAP_SPIRAM); + if (!m) return false; + s_png = new (m) PNG(); + } + if (s_png->openRAM((uint8_t*)png, (int)len, row) != PNG_SUCCESS) return false; + if (s_png->getWidth() == side && s_png->getHeight() == side) return true; + s_png->close(); + return false; +} + +static int onRow(PNGDRAW* d) { + s_png->getLineAsRGB565(d, (uint16_t*)d->pUser + d->y * d->iWidth, PNG_RGB565_LITTLE_ENDIAN, 0xffffffff); + return 1; +} + +// `png` (len bytes) into out[side * side]; false if it isn't a side x side +// picture PNGdec can read (the caller then tries lodepng). +bool pngdecRgb565(const uint8_t* png, size_t len, uint16_t* out, int side) { + if (!openPng(png, len, side, onRow)) return false; + bool ok = s_png->decode(out, 0) == PNG_SUCCESS; + s_png->close(); + return ok; +} + +// One pixel of an overlay over the tile, by its alpha. +static inline void blend(uint16_t& o, unsigned r, unsigned g, unsigned b, unsigned a) { + unsigned r0 = (o >> 8) & 0xF8, g0 = (o >> 3) & 0xFC, b0 = (o << 3) & 0xF8; + r = (r * a + r0 * (255 - a)) / 255; + g = (g * a + g0 * (255 - a)) / 255; + b = (b * a + b0 * (255 - a)) / 255; + o = (uint16_t)(((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)); +} + +static int onOverlayRow(PNGDRAW* d) { + uint16_t* o = (uint16_t*)d->pUser + d->y * d->iWidth; + const uint8_t* s = d->pPixels; + if (d->iPixelType == PNG_PIXEL_TRUECOLOR_ALPHA) { + for (int x = 0; x < d->iWidth; x++, s += 4) + if (s[3]) blend(o[x], s[0], s[1], s[2], s[3]); + return 1; + } + const uint8_t* pal = d->pPalette; // indexed: RGB triplets, then the alpha of each entry at 768 + const int bpp = d->iBpp; + for (int x = 0; x < d->iWidth; x++) { + unsigned i = bpp == 8 ? s[x] : (s[(x * bpp) >> 3] >> (8 - bpp - ((x * bpp) & 7))) & ((1u << bpp) - 1); + if (unsigned a = pal[768 + i]) blend(o[x], pal[i * 3], pal[i * 3 + 1], pal[i * 3 + 2], a); + } + return 1; +} + +// A transparent overlay (`png`) drawn over out[side * side]. Indexed or 8-bit +// RGBA pictures only (hiking trails come as either); false otherwise, with +// `out` untouched. +bool pngdecOverlayRgb565(const uint8_t* png, size_t len, uint16_t* out, int side) { + if (!openPng(png, len, side, onOverlayRow)) return false; + int t = s_png->getPixelType(); + bool ok = (t == PNG_PIXEL_INDEXED || (t == PNG_PIXEL_TRUECOLOR_ALPHA && s_png->getBpp() == 8)) + && s_png->decode(out, 0) == PNG_SUCCESS; + s_png->close(); + return ok; +} +#endif diff --git a/examples/companion_radio/ui-lvgl/lv_conf.h b/examples/companion_radio/ui-lvgl/lv_conf.h index bcf42dc8..299eb205 100644 --- a/examples/companion_radio/ui-lvgl/lv_conf.h +++ b/examples/companion_radio/ui-lvgl/lv_conf.h @@ -46,7 +46,8 @@ #define LV_TXT_BREAK_CHARS " ,;:-_)]}" #define LV_USE_FONT_COMPRESSED 0 // fonts are generated uncompressed: no per-glyph decompression on every draw -// PNG decoder for raster map tiles (map/TileProvider.h calls lodepng directly). +// PNG decoder for raster map tiles: the sim's, and the fallback on the L2 behind +// PNGdec (ui-lvgl/TilePng.cpp). map/TileProvider.h calls lodepng directly. #define LV_USE_LODEPNG 1 // Spans: message text with @[nick] mentions picked out (UITask.cpp msgText). diff --git a/examples/companion_radio/ui-lvgl/map/TileProvider.h b/examples/companion_radio/ui-lvgl/map/TileProvider.h index 7d2ed315..b589e91d 100644 --- a/examples/companion_radio/ui-lvgl/map/TileProvider.h +++ b/examples/companion_radio/ui-lvgl/map/TileProvider.h @@ -16,6 +16,11 @@ extern "C" { #include "src/libs/lodepng/lodepng.h" } +#if defined(ESP32) +bool pngdecRgb565(const uint8_t* png, size_t len, uint16_t* out, int side); // ui-lvgl/TilePng.cpp +bool pngdecOverlayRgb565(const uint8_t* png, size_t len, uint16_t* out, int side); +#endif + namespace mapview { // OpenTopoMap answers every tile past its last zoom level (17) with the same @@ -125,7 +130,7 @@ public: } #ifdef UI_PERF_TEST - uint32_t perf_read_us = 0, perf_dec_us = 0, perf_bytes = 0; + uint32_t perf_read_us = 0, perf_dec_us = 0, perf_bytes = 0, perf_ovl_us = 0; #endif bool renderTile(int z, int x, int y, uint16_t* out) override { _overlay_missed = false; @@ -142,21 +147,32 @@ public: perf_read_us += t1 - t0; perf_bytes += len; #endif - lv_draw_buf_t* db = decodePng(png, len); + bool ok = false; +#if defined(ESP32) + ok = pngdecRgb565(png, len, out, TILE_PX); // lodepng below if it can't +#endif + if (!ok) { + lv_draw_buf_t* db = decodePng(png, len); + if (!db) { lv_free(png); return false; } + for (int row = 0; row < TILE_PX; row++) { + const uint8_t* p = db->data + row * db->header.stride; // R, G, B, A + uint16_t* o = out + row * TILE_PX; + for (int i = 0; i < TILE_PX; i++, p += 4) + o[i] = (uint16_t)(((p[0] & 0xF8) << 8) | ((p[1] & 0xFC) << 3) | (p[2] >> 3)); + } + lv_draw_buf_destroy(db); + } lv_free(png); - if (!db) return false; #ifdef UI_PERF_TEST perf_dec_us += micros() - t1; #endif - - for (int row = 0; row < TILE_PX; row++) { - const uint8_t* p = db->data + row * db->header.stride; // R, G, B, A - uint16_t* o = out + row * TILE_PX; - for (int i = 0; i < TILE_PX; i++, p += 4) - o[i] = (uint16_t)(((p[0] & 0xF8) << 8) | ((p[1] & 0xFC) << 3) | (p[2] >> 3)); - } - lv_draw_buf_destroy(db); +#ifdef UI_PERF_TEST + uint32_t t2 = micros(); +#endif if (s_trails_on && z <= TRAILS_MAX_Z) drawOverlay(z, x, y, out); +#ifdef UI_PERF_TEST + perf_ovl_us += micros() - t2; +#endif return true; } @@ -188,6 +204,9 @@ private: uint8_t* png = sz > 0 ? readRange(f, 0, (uint32_t)sz) : nullptr; // 0 bytes: no trail here fclose(f); if (!png) return; +#if defined(ESP32) + if (pngdecOverlayRgb565(png, (size_t)sz, out, TILE_PX)) { lv_free(png); return; } // else lodepng +#endif lv_draw_buf_t* db = decodePng(png, (size_t)sz); lv_free(png); if (!db) return; diff --git a/solo/wio-tracker-l2/platformio.ini b/solo/wio-tracker-l2/platformio.ini index faff76c7..15dce0b2 100644 --- a/solo/wio-tracker-l2/platformio.ini +++ b/solo/wio-tracker-l2/platformio.ini @@ -92,6 +92,7 @@ lib_deps = ${Wio_Tracker_L2.lib_deps} densaugeo/base64 @ ~1.4.0 lvgl/lvgl @ 9.6.0 ; pinned: bump deliberately, re-test on hardware + bitbank2/PNGdec @ 1.1.6 ; map tiles: PNG straight to RGB565, a row at a time (ui-lvgl/TilePng.cpp) SD_MMC ; map tiles on the microSD (framework libraries from here on) FS WiFi ; on-device map download, live tiles, OTA @@ -111,6 +112,7 @@ lib_deps = ${Wio_Tracker_L2.lib_deps} densaugeo/base64 @ ~1.4.0 lvgl/lvgl @ 9.6.0 + bitbank2/PNGdec @ 1.1.6 SD_MMC FS WiFi