Further optimisations
This commit is contained in:
+118
-40
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user