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:
co-authored by
Claude Sonnet 4.6
parent
e4170e2272
commit
80722118a2
@@ -36,6 +36,34 @@ void FontDecompressor::freeHotGroup() {
|
|||||||
hotGroupFont = nullptr;
|
hotGroupFont = nullptr;
|
||||||
hotGroupIndex = UINT16_MAX;
|
hotGroupIndex = UINT16_MAX;
|
||||||
_hotGroupBufUsed = 0;
|
_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) {
|
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,
|
bool FontDecompressor::decompressGroup(const EpdFontData* fontData, uint16_t groupIndex, uint8_t* outBuf,
|
||||||
uint32_t outSize) {
|
uint32_t outSize) {
|
||||||
const EpdFontGroup& group = fontData->groups[groupIndex];
|
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();
|
const uint32_t tDecomp = millis();
|
||||||
inflateReader.init(false);
|
inflateReader.init(false);
|
||||||
|
|
||||||
inflateReader.setSource(&fontData->bitmap[group.compressedOffset], group.compressedSize);
|
inflateReader.setSource(&fontData->bitmap[group.compressedOffset], group.compressedSize);
|
||||||
|
|
||||||
if (!inflateReader.read(outBuf, outSize)) {
|
if (!inflateReader.read(outBuf, outSize)) {
|
||||||
@@ -179,6 +199,11 @@ const uint8_t* FontDecompressor::getBitmap(const EpdFontData* fontData, const Ep
|
|||||||
stats.cacheMisses++;
|
stats.cacheMisses++;
|
||||||
const EpdFontGroup& group = fontData->groups[groupIndex];
|
const EpdFontGroup& group = fontData->groups[groupIndex];
|
||||||
|
|
||||||
|
if (!ensureHotGroupBuf(fontData)) {
|
||||||
|
stats.getBitmapTimeUs += micros() - tStart;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
if (!decompressGroup(fontData, groupIndex, _hotGroupBuf, group.uncompressedSize)) {
|
if (!decompressGroup(fontData, groupIndex, _hotGroupBuf, group.uncompressedSize)) {
|
||||||
hotGroupFont = nullptr;
|
hotGroupFont = nullptr;
|
||||||
hotGroupIndex = UINT16_MAX;
|
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.
|
// Step 4: Ensure hot group buffer is sized for this font, then decompress each group and extract.
|
||||||
// No heap allocation — _hotGroupBuf is reused for each group in turn.
|
// ensureHotGroupBuf() allocates once for the largest group — reused across all groups in this prewarm.
|
||||||
// After prewarm, _hotGroupBuf is invalidated (hotGroupFont reset) since its contents are transient.
|
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;
|
uint32_t writeOffset = 0;
|
||||||
int missed = 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()
|
// 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.
|
// 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;
|
hotGroupFont = nullptr;
|
||||||
hotGroupIndex = UINT16_MAX;
|
hotGroupIndex = UINT16_MAX;
|
||||||
_hotGroupBufUsed = 0;
|
_hotGroupBufUsed = 0;
|
||||||
|
|||||||
@@ -63,19 +63,16 @@ class FontDecompressor {
|
|||||||
PageSlot pageSlots[MAX_PAGE_SLOTS] = {};
|
PageSlot pageSlots[MAX_PAGE_SLOTS] = {};
|
||||||
uint8_t pageSlotCount = 0;
|
uint8_t pageSlotCount = 0;
|
||||||
|
|
||||||
// Measured maxima across all built-in fonts:
|
static constexpr uint16_t HOT_GLYPH_BUF_SIZE = 512; // largest packed single glyph
|
||||||
// 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
|
|
||||||
|
|
||||||
// Hot group: last decompressed group (byte-aligned) for non-prewarmed fallback path.
|
// Hot group: single heap buffer sized to the largest group of the active font.
|
||||||
// Kept in byte-aligned format; individual glyphs are compacted on demand into _hotGlyphBuf.
|
// 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;
|
const EpdFontData* hotGroupFont = nullptr;
|
||||||
uint16_t hotGroupIndex = UINT16_MAX;
|
uint16_t hotGroupIndex = UINT16_MAX;
|
||||||
uint32_t _hotGroupBufUsed = 0;
|
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.
|
// Scratch buffer for compacting a single glyph out of the byte-aligned hot group.
|
||||||
// Valid until the next getBitmap() call.
|
// Valid until the next getBitmap() call.
|
||||||
@@ -83,6 +80,7 @@ class FontDecompressor {
|
|||||||
|
|
||||||
void freePageBuffer();
|
void freePageBuffer();
|
||||||
void freeHotGroup();
|
void freeHotGroup();
|
||||||
|
bool ensureHotGroupBuf(const EpdFontData* fontData);
|
||||||
uint16_t getGroupIndex(const EpdFontData* fontData, uint32_t glyphIndex);
|
uint16_t getGroupIndex(const EpdFontData* fontData, uint32_t glyphIndex);
|
||||||
uint32_t getAlignedOffset(const EpdFontData* fontData, uint16_t groupIndex, 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);
|
bool decompressGroup(const EpdFontData* fontData, uint16_t groupIndex, uint8_t* outBuf, uint32_t outSize);
|
||||||
|
|||||||
Reference in New Issue
Block a user