diff --git a/lib/EpdFont/FontDecompressor.cpp b/lib/EpdFont/FontDecompressor.cpp index 6dea3c01..38fe095c 100644 --- a/lib/EpdFont/FontDecompressor.cpp +++ b/lib/EpdFont/FontDecompressor.cpp @@ -13,15 +13,9 @@ bool FontDecompressor::init() { return true; } -void FontDecompressor::deinit() { - freePageBuffer(); - freeHotGroup(); -} +void FontDecompressor::deinit() { freePageBuffer(); } -void FontDecompressor::clearCache() { - freePageBuffer(); - freeHotGroup(); -} +void FontDecompressor::clearCache() { freePageBuffer(); } void FontDecompressor::freePageBuffer() { for (uint8_t s = 0; s < pageSlotCount; s++) { @@ -32,40 +26,6 @@ void FontDecompressor::freePageBuffer() { pageSlotCount = 0; } -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) { // O(1) path for frequency-grouped fonts with glyphToGroup mapping if (fontData->glyphToGroup != nullptr) { @@ -186,7 +146,8 @@ const uint8_t* FontDecompressor::getBitmap(const EpdFontData* fontData, const Ep break; // Found the right slot but glyph wasn't in it; don't check other slots } - // Fallback: hot group slot + // Fallback: glyph wasn't in the page buffer — decompress its group transiently. + // This is the rare path (prewarm should cover all glyphs on a normal page). uint16_t groupIndex = getGroupIndex(fontData, glyphIndex); if (groupIndex >= fontData->groupCount) { LOG_ERR("FDC", "Glyph %u not found in any group", glyphIndex); @@ -194,40 +155,34 @@ const uint8_t* FontDecompressor::getBitmap(const EpdFontData* fontData, const Ep return nullptr; } - // Check if hot group already has this group decompressed — if not, decompress it - if (!(hotGroupFont == fontData && hotGroupIndex == groupIndex && _hotGroupBufUsed > 0)) { - stats.cacheMisses++; - const EpdFontGroup& group = fontData->groups[groupIndex]; + stats.cacheMisses++; + const EpdFontGroup& group = fontData->groups[groupIndex]; - if (!ensureHotGroupBuf(fontData)) { - stats.getBitmapTimeUs += micros() - tStart; - return nullptr; - } + if (group.uncompressedSize > stats.peakTempBytes) stats.peakTempBytes = group.uncompressedSize; - if (!decompressGroup(fontData, groupIndex, _hotGroupBuf, group.uncompressedSize)) { - hotGroupFont = nullptr; - hotGroupIndex = UINT16_MAX; - _hotGroupBufUsed = 0; - stats.getBitmapTimeUs += micros() - tStart; - return nullptr; - } + uint8_t* groupBuf = static_cast(malloc(group.uncompressedSize)); + if (!groupBuf) { + LOG_ERR("FDC", "OOM: cannot allocate %lu bytes for group %u fallback", group.uncompressedSize, groupIndex); + stats.getBitmapTimeUs += micros() - tStart; + return nullptr; + } - hotGroupFont = fontData; - hotGroupIndex = groupIndex; - _hotGroupBufUsed = group.uncompressedSize; - stats.hotGroupBytes = group.uncompressedSize; - } else { - stats.cacheHits++; + if (!decompressGroup(fontData, groupIndex, groupBuf, group.uncompressedSize)) { + free(groupBuf); + stats.getBitmapTimeUs += micros() - tStart; + return nullptr; } if (glyph->dataLength > HOT_GLYPH_BUF_SIZE) { LOG_ERR("FDC", "Glyph dataLength %u exceeds HOT_GLYPH_BUF_SIZE %u", glyph->dataLength, HOT_GLYPH_BUF_SIZE); + free(groupBuf); stats.getBitmapTimeUs += micros() - tStart; return nullptr; } uint32_t alignedOff = getAlignedOffset(fontData, groupIndex, glyphIndex); - compactSingleGlyph(&_hotGroupBuf[alignedOff], _hotGlyphBuf, glyph->width, glyph->height); + compactSingleGlyph(&groupBuf[alignedOff], _hotGlyphBuf, glyph->width, glyph->height); + free(groupBuf); stats.getBitmapTimeUs += micros() - tStart; return _hotGlyphBuf; @@ -448,17 +403,9 @@ int FontDecompressor::prewarmCache(const EpdFontData* fontData, const char* utf8 } } - // 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; - } - + // Step 4: For each unique group, malloc a transient buffer, decompress, extract needed glyphs, free. + // One malloc/free per group per prewarm call. Groups are visited in sorted order, so + // only one group buffer is alive at a time — peak heap = page buffer + largest single group. uint32_t writeOffset = 0; int missed = 0; @@ -466,11 +413,17 @@ int FontDecompressor::prewarmCache(const EpdFontData* fontData, const char* utf8 uint16_t groupIdx = neededGroups[g]; const EpdFontGroup& group = fontData->groups[groupIdx]; - if (group.uncompressedSize > stats.peakTempBytes) { - stats.peakTempBytes = group.uncompressedSize; + if (group.uncompressedSize > stats.peakTempBytes) stats.peakTempBytes = group.uncompressedSize; + + uint8_t* groupBuf = static_cast(malloc(group.uncompressedSize)); + if (!groupBuf) { + LOG_ERR("FDC", "OOM: cannot allocate %lu bytes for group %u during prewarm", group.uncompressedSize, groupIdx); + missed++; + continue; } - if (!decompressGroup(fontData, groupIdx, _hotGroupBuf, group.uncompressedSize)) { + if (!decompressGroup(fontData, groupIdx, groupBuf, group.uncompressedSize)) { + free(groupBuf); missed++; continue; } @@ -482,19 +435,14 @@ int FontDecompressor::prewarmCache(const EpdFontData* fontData, const char* utf8 if (getGroupIndex(fontData, slot.glyphs[i].glyphIndex) != groupIdx) continue; const EpdGlyph& glyph = fontData->glyph[slot.glyphs[i].glyphIndex]; - compactSingleGlyph(&_hotGroupBuf[slot.glyphs[i].alignedOffset], &slot.buffer[writeOffset], glyph.width, + compactSingleGlyph(&groupBuf[slot.glyphs[i].alignedOffset], &slot.buffer[writeOffset], glyph.width, glyph.height); slot.glyphs[i].bufferOffset = writeOffset; writeOffset += glyph.dataLength; } - } - // 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; + free(groupBuf); + } LOG_DBG("FDC", "Prewarm: %u glyphs in %u bytes from %u groups (%d missed)", glyphCount, writeOffset, groupCount, missed); @@ -511,8 +459,8 @@ void FontDecompressor::logStats(const char* label) { LOG_DBG("FDC", "[%s] hits=%lu misses=%lu (%.1f%% hit rate)", label, stats.cacheHits, stats.cacheMisses, total > 0 ? 100.0f * stats.cacheHits / total : 0.0f); LOG_DBG("FDC", "[%s] decompress=%lums groups_accessed=%u", label, stats.decompressTimeMs, stats.uniqueGroupsAccessed); - LOG_DBG("FDC", "[%s] mem: pageBuf=%lu pageGlyphs=%lu hotGroup=%lu peakTemp=%lu", label, stats.pageBufferBytes, - stats.pageGlyphsBytes, stats.hotGroupBytes, stats.peakTempBytes); + LOG_DBG("FDC", "[%s] mem: pageBuf=%lu pageGlyphs=%lu peakTemp=%lu", label, stats.pageBufferBytes, + stats.pageGlyphsBytes, stats.peakTempBytes); if (stats.getBitmapCalls > 0) { LOG_DBG("FDC", "[%s] getBitmap: %lu calls, %luus total, %luus/call avg", label, stats.getBitmapCalls, stats.getBitmapTimeUs, stats.getBitmapTimeUs / stats.getBitmapCalls); diff --git a/lib/EpdFont/FontDecompressor.h b/lib/EpdFont/FontDecompressor.h index 88b552d2..2894406a 100644 --- a/lib/EpdFont/FontDecompressor.h +++ b/lib/EpdFont/FontDecompressor.h @@ -19,7 +19,7 @@ class FontDecompressor { // Checks the page buffer (from prewarm) first, then falls back to the hot group slot. const uint8_t* getBitmap(const EpdFontData* fontData, const EpdGlyph* glyph, uint32_t glyphIndex); - // Free all cached data (page buffer + hot group). + // Free all cached data (page buffers). void clearCache(); // Pre-scan UTF-8 text and extract needed glyph bitmaps into a flat page buffer. @@ -34,8 +34,7 @@ class FontDecompressor { uint16_t uniqueGroupsAccessed = 0; uint32_t pageBufferBytes = 0; // pageBuffer allocation uint32_t pageGlyphsBytes = 0; // pageGlyphs lookup table allocation - uint32_t hotGroupBytes = 0; // current hot group allocation - uint32_t peakTempBytes = 0; // largest temp buffer in prewarm + uint32_t peakTempBytes = 0; // largest temp buffer in prewarm or getBitmap miss uint32_t getBitmapTimeUs = 0; // cumulative getBitmap time (micros) uint32_t getBitmapCalls = 0; // number of getBitmap calls }; @@ -65,22 +64,11 @@ class FontDecompressor { static constexpr uint16_t HOT_GLYPH_BUF_SIZE = 512; // largest packed single glyph - // 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 = nullptr; - uint32_t _hotGroupBufSize = 0; - - // Scratch buffer for compacting a single glyph out of the byte-aligned hot group. + // Scratch buffer for compacting a single glyph after a getBitmap() miss. // Valid until the next getBitmap() call. uint8_t _hotGlyphBuf[HOT_GLYPH_BUF_SIZE]; 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);