Further optimisations

This commit is contained in:
jpirnay
2026-05-17 13:55:55 +02:00
parent 8cae186cc1
commit a7450fa8d4
2 changed files with 121 additions and 41 deletions
+118 -40
View File
@@ -272,7 +272,7 @@ static uint8_t miniLookupKernClass(const EpdKernClassEntry* entries, uint16_t co
// the mini versions together in applyKernLigaturePointers, so a codepoint not // the mini versions together in applyKernLigaturePointers, so a codepoint not
// on this page simply returns class 0 (no kerning), which was the pre-existing // on this page simply returns class 0 (no kerning), which was the pre-existing
// behavior for any codepoint outside the kern classes. // behavior for any codepoint outside the kern classes.
bool SdCardFont::buildMiniKernMatrix(PerStyle& s, const uint32_t* codepoints, uint32_t cpCount) { bool SdCardFont::buildMiniKernMatrix(PerStyle& s, const uint32_t* codepoints, uint32_t cpCount, HalFile& file) {
freeStyleMiniKern(s); freeStyleMiniKern(s);
if (!s.kernLeftClasses || !s.kernRightClasses || s.header.kernLeftEntryCount == 0 || if (!s.kernLeftClasses || !s.kernRightClasses || s.header.kernLeftEntryCount == 0 ||
s.header.kernRightEntryCount == 0) { s.header.kernRightEntryCount == 0) {
@@ -368,56 +368,123 @@ bool SdCardFont::buildMiniKernMatrix(PerStyle& s, const uint32_t* codepoints, ui
} }
} }
// Step 6: read the full matrix's rows for each used left class, keep only // Step 6: read the full matrix's rows for each used left class, keeping only
// columns for used right classes. One SD seek + one read per used left class; // columns for used right classes.
// a row is kernRightClassCount bytes (~200 for Literata). //
FsFile file; // Strategy: read the matrix in fixed-size chunks (KERN_CHUNK_BYTES), sweeping
if (!Storage.openFileForRead("SDCF", filePath_, file)) { // forward through the file. Each chunk covers a contiguous run of full rows;
LOG_ERR("SDCF", "Failed to open .cpfont for mini kern: %s", filePath_); // we extract the needed columns inline as each chunk is read. This costs at
freeStyleMiniKern(s); // most ceil(fullMatrixBytes / KERN_CHUNK_BYTES) seeks + reads regardless of
return false; // how many left classes are used, and caps the heap spike at KERN_CHUNK_BYTES
// instead of the full matrix size (~28 KB for Literata).
//
// Fallback: if even the chunk buffer can't be allocated, fall back to one
// seek + one row read per used left class (original behavior).
static constexpr uint32_t KERN_CHUNK_BYTES = 4096;
const uint32_t rowBytes = s.header.kernRightClassCount;
// Build a sorted-by-oldL mapping so we sweep the file forward.
// Each entry: (oldL, newL) — oldL is the file row index (1-based).
// We reuse the usedLeft and usedRight scratch slots (slots 0 and 1), which
// are fully consumed after step 1 and not read again. newToOldLeft/Right
// (slots 4 and 5) must stay intact — they are still read during column
// extraction in the inner loop below.
uint8_t* sortedOldL = base + 0 * 256; // reuse usedLeft slot
uint8_t* sortedNewL = base + 1 * 256; // reuse usedRight slot
{
uint8_t k = 0;
// Sweep oldL in ascending order — newToOldLeft maps newL→oldL, so we
// scan all 255 possible oldL values and emit matches in order. O(255×numLeft).
for (int oldL = 1; oldL < 256; oldL++) {
for (uint8_t newL = 1; newL <= numLeft; newL++) {
if (newToOldLeft[newL] == static_cast<uint8_t>(oldL)) {
sortedOldL[k] = static_cast<uint8_t>(oldL);
sortedNewL[k] = newL;
k++;
break;
}
}
}
// k == numLeft at this point
} }
std::unique_ptr<int8_t[]> rowBuf(new (std::nothrow) int8_t[s.header.kernRightClassCount]); std::unique_ptr<int8_t[]> chunkBuf(new (std::nothrow) int8_t[KERN_CHUNK_BYTES]);
if (!rowBuf) { if (chunkBuf) {
LOG_ERR("SDCF", "Failed to allocate row buffer (%u bytes)", s.header.kernRightClassCount); // Chunked sweep: one seek per chunk boundary, rows processed in oldL order.
file.close(); uint32_t chunkStart = UINT32_MAX; // file offset of first byte in chunkBuf
freeStyleMiniKern(s); uint32_t chunkEnd = 0; // one-past-last valid byte in chunkBuf
return false; uint32_t chunkSeeks = 0;
}
for (uint8_t newL = 1; newL <= numLeft; newL++) { for (uint8_t k = 0; k < numLeft; k++) {
const uint8_t oldL = newToOldLeft[newL]; const uint8_t oldL = sortedOldL[k];
const uint32_t rowFileOff = s.kernMatrixFileOffset + (oldL - 1u) * s.header.kernRightClassCount; const uint8_t newL = sortedNewL[k];
if (!file.seekSet(rowFileOff)) { const uint32_t rowFileOff = s.kernMatrixFileOffset + (oldL - 1u) * rowBytes;
LOG_ERR("SDCF", "Failed to seek to kern row %u", oldL); const uint32_t rowFileEnd = rowFileOff + rowBytes;
file.close();
// If this row is not fully inside the current chunk, load the next chunk.
if (rowFileOff < chunkStart || rowFileEnd > chunkEnd) {
chunkStart = rowFileOff;
uint32_t toRead = KERN_CHUNK_BYTES;
const uint32_t matrixEnd =
s.kernMatrixFileOffset + static_cast<uint32_t>(s.header.kernLeftClassCount) * rowBytes;
if (chunkStart + toRead > matrixEnd) toRead = matrixEnd - chunkStart;
if (!file.seekSet(chunkStart)) {
LOG_ERR("SDCF", "Failed to seek to kern chunk at %u", chunkStart);
freeStyleMiniKern(s);
return false;
}
if (file.read(reinterpret_cast<uint8_t*>(chunkBuf.get()), toRead) != static_cast<int>(toRead)) {
LOG_ERR("SDCF", "Failed to read kern chunk (%u bytes)", toRead);
freeStyleMiniKern(s);
return false;
}
chunkEnd = chunkStart + toRead;
chunkSeeks++;
}
const int8_t* srcRow = chunkBuf.get() + (rowFileOff - chunkStart);
int8_t* miniRow = s.miniKernMatrix + (newL - 1u) * numRight;
for (uint8_t newR = 1; newR <= numRight; newR++) {
miniRow[newR - 1] = srcRow[newToOldRight[newR] - 1u];
}
}
LOG_DBG("SDCF", "Built mini kern (chunked %uB): %u×%u=%u bytes, %u seeks (full was %u×%u=%u bytes)",
KERN_CHUNK_BYTES, numLeft, numRight, matrixBytes, chunkSeeks, s.header.kernLeftClassCount,
s.header.kernRightClassCount, static_cast<uint32_t>(s.header.kernLeftClassCount) * rowBytes);
} else {
// Fallback: one seek + one row read per used left class.
LOG_DBG("SDCF", "Built mini kern (per-row fallback): %u rows", numLeft);
std::unique_ptr<int8_t[]> rowBuf(new (std::nothrow) int8_t[rowBytes]);
if (!rowBuf) {
LOG_ERR("SDCF", "Failed to allocate row buffer (%u bytes)", rowBytes);
freeStyleMiniKern(s); freeStyleMiniKern(s);
return false; return false;
} }
if (file.read(reinterpret_cast<uint8_t*>(rowBuf.get()), s.header.kernRightClassCount) != for (uint8_t k = 0; k < numLeft; k++) {
static_cast<int>(s.header.kernRightClassCount)) { const uint8_t oldL = sortedOldL[k];
LOG_ERR("SDCF", "Failed to read kern row %u", oldL); const uint8_t newL = sortedNewL[k];
file.close(); const uint32_t rowFileOff = s.kernMatrixFileOffset + (oldL - 1u) * rowBytes;
freeStyleMiniKern(s); if (!file.seekSet(rowFileOff)) {
return false; LOG_ERR("SDCF", "Failed to seek to kern row %u", oldL);
} freeStyleMiniKern(s);
int8_t* miniRow = s.miniKernMatrix + (newL - 1u) * numRight; return false;
for (uint8_t newR = 1; newR <= numRight; newR++) { }
miniRow[newR - 1] = rowBuf[newToOldRight[newR] - 1u]; if (file.read(reinterpret_cast<uint8_t*>(rowBuf.get()), rowBytes) != static_cast<int>(rowBytes)) {
LOG_ERR("SDCF", "Failed to read kern row %u", oldL);
freeStyleMiniKern(s);
return false;
}
int8_t* miniRow = s.miniKernMatrix + (newL - 1u) * numRight;
for (uint8_t newR = 1; newR <= numRight; newR++) {
miniRow[newR - 1] = rowBuf[newToOldRight[newR] - 1u];
}
} }
} }
file.close();
s.miniKernLeftEntryCount = lIdx; s.miniKernLeftEntryCount = lIdx;
s.miniKernRightEntryCount = rIdx; s.miniKernRightEntryCount = rIdx;
s.miniKernLeftClassCount = numLeft; s.miniKernLeftClassCount = numLeft;
s.miniKernRightClassCount = numRight; s.miniKernRightClassCount = numRight;
LOG_DBG("SDCF", "Built mini kern: %u×%u matrix (%u bytes, full was %u×%u = %u bytes)", numLeft, numRight, matrixBytes,
s.header.kernLeftClassCount, s.header.kernRightClassCount,
static_cast<uint32_t>(s.header.kernLeftClassCount) * s.header.kernRightClassCount);
return true; return true;
} }
@@ -1164,7 +1231,6 @@ int SdCardFont::prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint3
} }
uint32_t sdTime = millis() - sdStart; uint32_t sdTime = millis() - sdStart;
if (file) file.close();
delete[] mappings; delete[] mappings;
// Kern/ligature wiring strategy: // Kern/ligature wiring strategy:
@@ -1183,7 +1249,17 @@ int SdCardFont::prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint3
bool kernLigOk = false; bool kernLigOk = false;
if (!metadataOnly) { if (!metadataOnly) {
if (loadStyleKernLigatureData(s)) { if (loadStyleKernLigatureData(s)) {
kernLigOk = buildMiniKernMatrix(s, codepoints, cpCount); // Reuse the file handle from the bitmap pass to avoid a redundant
// Storage.openFileForRead() inside buildMiniKernMatrix (fix A).
if (!file) {
if (!Storage.openFileForRead("SDCF", filePath_, file)) {
LOG_ERR("SDCF", "Failed to open .cpfont for kern matrix (style %u)", styleIdx);
} else {
kernLigOk = buildMiniKernMatrix(s, codepoints, cpCount, file);
}
} else {
kernLigOk = buildMiniKernMatrix(s, codepoints, cpCount, file);
}
} }
} else if (loadKernLigatureData) { } else if (loadKernLigatureData) {
loadStyleKernLigatureData(s, /*ligatureOnly=*/true); loadStyleKernLigatureData(s, /*ligatureOnly=*/true);
@@ -1191,6 +1267,8 @@ int SdCardFont::prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint3
// ligatures are still resident on stubData (set in loadStyleKernLigatureData). // ligatures are still resident on stubData (set in loadStyleKernLigatureData).
} }
if (file) file.close();
// Populate miniData and swap // Populate miniData and swap
memset(&s.miniData, 0, sizeof(s.miniData)); memset(&s.miniData, 0, sizeof(s.miniData));
s.miniData.bitmap = s.miniBitmap; s.miniData.bitmap = s.miniBitmap;
+3 -1
View File
@@ -6,6 +6,8 @@
#include "EpdFont.h" #include "EpdFont.h"
#include "EpdFontData.h" #include "EpdFontData.h"
class HalFile;
class SdCardFont { class SdCardFont {
public: public:
static constexpr uint16_t MAX_PAGE_GLYPHS = 512; static constexpr uint16_t MAX_PAGE_GLYPHS = 512;
@@ -206,7 +208,7 @@ class SdCardFont {
void freeStyleKernLigatureData(PerStyle& s); void freeStyleKernLigatureData(PerStyle& s);
void freeStyleMiniKern(PerStyle& s); void freeStyleMiniKern(PerStyle& s);
bool loadStyleKernLigatureData(PerStyle& s, bool ligatureOnly = false); bool loadStyleKernLigatureData(PerStyle& s, bool ligatureOnly = false);
bool buildMiniKernMatrix(PerStyle& s, const uint32_t* codepoints, uint32_t cpCount); bool buildMiniKernMatrix(PerStyle& s, const uint32_t* codepoints, uint32_t cpCount, HalFile& file);
void applyKernLigaturePointers(const PerStyle& s, EpdFontData& data) const; void applyKernLigaturePointers(const PerStyle& s, EpdFontData& data) const;
void applyGlyphMissCallback(uint8_t styleIdx); void applyGlyphMissCallback(uint8_t styleIdx);
int32_t findGlobalGlyphIndex(const PerStyle& s, uint32_t codepoint) const; int32_t findGlobalGlyphIndex(const PerStyle& s, uint32_t codepoint) const;