perf(box): cache Lire icons in PSRAM
The reading game referenced icons by SD file path, so LVGL re-read and decoded each .bin from the slow microSD on every draw band (16-line buffer) and every frame, with no image cache — hence the laggy display. Read each .bin once into PSRAM and hand LVGL an in-RAM image descriptor: no per-band SD reads, and repeated words are instant.
This commit is contained in:
+59
-8
@@ -139,17 +139,68 @@ static void play_word(int idx)
|
||||
lisael_aac_play_file(p);
|
||||
}
|
||||
|
||||
// A word's picture from the SD (or a coloured fallback with its first letter).
|
||||
// --- PSRAM icon cache --------------------------------------------------------
|
||||
// Referencing icons by SD file path ("S:/…") made LVGL re-read+decode each .bin
|
||||
// from the slow microSD on every draw band (the draw buffer is only 16 lines)
|
||||
// AND every frame, with no image cache (LV_CACHE_DEF_SIZE=0) — hence the laggy
|
||||
// display. Instead we read each .bin ONCE into PSRAM and hand LVGL an in-RAM
|
||||
// image descriptor: no per-band SD reads ever, and repeated words are instant.
|
||||
typedef struct {
|
||||
char id[28];
|
||||
void *buf; // the whole .bin in PSRAM (NULL = known-missing)
|
||||
lv_image_dsc_t dsc;
|
||||
} lire_icon_t;
|
||||
|
||||
static lire_icon_t *s_icons; // PSRAM, one entry per distinct id seen
|
||||
static int s_icons_n;
|
||||
|
||||
static const lv_image_dsc_t *icon_dsc(const char *id)
|
||||
{
|
||||
for (int i = 0; i < s_icons_n; i++) {
|
||||
if (!strcmp(s_icons[i].id, id)) {
|
||||
return s_icons[i].buf ? &s_icons[i].dsc : NULL;
|
||||
}
|
||||
}
|
||||
if (!s_icons) {
|
||||
s_icons = heap_caps_calloc(LIRE_MAX_WORDS, sizeof(lire_icon_t), MALLOC_CAP_SPIRAM);
|
||||
if (!s_icons) return NULL;
|
||||
}
|
||||
if (s_icons_n >= LIRE_MAX_WORDS) return NULL;
|
||||
lire_icon_t *c = &s_icons[s_icons_n++]; // also negatively caches misses
|
||||
snprintf(c->id, sizeof(c->id), "%s", id);
|
||||
c->buf = NULL;
|
||||
|
||||
char p[80];
|
||||
snprintf(p, sizeof(p), "/sdcard/lire/img/%s.bin", id);
|
||||
FILE *f = fopen(p, "rb");
|
||||
if (!f) return NULL;
|
||||
fseek(f, 0, SEEK_END);
|
||||
long sz = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
if (sz <= (long)sizeof(lv_image_header_t) || sz > 256 * 1024) { fclose(f); return NULL; }
|
||||
uint8_t *buf = heap_caps_malloc((size_t)sz, MALLOC_CAP_SPIRAM);
|
||||
if (!buf) { fclose(f); return NULL; }
|
||||
size_t rd = fread(buf, 1, (size_t)sz, f);
|
||||
fclose(f);
|
||||
if (rd != (size_t)sz) { heap_caps_free(buf); return NULL; }
|
||||
|
||||
// The LVGL .bin layout is lv_image_header_t followed by pixel data, so the
|
||||
// PSRAM copy doubles as a variable-image descriptor (same bytes LVGL would
|
||||
// read from the file — which already renders correctly here).
|
||||
lv_memcpy(&c->dsc.header, buf, sizeof(lv_image_header_t));
|
||||
c->dsc.data = buf + sizeof(lv_image_header_t);
|
||||
c->dsc.data_size = (uint32_t)(sz - (long)sizeof(lv_image_header_t));
|
||||
c->buf = buf;
|
||||
return &c->dsc;
|
||||
}
|
||||
|
||||
// A word's picture (from the PSRAM cache) or a coloured fallback with its initial.
|
||||
static lv_obj_t *make_word_icon(lv_obj_t *parent, int idx, int px)
|
||||
{
|
||||
char real[80];
|
||||
snprintf(real, sizeof(real), "/sdcard/lire/img/%s.bin", s_words[idx].id);
|
||||
struct stat st;
|
||||
if (stat(real, &st) == 0 && st.st_size > 0) {
|
||||
char src[88];
|
||||
snprintf(src, sizeof(src), "S:/sdcard/lire/img/%s.bin", s_words[idx].id);
|
||||
const lv_image_dsc_t *d = icon_dsc(s_words[idx].id);
|
||||
if (d) {
|
||||
lv_obj_t *img = lv_image_create(parent);
|
||||
lv_image_set_src(img, src);
|
||||
lv_image_set_src(img, d);
|
||||
lv_obj_set_size(img, px, px);
|
||||
lv_image_set_inner_align(img, LV_IMAGE_ALIGN_CONTAIN);
|
||||
return img;
|
||||
|
||||
Reference in New Issue
Block a user