mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-01 10:21:59 +00:00
perf(l2): map tiles decode with PNGdec
PNGdec (bitbank2, 1.1.6) decodes a tile a row at a time straight into RGB565, without lodepng's 256 KB RGBA buffer; the trail overlay goes through it too, blended by hand for palette-with-alpha and RGBA. It lives in its own file (ui-lvgl/TilePng.cpp) because its zlib headers put DONE/EXTRA in the global namespace. lodepng stays the fallback and the sim's decoder. Same 20 tiles: decode 78 -> 50 ms, trails 42 -> 17.5 ms, a whole tile 170 -> 105 ms; the map with trails is pixel-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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 <PNGdec.h>
|
||||
#include <esp_heap_caps.h>
|
||||
#include <new>
|
||||
|
||||
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
|
||||
@@ -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).
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user