A fork of https://github.com/crosspoint-reader/crosspoint-reader
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: Fix prewarm perf when a page contains many styles (#1451)

## Summary

**What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)

Fix prewarm perf when a page contains many styles.

The prewarm page buffer was a single slot, so each `prewarmCache` call
for a new font style freed the previous style's glyphs. On pages with
multiple styles (regular + bold + italic), only the last style was
prewarmed. The others fell through to the hot-group compaction path at
~2-3ms per glyph.

This was most visible on rich formatting (e.g. this [Czech prayer
book](https://stahuj.kancional.cz/e-kniha/kancional.epub) with bold
headings, italic liturgical text, and regular body), where page renders
took 3-5 seconds instead of ~700ms.

Fix: use up to 4 page buffer slots (one per font style) so all styles
stay prewarmed simultaneously.

Fixes #1450.

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? PARTIALLY: to diagnose and
brainstorm solutions.

authored by

Adrian Wilkins-Caruana and committed by
GitHub
0c9e8b3e 53beeeed

+67 -48
+56 -43
lib/EpdFont/FontDecompressor.cpp
··· 24 24 } 25 25 26 26 void FontDecompressor::freePageBuffer() { 27 - free(pageBuffer); 28 - pageBuffer = nullptr; 29 - free(pageGlyphs); 30 - pageGlyphs = nullptr; 31 - pageFont = nullptr; 32 - pageGlyphCount = 0; 27 + for (uint8_t s = 0; s < pageSlotCount; s++) { 28 + free(pageSlots[s].buffer); 29 + free(pageSlots[s].glyphs); 30 + pageSlots[s] = {}; 31 + } 32 + pageSlotCount = 0; 33 33 } 34 34 35 35 void FontDecompressor::freeHotGroup() { ··· 137 137 return &fontData->bitmap[glyph->dataOffset]; 138 138 } 139 139 140 - // Check page buffer first (populated by prewarmCache) 141 - if (pageBuffer && pageFont == fontData && pageGlyphCount > 0) { 142 - int left = 0, right = pageGlyphCount - 1; 140 + // Check page buffer slots (populated by prewarmCache — one slot per font style) 141 + for (uint8_t s = 0; s < pageSlotCount; s++) { 142 + const auto& slot = pageSlots[s]; 143 + if (slot.fontData != fontData || slot.glyphCount == 0) continue; 144 + 145 + int left = 0, right = slot.glyphCount - 1; 143 146 while (left <= right) { 144 147 int mid = left + (right - left) / 2; 145 - if (pageGlyphs[mid].glyphIndex == glyphIndex) { 146 - if (pageGlyphs[mid].bufferOffset != UINT32_MAX) { 148 + if (slot.glyphs[mid].glyphIndex == glyphIndex) { 149 + if (slot.glyphs[mid].bufferOffset != UINT32_MAX) { 147 150 stats.cacheHits++; 148 151 stats.getBitmapTimeUs += micros() - tStart; 149 - return &pageBuffer[pageGlyphs[mid].bufferOffset]; 152 + return &slot.buffer[slot.glyphs[mid].bufferOffset]; 150 153 } 151 154 break; // Not extracted during prewarm; fall through to hot-group path 152 155 } 153 - if (pageGlyphs[mid].glyphIndex < glyphIndex) 156 + if (slot.glyphs[mid].glyphIndex < glyphIndex) 154 157 left = mid + 1; 155 158 else 156 159 right = mid - 1; 157 160 } 161 + break; // Found the right slot but glyph wasn't in it; don't check other slots 158 162 } 159 163 160 164 // Fallback: hot group slot ··· 239 243 } 240 244 241 245 int FontDecompressor::prewarmCache(const EpdFontData* fontData, const char* utf8Text) { 242 - freePageBuffer(); 243 246 if (!fontData || !fontData->groups || !utf8Text) return 0; 247 + 248 + // Allocate the next available slot (caller must call freePageBuffer/clearCache to reset) 249 + if (pageSlotCount >= MAX_PAGE_SLOTS) { 250 + LOG_ERR("FDC", "All %u page buffer slots full, cannot prewarm fontData=%p", MAX_PAGE_SLOTS, (void*)fontData); 251 + return -1; 252 + } 253 + PageSlot& slot = pageSlots[pageSlotCount]; 244 254 245 255 // Step 1: Collect unique glyph indices needed for this page 246 256 uint32_t neededGlyphs[MAX_PAGE_GLYPHS]; ··· 304 314 305 315 stats.uniqueGroupsAccessed = groupCount; 306 316 307 - // Step 3: Allocate page buffer and lookup table 308 - pageBuffer = static_cast<uint8_t*>(malloc(totalBytes)); 309 - pageGlyphs = static_cast<PageGlyphEntry*>(malloc(glyphCount * sizeof(PageGlyphEntry))); 310 - if (!pageBuffer || !pageGlyphs) { 317 + // Step 3: Allocate page buffer and lookup table for this slot 318 + slot.buffer = static_cast<uint8_t*>(malloc(totalBytes)); 319 + slot.glyphs = static_cast<PageGlyphEntry*>(malloc(glyphCount * sizeof(PageGlyphEntry))); 320 + if (!slot.buffer || !slot.glyphs) { 311 321 LOG_ERR("FDC", "Failed to allocate page buffer (%u bytes, %u glyphs)", totalBytes, glyphCount); 312 - freePageBuffer(); 322 + free(slot.buffer); 323 + free(slot.glyphs); 324 + slot = {}; 313 325 return glyphCount; 314 326 } 315 - stats.pageBufferBytes = totalBytes; 316 - stats.pageGlyphsBytes = glyphCount * sizeof(PageGlyphEntry); 327 + stats.pageBufferBytes += totalBytes; 328 + stats.pageGlyphsBytes += glyphCount * sizeof(PageGlyphEntry); 317 329 318 - pageFont = fontData; 319 - pageGlyphCount = glyphCount; 330 + slot.fontData = fontData; 331 + slot.glyphCount = glyphCount; 332 + pageSlotCount++; 320 333 321 334 // Initialize lookup entries (bufferOffset = UINT32_MAX means not yet extracted) 322 335 for (uint16_t i = 0; i < glyphCount; i++) { 323 - pageGlyphs[i] = {neededGlyphs[i], UINT32_MAX, 0}; 336 + slot.glyphs[i] = {neededGlyphs[i], UINT32_MAX, 0}; 324 337 } 325 338 326 339 // Sort by glyphIndex for binary search in getBitmap() 327 340 for (uint16_t i = 1; i < glyphCount; i++) { 328 - PageGlyphEntry key = pageGlyphs[i]; 341 + PageGlyphEntry key = slot.glyphs[i]; 329 342 int j = i - 1; 330 - while (j >= 0 && pageGlyphs[j].glyphIndex > key.glyphIndex) { 331 - pageGlyphs[j + 1] = pageGlyphs[j]; 343 + while (j >= 0 && slot.glyphs[j].glyphIndex > key.glyphIndex) { 344 + slot.glyphs[j + 1] = slot.glyphs[j]; 332 345 j--; 333 346 } 334 - pageGlyphs[j + 1] = key; 347 + slot.glyphs[j + 1] = key; 335 348 } 336 349 337 350 // Step 3b: Pre-scan to compute each needed glyph's byte-aligned offset within its group. ··· 357 370 358 371 const EpdGlyph& glyph = fontData->glyph[i]; 359 372 360 - // Binary search in sorted pageGlyphs to find if glyph i is needed 361 - int left = 0, right = (int)pageGlyphCount - 1; 373 + // Binary search in sorted slot.glyphs to find if glyph i is needed 374 + int left = 0, right = (int)slot.glyphCount - 1; 362 375 while (left <= right) { 363 376 const int mid = left + (right - left) / 2; 364 - if (pageGlyphs[mid].glyphIndex == i) { 365 - pageGlyphs[mid].alignedOffset = groupAlignedTracker[gpPos]; 377 + if (slot.glyphs[mid].glyphIndex == i) { 378 + slot.glyphs[mid].alignedOffset = groupAlignedTracker[gpPos]; 366 379 break; 367 380 } 368 - if (pageGlyphs[mid].glyphIndex < i) 381 + if (slot.glyphs[mid].glyphIndex < i) 369 382 left = mid + 1; 370 383 else 371 384 right = mid - 1; ··· 384 397 const uint32_t glyphI = group.firstGlyphIndex + j; 385 398 const EpdGlyph& glyph = fontData->glyph[glyphI]; 386 399 387 - int left = 0, right = (int)pageGlyphCount - 1; 400 + int left = 0, right = (int)slot.glyphCount - 1; 388 401 while (left <= right) { 389 402 const int mid = left + (right - left) / 2; 390 - if (pageGlyphs[mid].glyphIndex == glyphI) { 391 - pageGlyphs[mid].alignedOffset = alignedOff; 403 + if (slot.glyphs[mid].glyphIndex == glyphI) { 404 + slot.glyphs[mid].alignedOffset = alignedOff; 392 405 break; 393 406 } 394 - if (pageGlyphs[mid].glyphIndex < glyphI) 407 + if (slot.glyphs[mid].glyphIndex < glyphI) 395 408 left = mid + 1; 396 409 else 397 410 right = mid - 1; ··· 430 443 431 444 // Extract needed glyphs directly from the byte-aligned temp buffer, compacting on the fly. 432 445 // alignedOffset was pre-computed in step 3b — no full-group compact scan needed. 433 - for (uint16_t i = 0; i < pageGlyphCount; i++) { 434 - if (pageGlyphs[i].bufferOffset != UINT32_MAX) continue; // already extracted 435 - if (getGroupIndex(fontData, pageGlyphs[i].glyphIndex) != groupIdx) continue; 446 + for (uint16_t i = 0; i < slot.glyphCount; i++) { 447 + if (slot.glyphs[i].bufferOffset != UINT32_MAX) continue; // already extracted 448 + if (getGroupIndex(fontData, slot.glyphs[i].glyphIndex) != groupIdx) continue; 436 449 437 - const EpdGlyph& glyph = fontData->glyph[pageGlyphs[i].glyphIndex]; 438 - compactSingleGlyph(&tempBuf[pageGlyphs[i].alignedOffset], &pageBuffer[writeOffset], glyph.width, glyph.height); 439 - pageGlyphs[i].bufferOffset = writeOffset; 450 + const EpdGlyph& glyph = fontData->glyph[slot.glyphs[i].glyphIndex]; 451 + compactSingleGlyph(&tempBuf[slot.glyphs[i].alignedOffset], &slot.buffer[writeOffset], glyph.width, glyph.height); 452 + slot.glyphs[i].bufferOffset = writeOffset; 440 453 writeOffset += glyph.dataLength; 441 454 } 442 455
+11 -5
lib/EpdFont/FontDecompressor.h
··· 9 9 class FontDecompressor { 10 10 public: 11 11 static constexpr uint16_t MAX_PAGE_GLYPHS = 512; 12 + static constexpr uint8_t MAX_PAGE_SLOTS = 4; // One per font style (R/B/I/BI) 12 13 13 14 FontDecompressor() = default; 14 15 ~FontDecompressor(); ··· 48 49 Stats stats; 49 50 InflateReader inflateReader; 50 51 51 - // Page buffer: flat array of prewarmed glyph bitmaps with sorted lookup 52 + // Page buffer slots: each style gets its own flat glyph buffer with sorted lookup. 53 + // Up to MAX_PAGE_SLOTS (4) styles can be prewarmed simultaneously. 52 54 struct PageGlyphEntry { 53 55 uint32_t glyphIndex; 54 56 uint32_t bufferOffset; 55 57 uint32_t alignedOffset; // byte-aligned offset within its decompressed group (set during prewarm pre-scan) 56 58 }; 57 - uint8_t* pageBuffer = nullptr; 58 - const EpdFontData* pageFont = nullptr; 59 - PageGlyphEntry* pageGlyphs = nullptr; 60 - uint16_t pageGlyphCount = 0; 59 + struct PageSlot { 60 + uint8_t* buffer = nullptr; 61 + const EpdFontData* fontData = nullptr; 62 + PageGlyphEntry* glyphs = nullptr; 63 + uint16_t glyphCount = 0; 64 + }; 65 + PageSlot pageSlots[MAX_PAGE_SLOTS] = {}; 66 + uint8_t pageSlotCount = 0; 61 67 62 68 // Hot group: last decompressed group (byte-aligned) for non-prewarmed fallback path. 63 69 // Kept in byte-aligned format; individual glyphs are compacted on demand into hotGlyphBuf.