FontDecompressor: heap-allocate hot group buffer sized to active font

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 <noreply@anthropic.com>
This commit is contained in:
jpirnay
2026-04-22 08:14:04 +02:00
co-authored by Claude Sonnet 4.6
parent e4170e2272
commit 80722118a2
2 changed files with 52 additions and 20 deletions
+45 -11
View File
@@ -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<uint8_t*>(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;