From 80722118a2bffc78421e8a9cb85b944e2040f6af Mon Sep 17 00:00:00 2001 From: jpirnay Date: Wed, 22 Apr 2026 08:14:04 +0200 Subject: [PATCH] FontDecompressor: heap-allocate hot group buffer sized to active font MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the 50 KB static BSS _hotGroupBuf array with a heap pointer allocated once per font (ensureHotGroupBuf). The buffer is sized to the largest group in the active font — 6 KB for small fonts, ~50 KB for 18pt — so no memory is wasted when rendering body text at typical sizes. The buffer persists across pages (freed only in freeHotGroup/deinit), so there is exactly one malloc per font session rather than one per page. Co-Authored-By: Claude Sonnet 4.6 --- lib/EpdFont/FontDecompressor.cpp | 56 +++++++++++++++++++++++++------- lib/EpdFont/FontDecompressor.h | 16 ++++----- 2 files changed, 52 insertions(+), 20 deletions(-) diff --git a/lib/EpdFont/FontDecompressor.cpp b/lib/EpdFont/FontDecompressor.cpp index fda79fad..6dea3c01 100644 --- a/lib/EpdFont/FontDecompressor.cpp +++ b/lib/EpdFont/FontDecompressor.cpp @@ -36,6 +36,34 @@ void FontDecompressor::freeHotGroup() { hotGroupFont = nullptr; hotGroupIndex = UINT16_MAX; _hotGroupBufUsed = 0; + free(_hotGroupBuf); + _hotGroupBuf = nullptr; + _hotGroupBufSize = 0; +} + +bool FontDecompressor::ensureHotGroupBuf(const EpdFontData* fontData) { + // Find largest uncompressed group size for this font + uint32_t maxSize = 0; + for (uint16_t i = 0; i < fontData->groupCount; i++) { + if (fontData->groups[i].uncompressedSize > maxSize) maxSize = fontData->groups[i].uncompressedSize; + } + if (maxSize == 0) return false; + + if (_hotGroupBufSize >= maxSize) return true; // existing allocation is large enough + + free(_hotGroupBuf); + _hotGroupBuf = static_cast(malloc(maxSize)); + if (!_hotGroupBuf) { + _hotGroupBufSize = 0; + LOG_ERR("FDC", "OOM: cannot allocate %lu bytes for hot group buf", maxSize); + return false; + } + _hotGroupBufSize = maxSize; + // Switching fonts invalidates any cached group + hotGroupFont = nullptr; + hotGroupIndex = UINT16_MAX; + _hotGroupBufUsed = 0; + return true; } uint16_t FontDecompressor::getGroupIndex(const EpdFontData* fontData, uint32_t glyphIndex) { @@ -57,16 +85,8 @@ uint16_t FontDecompressor::getGroupIndex(const EpdFontData* fontData, uint32_t g bool FontDecompressor::decompressGroup(const EpdFontData* fontData, uint16_t groupIndex, uint8_t* outBuf, uint32_t outSize) { const EpdFontGroup& group = fontData->groups[groupIndex]; - - if (outSize > HOT_GROUP_BUF_SIZE) { - LOG_ERR("FDC", "Group %u uncompressed size %lu exceeds HOT_GROUP_BUF_SIZE %lu", groupIndex, outSize, - HOT_GROUP_BUF_SIZE); - return false; - } - const uint32_t tDecomp = millis(); inflateReader.init(false); - inflateReader.setSource(&fontData->bitmap[group.compressedOffset], group.compressedSize); if (!inflateReader.read(outBuf, outSize)) { @@ -179,6 +199,11 @@ const uint8_t* FontDecompressor::getBitmap(const EpdFontData* fontData, const Ep stats.cacheMisses++; const EpdFontGroup& group = fontData->groups[groupIndex]; + if (!ensureHotGroupBuf(fontData)) { + stats.getBitmapTimeUs += micros() - tStart; + return nullptr; + } + if (!decompressGroup(fontData, groupIndex, _hotGroupBuf, group.uncompressedSize)) { hotGroupFont = nullptr; hotGroupIndex = UINT16_MAX; @@ -423,9 +448,17 @@ int FontDecompressor::prewarmCache(const EpdFontData* fontData, const char* utf8 } } - // Step 4: For each unique group, decompress into the static _hotGroupBuf and extract needed glyphs. - // No heap allocation — _hotGroupBuf is reused for each group in turn. - // After prewarm, _hotGroupBuf is invalidated (hotGroupFont reset) since its contents are transient. + // Step 4: Ensure hot group buffer is sized for this font, then decompress each group and extract. + // ensureHotGroupBuf() allocates once for the largest group — reused across all groups in this prewarm. + if (!ensureHotGroupBuf(fontData)) { + LOG_ERR("FDC", "Failed to allocate hot group buf during prewarm"); + free(slot.buffer); + free(slot.glyphs); + slot = {}; + pageSlotCount--; + return glyphCount; + } + uint32_t writeOffset = 0; int missed = 0; @@ -458,6 +491,7 @@ int FontDecompressor::prewarmCache(const EpdFontData* fontData, const char* utf8 // Prewarm reused _hotGroupBuf transiently — invalidate hot group state so getBitmap() // doesn't treat stale contents as a valid cache entry for a different glyph request. + // The buffer itself is kept allocated (freed in freeHotGroup/deinit) — no churn between pages. hotGroupFont = nullptr; hotGroupIndex = UINT16_MAX; _hotGroupBufUsed = 0; diff --git a/lib/EpdFont/FontDecompressor.h b/lib/EpdFont/FontDecompressor.h index bd1c25ac..88b552d2 100644 --- a/lib/EpdFont/FontDecompressor.h +++ b/lib/EpdFont/FontDecompressor.h @@ -63,19 +63,16 @@ class FontDecompressor { PageSlot pageSlots[MAX_PAGE_SLOTS] = {}; uint8_t pageSlotCount = 0; - // Measured maxima across all built-in fonts: - // uncompressedSize: 50 KB (notosans_18 / bookerly_18) - // glyph dataLength: 500 B (bookerly_18) - // Static BSS arrays eliminate per-page heap alloc/free and the fragmentation it causes. - static constexpr uint32_t HOT_GROUP_BUF_SIZE = 51200; // 50 KB uncompressed group - static constexpr uint16_t HOT_GLYPH_BUF_SIZE = 512; // largest packed single glyph + static constexpr uint16_t HOT_GLYPH_BUF_SIZE = 512; // largest packed single glyph - // Hot group: last decompressed group (byte-aligned) for non-prewarmed fallback path. - // Kept in byte-aligned format; individual glyphs are compacted on demand into _hotGlyphBuf. + // Hot group: single heap buffer sized to the largest group of the active font. + // Allocated once per font (lazily in ensureHotGroupBuf) and freed in freeHotGroup(). + // Single malloc per font session — no repeated alloc/free per page. const EpdFontData* hotGroupFont = nullptr; uint16_t hotGroupIndex = UINT16_MAX; uint32_t _hotGroupBufUsed = 0; - uint8_t _hotGroupBuf[HOT_GROUP_BUF_SIZE]; + uint8_t* _hotGroupBuf = nullptr; + uint32_t _hotGroupBufSize = 0; // Scratch buffer for compacting a single glyph out of the byte-aligned hot group. // Valid until the next getBitmap() call. @@ -83,6 +80,7 @@ class FontDecompressor { void freePageBuffer(); void freeHotGroup(); + bool ensureHotGroupBuf(const EpdFontData* fontData); uint16_t getGroupIndex(const EpdFontData* fontData, uint32_t glyphIndex); uint32_t getAlignedOffset(const EpdFontData* fontData, uint16_t groupIndex, uint32_t glyphIndex); bool decompressGroup(const EpdFontData* fontData, uint16_t groupIndex, uint8_t* outBuf, uint32_t outSize);