Merge branch 'master' of https://github.com/jpirnay/crosspoint-reader into feat-rendering-perf

This commit is contained in:
jpirnay
2026-04-24 17:22:10 +02:00
7 changed files with 446 additions and 20 deletions
+1
View File
@@ -389,6 +389,7 @@ STR_DELETE_CONFIRM: "Delete this server?"
STR_OPDS_SERVERS: "OPDS Servers"
STR_AUTO_TURN_ENABLED: "Auto Turn Enabled: "
STR_AUTO_TURN_PAGES_PER_MIN: "Auto Turn (Pages Per Minute)"
STR_RENDER_BENCHMARK: "Render Benchmark"
STR_WEATHER: "Weather"
STR_WEATHER_LOCATION: "Location"
STR_WEATHER_UNITS: "Units"
+298 -18
View File
@@ -7,6 +7,7 @@
#include <Epub/Page.h>
#include <Epub/blocks/TextBlock.h>
#include <FontCacheManager.h>
#include <FontDecompressor.h>
#include <FsHelpers.h>
#include <GfxRenderer.h>
#include <HalStorage.h>
@@ -22,6 +23,7 @@
#include "EpubReaderChapterSelectionActivity.h"
#include "EpubReaderFootnotesActivity.h"
#include "EpubReaderPercentSelectionActivity.h"
#include "EpubRenderBenchmarkActivity.h"
#include "GlobalBookmarkIndex.h"
#include "KOReaderCredentialStore.h"
#include "MappedInputManager.h"
@@ -79,6 +81,20 @@ int clampPercent(int percent) {
return percent;
}
const char* orientationToString(const GfxRenderer::Orientation orientation) {
switch (orientation) {
case GfxRenderer::Portrait:
return "Portrait";
case GfxRenderer::LandscapeClockwise:
return "Landscape CW";
case GfxRenderer::PortraitInverted:
return "Portrait Inverted";
case GfxRenderer::LandscapeCounterClockwise:
return "Landscape CCW";
}
return "Unknown";
}
} // namespace
void EpubReaderActivity::onEnter() {
@@ -593,6 +609,10 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
onGoHome();
return;
}
case EpubReaderMenuActivity::MenuAction::RENDER_BENCHMARK: {
runRenderBenchmark();
break;
}
case EpubReaderMenuActivity::MenuAction::SCREENSHOT: {
{
RenderLock lock(*this);
@@ -620,6 +640,202 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
}
}
void EpubReaderActivity::runRenderBenchmark() {
if (!epub) {
return;
}
if (!section) {
requestUpdateAndWait();
if (!section) {
return;
}
}
const LastRenderStats startSnapshot = lastRenderStats;
BenchmarkAggregate aggregate;
auto recordRender = [&aggregate](const LastRenderStats& snapshot) {
if (!snapshot.valid) {
return;
}
aggregate.renderCount++;
aggregate.imagePageCount += snapshot.hadImages ? 1 : 0;
aggregate.cacheRebuildCount += snapshot.cacheRebuilt ? 1 : 0;
if (snapshot.footnoteCount > aggregate.maxFootnotes) {
aggregate.maxFootnotes = snapshot.footnoteCount;
}
aggregate.totalRequestRenderMs += snapshot.requestRenderMs;
if (aggregate.renderCount == 1 || snapshot.requestRenderMs < aggregate.minRequestRenderMs) {
aggregate.minRequestRenderMs = snapshot.requestRenderMs;
}
if (snapshot.requestRenderMs > aggregate.maxRequestRenderMs) {
aggregate.maxRequestRenderMs = snapshot.requestRenderMs;
}
aggregate.totalRenderMs += snapshot.phases.totalMs;
if (aggregate.renderCount == 1 || snapshot.phases.totalMs < aggregate.minRenderMs) {
aggregate.minRenderMs = snapshot.phases.totalMs;
}
if (snapshot.phases.totalMs > aggregate.maxRenderMs) {
aggregate.maxRenderMs = snapshot.phases.totalMs;
}
aggregate.totalSectionLoadMs += snapshot.sectionLoadMs;
aggregate.totalPageLoadMs += snapshot.pageLoadMs;
aggregate.totalPhases.prewarmMs += snapshot.phases.prewarmMs;
aggregate.totalPhases.bwRenderMs += snapshot.phases.bwRenderMs;
aggregate.totalPhases.displayMs += snapshot.phases.displayMs;
aggregate.totalPhases.bwStoreMs += snapshot.phases.bwStoreMs;
aggregate.totalPhases.grayLsbMs += snapshot.phases.grayLsbMs;
aggregate.totalPhases.grayMsbMs += snapshot.phases.grayMsbMs;
aggregate.totalPhases.grayDisplayMs += snapshot.phases.grayDisplayMs;
aggregate.totalPhases.bwRestoreMs += snapshot.phases.bwRestoreMs;
aggregate.totalPhases.totalMs += snapshot.phases.totalMs;
aggregate.totalFontCacheHits += snapshot.fontCacheHits;
aggregate.totalFontCacheMisses += snapshot.fontCacheMisses;
aggregate.totalFontDecompressMs += snapshot.fontDecompressMs;
aggregate.totalFontGetBitmapTimeUs += snapshot.fontGetBitmapTimeUs;
aggregate.totalFontGetBitmapCalls += snapshot.fontGetBitmapCalls;
if (aggregate.renderCount == 1 || snapshot.freeHeapAfter < aggregate.minFreeHeapAfter) {
aggregate.minFreeHeapAfter = snapshot.freeHeapAfter;
}
if (snapshot.freeHeapAfter > aggregate.maxFreeHeapAfter) {
aggregate.maxFreeHeapAfter = snapshot.freeHeapAfter;
}
};
const unsigned long startTime = millis();
int forwardTurns = 0;
int backwardTurns = 0;
for (int i = 0; i < 10; i++) {
if (!stepPageState(true)) {
break;
}
requestUpdateAndWait();
recordRender(lastRenderStats);
forwardTurns++;
}
const unsigned long forwardMs = millis() - startTime;
const unsigned long backwardStart = millis();
for (int i = 0; i < 10; i++) {
if (!stepPageState(false)) {
break;
}
requestUpdateAndWait();
recordRender(lastRenderStats);
backwardTurns++;
}
const unsigned long backwardMs = millis() - backwardStart;
startActivityForResult(
std::make_unique<EpubRenderBenchmarkActivity>(
renderer, mappedInput,
buildRenderBenchmarkReport(startSnapshot, aggregate, forwardTurns, forwardMs, backwardTurns, backwardMs)),
[this](const ActivityResult&) { requestUpdate(); });
}
std::string EpubReaderActivity::buildRenderBenchmarkReport(const LastRenderStats& startSnapshot,
const BenchmarkAggregate& aggregate, const int forwardTurns,
const unsigned long forwardMs, const int backwardTurns,
const unsigned long backwardMs) const {
const LastRenderStats& endSnapshot = lastRenderStats.valid ? lastRenderStats : startSnapshot;
std::string report;
report.reserve(768);
auto appendLine = [&report](const std::string& line) {
if (!report.empty()) {
report += '\n';
}
report += line;
};
appendLine("Forward 10: " + std::to_string(forwardTurns) + " turns in " + std::to_string(forwardMs) + " ms");
if (forwardTurns > 0) {
appendLine("Forward avg: " + std::to_string(forwardMs / static_cast<unsigned long>(forwardTurns)) + " ms/turn");
}
appendLine("Backward 10: " + std::to_string(backwardTurns) + " turns in " + std::to_string(backwardMs) + " ms");
if (backwardTurns > 0) {
appendLine("Backward avg: " + std::to_string(backwardMs / static_cast<unsigned long>(backwardTurns)) + " ms/turn");
}
appendLine("Measured renders: " + std::to_string(aggregate.renderCount) + ", image pages " +
std::to_string(aggregate.imagePageCount) + ", cache rebuilds " +
std::to_string(aggregate.cacheRebuildCount));
appendLine("Start: spine " + std::to_string(startSnapshot.spineIndex) + ", page " +
std::to_string(startSnapshot.pageIndex + 1) + "/" + std::to_string(startSnapshot.pageCount));
appendLine("End: spine " + std::to_string(endSnapshot.spineIndex) + ", page " +
std::to_string(endSnapshot.pageIndex + 1) + "/" + std::to_string(endSnapshot.pageCount));
appendLine("Orientation: " +
std::string(orientationToString(static_cast<GfxRenderer::Orientation>(endSnapshot.orientation))));
appendLine("Viewport: " + std::to_string(endSnapshot.viewportWidth) + "x" +
std::to_string(endSnapshot.viewportHeight) + " px, margins T/R/B/L " +
std::to_string(endSnapshot.marginTop) + "/" + std::to_string(endSnapshot.marginRight) + "/" +
std::to_string(endSnapshot.marginBottom) + "/" + std::to_string(endSnapshot.marginLeft));
appendLine("Font: " + std::to_string(endSnapshot.effectiveFontId) + ", embedded CSS " +
std::string(endSnapshot.embeddedStyle ? "on" : "off") + ", images " +
std::to_string(endSnapshot.imageRendering) + ", AA " +
std::string(endSnapshot.textAntiAliasing ? "on" : "off"));
appendLine("Last page: images " + std::string(endSnapshot.hadImages ? "yes" : "no") + ", footnotes " +
std::to_string(endSnapshot.footnoteCount) + ", cache rebuilt " +
std::string(endSnapshot.cacheRebuilt ? "yes" : "no"));
if (aggregate.renderCount > 0) {
appendLine("Render avg/min/max: request " +
std::to_string(aggregate.totalRequestRenderMs / static_cast<unsigned long>(aggregate.renderCount)) +
"/" + std::to_string(aggregate.minRequestRenderMs) + "/" + std::to_string(aggregate.maxRequestRenderMs) +
" ms, core " +
std::to_string(aggregate.totalRenderMs / static_cast<unsigned long>(aggregate.renderCount)) + "/" +
std::to_string(aggregate.minRenderMs) + "/" + std::to_string(aggregate.maxRenderMs) + " ms");
appendLine("Aggregate loads: section " + std::to_string(aggregate.totalSectionLoadMs) + " ms, page " +
std::to_string(aggregate.totalPageLoadMs) + " ms, max footnotes " +
std::to_string(aggregate.maxFootnotes));
appendLine("Aggregate phases: prewarm " + std::to_string(aggregate.totalPhases.prewarmMs) + ", bw " +
std::to_string(aggregate.totalPhases.bwRenderMs) + ", display " +
std::to_string(aggregate.totalPhases.displayMs) + ", gray total " +
std::to_string(aggregate.totalPhases.grayLsbMs + aggregate.totalPhases.grayMsbMs +
aggregate.totalPhases.grayDisplayMs));
appendLine("Aggregate font: hits " + std::to_string(aggregate.totalFontCacheHits) + ", misses " +
std::to_string(aggregate.totalFontCacheMisses) + ", decompress " +
std::to_string(aggregate.totalFontDecompressMs) + " ms");
appendLine("Aggregate glyph lookups: " + std::to_string(aggregate.totalFontGetBitmapCalls) + " calls, " +
std::to_string(aggregate.totalFontGetBitmapTimeUs) + " us total");
appendLine("Heap after render min/max: " + std::to_string(aggregate.minFreeHeapAfter) + "/" +
std::to_string(aggregate.maxFreeHeapAfter));
}
appendLine("Last render: request " + std::to_string(endSnapshot.requestRenderMs) + " ms, section load " +
std::to_string(endSnapshot.sectionLoadMs) + " ms, page load " + std::to_string(endSnapshot.pageLoadMs) +
" ms, render total " + std::to_string(endSnapshot.phases.totalMs) + " ms");
appendLine("Phases: prewarm " + std::to_string(endSnapshot.phases.prewarmMs) + ", bw " +
std::to_string(endSnapshot.phases.bwRenderMs) + ", display " +
std::to_string(endSnapshot.phases.displayMs) + ", store " + std::to_string(endSnapshot.phases.bwStoreMs) +
", gray lsb " + std::to_string(endSnapshot.phases.grayLsbMs) + ", gray msb " +
std::to_string(endSnapshot.phases.grayMsbMs) + ", gray display " +
std::to_string(endSnapshot.phases.grayDisplayMs) + ", restore " +
std::to_string(endSnapshot.phases.bwRestoreMs));
appendLine("Font cache: hits " + std::to_string(endSnapshot.fontCacheHits) + ", misses " +
std::to_string(endSnapshot.fontCacheMisses) + ", decompress " +
std::to_string(endSnapshot.fontDecompressMs) + " ms, groups " +
std::to_string(endSnapshot.fontUniqueGroups));
appendLine("Font buffers: page " + std::to_string(endSnapshot.fontPageBufferBytes) + ", glyph table " +
std::to_string(endSnapshot.fontPageGlyphsBytes) + ", hot group " +
std::to_string(endSnapshot.fontHotGroupBytes) + ", peak temp " +
std::to_string(endSnapshot.fontPeakTempBytes));
appendLine("Glyph lookups: " + std::to_string(endSnapshot.fontGetBitmapCalls) + " calls, " +
std::to_string(endSnapshot.fontGetBitmapTimeUs) + " us total");
appendLine("Heap: before " + std::to_string(endSnapshot.freeHeapBefore) + "/" +
std::to_string(endSnapshot.largestFreeBlockBefore) + ", after " +
std::to_string(endSnapshot.freeHeapAfter) + "/" + std::to_string(endSnapshot.largestFreeBlockAfter));
return report;
}
void EpubReaderActivity::launchKOReaderSync(const SyncLaunchMode mode) {
if (!epub) {
return;
@@ -903,33 +1119,43 @@ int EpubReaderActivity::getEffectiveReaderFontId() const {
}
}
void EpubReaderActivity::pageTurn(bool isForwardTurn) {
bool EpubReaderActivity::stepPageState(const bool isForwardTurn) {
if (!epub || !section || section->pageCount <= 0) {
return false;
}
if (isForwardTurn) {
if (section->currentPage < section->pageCount - 1) {
section->currentPage++;
} else if (currentSpineIndex + 1 < epub->getSpineItemsCount()) {
RenderLock lock(*this);
nextPageNumber = 0;
currentSpineIndex++;
section.reset();
} else {
// We don't want to delete the section mid-render, so grab the semaphore
{
RenderLock lock(*this);
nextPageNumber = 0;
currentSpineIndex++;
section.reset();
}
return false;
}
} else {
if (section->currentPage > 0) {
section->currentPage--;
} else if (currentSpineIndex > 0) {
// We don't want to delete the section mid-render, so grab the semaphore
{
RenderLock lock(*this);
nextPageNumber = UINT16_MAX;
currentSpineIndex--;
section.reset();
}
RenderLock lock(*this);
nextPageNumber = UINT16_MAX;
currentSpineIndex--;
section.reset();
} else {
return false;
}
}
lastPageTurnTime = millis();
return true;
}
void EpubReaderActivity::pageTurn(bool isForwardTurn) {
if (!stepPageState(isForwardTurn)) {
return;
}
requestUpdate();
}
@@ -979,18 +1205,34 @@ void EpubReaderActivity::render(RenderLock&& lock) {
const uint16_t viewportWidth = renderer.getScreenWidth() - orientedMarginLeft - orientedMarginRight;
const uint16_t viewportHeight = renderer.getScreenHeight() - orientedMarginTop - orientedMarginBottom;
lastRenderStats = {};
lastRenderStats.orientation = static_cast<uint8_t>(renderer.getOrientation());
lastRenderStats.marginTop = orientedMarginTop;
lastRenderStats.marginRight = orientedMarginRight;
lastRenderStats.marginBottom = orientedMarginBottom;
lastRenderStats.marginLeft = orientedMarginLeft;
lastRenderStats.viewportWidth = viewportWidth;
lastRenderStats.viewportHeight = viewportHeight;
lastRenderStats.embeddedStyle = getEffectiveEmbeddedStyle();
lastRenderStats.imageRendering = getEffectiveImageRendering();
lastRenderStats.effectiveFontId = getEffectiveReaderFontId();
lastRenderStats.textAntiAliasing = SETTINGS.textAntiAliasing;
lastRenderStats.freeHeapBefore = esp_get_free_heap_size();
lastRenderStats.largestFreeBlockBefore = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_DEFAULT);
if (!section) {
const bool embeddedStyle = getEffectiveEmbeddedStyle();
const uint8_t imageRendering = getEffectiveImageRendering();
const bool embeddedStyle = lastRenderStats.embeddedStyle;
const uint8_t imageRendering = lastRenderStats.imageRendering;
const auto filepath = epub->getSpineItem(currentSpineIndex).href;
LOG_DBG("ERS", "Loading file: %s, index: %d", filepath.c_str(), currentSpineIndex);
section = std::make_unique<Section>(epub, currentSpineIndex, renderer);
const unsigned long sectionStart = millis();
if (!section->loadSectionFile(getEffectiveReaderFontId(), SETTINGS.getReaderLineCompression(),
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering)) {
LOG_DBG("ERS", "Cache not found, building...");
lastRenderStats.cacheRebuilt = true;
Rect popupRect{};
const auto progressFn = [this, &popupRect](int progress) {
@@ -1011,6 +1253,7 @@ void EpubReaderActivity::render(RenderLock&& lock) {
} else {
LOG_DBG("ERS", "Cache found, skipping build...");
}
lastRenderStats.sectionLoadMs = millis() - sectionStart;
if (nextPageNumber == UINT16_MAX) {
section->currentPage = section->pageCount - 1;
@@ -1097,7 +1340,9 @@ void EpubReaderActivity::render(RenderLock&& lock) {
}
{
const unsigned long pageLoadStart = millis();
auto p = section->loadPageFromSectionFile();
lastRenderStats.pageLoadMs = millis() - pageLoadStart;
if (!p) {
LOG_ERR("ERS", "Failed to load page from SD - clearing section cache");
section->clearCache();
@@ -1110,13 +1355,22 @@ void EpubReaderActivity::render(RenderLock&& lock) {
// Collect footnotes from the loaded page
currentPageFootnotes = std::move(p->footnotes);
lastRenderStats.hadImages = p->hasImages();
lastRenderStats.footnoteCount = static_cast<int>(currentPageFootnotes.size());
lastRenderStats.spineIndex = currentSpineIndex;
lastRenderStats.pageIndex = section->currentPage;
lastRenderStats.pageCount = section->pageCount;
const auto start = millis();
renderContents(std::move(p), orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
LOG_DBG("ERS", "Rendered page in %dms", millis() - start);
lastRenderStats.requestRenderMs = millis() - start;
LOG_DBG("ERS", "Rendered page in %dms", lastRenderStats.requestRenderMs);
}
silentIndexNextChapterIfNeeded(viewportWidth, viewportHeight);
saveProgress(currentSpineIndex, section->currentPage, section->pageCount);
lastRenderStats.freeHeapAfter = esp_get_free_heap_size();
lastRenderStats.largestFreeBlockAfter = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_DEFAULT);
lastRenderStats.valid = true;
if (pendingScreenshot) {
pendingScreenshot = false;
@@ -1200,6 +1454,8 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
bool imagePageWithAA = page->hasImages() && SETTINGS.textAntiAliasing;
bool forceHalfRefreshThisPage = pendingHalfRefreshAfterImagePage && SETTINGS.halfRefreshAfterImagePage;
pendingHalfRefreshAfterImagePage = false;
lastRenderStats.imagePageWithAA = imagePageWithAA;
lastRenderStats.forcedHalfRefresh = forceHalfRefreshThisPage;
logReaderMemSnapshot("before_bw_render");
page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, orientedMarginTop);
@@ -1283,6 +1539,10 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
logReaderMemSnapshot("bw_restore_end");
const auto tEnd = millis();
lastRenderStats.usedGrayscale = true;
lastRenderStats.phases = {tPrewarm - t0, tBwRender - tPrewarm, tDisplay - tBwRender,
tBwStore - tDisplay, tGrayLsb - tBwStore, tGrayMsb - tGrayLsb,
tGrayDisplay - tGrayMsb, tBwRestore - tGrayDisplay, tEnd - t0};
LOG_DBG("ERS",
"Page render: prewarm=%lums bw_render=%lums display=%lums bw_store=%lums "
"gray_lsb=%lums gray_msb=%lums gray_display=%lums bw_restore=%lums total=%lums",
@@ -1296,11 +1556,31 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
logReaderMemSnapshot("bw_restore_end");
const auto tEnd = millis();
lastRenderStats.usedGrayscale = false;
lastRenderStats.phases = {
tPrewarm - t0, tBwRender - tPrewarm, tDisplay - tBwRender, tBwStore - tDisplay, 0, 0, 0, tBwRestore - tBwStore,
tEnd - t0};
LOG_DBG("ERS",
"Page render: prewarm=%lums bw_render=%lums display=%lums bw_store=%lums bw_restore=%lums total=%lums",
tPrewarm - t0, tBwRender - tPrewarm, tDisplay - tBwRender, tBwStore - tDisplay, tBwRestore - tBwStore,
tEnd - t0);
}
if (auto* cacheManager = renderer.getFontCacheManager()) {
if (auto* decompressor = cacheManager->getDecompressor()) {
const auto& stats = decompressor->getStats();
lastRenderStats.fontCacheHits = stats.cacheHits;
lastRenderStats.fontCacheMisses = stats.cacheMisses;
lastRenderStats.fontDecompressMs = stats.decompressTimeMs;
lastRenderStats.fontUniqueGroups = stats.uniqueGroupsAccessed;
lastRenderStats.fontPageBufferBytes = stats.pageBufferBytes;
lastRenderStats.fontPageGlyphsBytes = stats.pageGlyphsBytes;
lastRenderStats.fontHotGroupBytes = stats.hotGroupBytes;
lastRenderStats.fontPeakTempBytes = stats.peakTempBytes;
lastRenderStats.fontGetBitmapTimeUs = stats.getBitmapTimeUs;
lastRenderStats.fontGetBitmapCalls = stats.getBitmapCalls;
}
}
}
void EpubReaderActivity::renderStatusBar() const {
@@ -38,6 +38,81 @@ class EpubReaderActivity final : public Activity {
unsigned long lastPageTurnTime = 0UL;
unsigned long pageTurnDuration = 0UL;
bool pendingHalfRefreshAfterImagePage = false;
struct RenderPhaseStats {
unsigned long prewarmMs = 0UL;
unsigned long bwRenderMs = 0UL;
unsigned long displayMs = 0UL;
unsigned long bwStoreMs = 0UL;
unsigned long grayLsbMs = 0UL;
unsigned long grayMsbMs = 0UL;
unsigned long grayDisplayMs = 0UL;
unsigned long bwRestoreMs = 0UL;
unsigned long totalMs = 0UL;
};
struct LastRenderStats {
bool valid = false;
bool cacheRebuilt = false;
bool usedGrayscale = false;
bool hadImages = false;
bool imagePageWithAA = false;
bool forcedHalfRefresh = false;
uint8_t orientation = 0;
uint8_t imageRendering = 0;
bool embeddedStyle = false;
bool textAntiAliasing = false;
int effectiveFontId = 0;
int spineIndex = 0;
int pageIndex = 0;
int pageCount = 0;
int footnoteCount = 0;
int marginTop = 0;
int marginRight = 0;
int marginBottom = 0;
int marginLeft = 0;
uint16_t viewportWidth = 0;
uint16_t viewportHeight = 0;
unsigned long sectionLoadMs = 0UL;
unsigned long pageLoadMs = 0UL;
unsigned long requestRenderMs = 0UL;
RenderPhaseStats phases;
uint32_t freeHeapBefore = 0;
uint32_t largestFreeBlockBefore = 0;
uint32_t freeHeapAfter = 0;
uint32_t largestFreeBlockAfter = 0;
uint32_t fontCacheHits = 0;
uint32_t fontCacheMisses = 0;
uint32_t fontDecompressMs = 0;
uint16_t fontUniqueGroups = 0;
uint32_t fontPageBufferBytes = 0;
uint32_t fontPageGlyphsBytes = 0;
uint32_t fontHotGroupBytes = 0;
uint32_t fontPeakTempBytes = 0;
uint32_t fontGetBitmapTimeUs = 0;
uint32_t fontGetBitmapCalls = 0;
};
struct BenchmarkAggregate {
int renderCount = 0;
int imagePageCount = 0;
int cacheRebuildCount = 0;
int maxFootnotes = 0;
unsigned long totalRequestRenderMs = 0UL;
unsigned long minRequestRenderMs = 0UL;
unsigned long maxRequestRenderMs = 0UL;
unsigned long totalRenderMs = 0UL;
unsigned long minRenderMs = 0UL;
unsigned long maxRenderMs = 0UL;
unsigned long totalSectionLoadMs = 0UL;
unsigned long totalPageLoadMs = 0UL;
RenderPhaseStats totalPhases;
uint32_t totalFontCacheHits = 0;
uint32_t totalFontCacheMisses = 0;
uint32_t totalFontDecompressMs = 0;
uint32_t totalFontGetBitmapTimeUs = 0;
uint32_t totalFontGetBitmapCalls = 0;
uint32_t minFreeHeapAfter = 0;
uint32_t maxFreeHeapAfter = 0;
};
LastRenderStats lastRenderStats;
// Signals that the next render should reposition within the newly loaded section
// based on a cross-book percentage jump.
bool pendingPercentJump = false;
@@ -95,7 +170,12 @@ class EpubReaderActivity final : public Activity {
bool getEffectiveEmbeddedStyle() const;
uint8_t getEffectiveImageRendering() const;
int getEffectiveReaderFontId() const;
bool stepPageState(bool isForwardTurn);
void pageTurn(bool isForwardTurn);
void runRenderBenchmark();
std::string buildRenderBenchmarkReport(const LastRenderStats& startSnapshot, const BenchmarkAggregate& aggregate,
int forwardTurns, unsigned long forwardMs, int backwardTurns,
unsigned long backwardMs) const;
// Footnote navigation
void navigateToHref(const std::string& href, bool savePosition = false);
@@ -33,7 +33,7 @@ EpubReaderMenuActivity::EpubReaderMenuActivity(GfxRenderer& renderer, MappedInpu
}
void EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes, bool hasStarredPages) {
menuItems.reserve(19);
menuItems.reserve(20);
// --- Navigation ---
menuItems.push_back(SettingInfo::Separator(StrId::STR_READER_NAVIGATION));
@@ -161,6 +161,8 @@ void EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes, bool hasStarredPa
menuItems.push_back(
SettingInfo::Action(StrId::STR_DELETE_CACHE, SettingAction::None).withSubmenu(StrId::STR_READER_TOOLS));
menuItems.push_back(SettingInfo::Action(StrId::STR_GO_HOME_BUTTON, SettingAction::None));
menuItems.push_back(
SettingInfo::Action(StrId::STR_RENDER_BENCHMARK, SettingAction::None).withSubmenu(StrId::STR_READER_TOOLS));
}
EpubReaderMenuActivity::MenuAction EpubReaderMenuActivity::actionForNameId(StrId nameId) {
@@ -195,6 +197,8 @@ EpubReaderMenuActivity::MenuAction EpubReaderMenuActivity::actionForNameId(StrId
return MenuAction::DISPLAY_QR;
case StrId::STR_DELETE_CACHE:
return MenuAction::DELETE_CACHE;
case StrId::STR_RENDER_BENCHMARK:
return MenuAction::RENDER_BENCHMARK;
case StrId::STR_GO_HOME_BUTTON:
return MenuAction::GO_HOME;
default:
@@ -28,7 +28,8 @@ class EpubReaderMenuActivity final : public MenuListActivity {
PUSH_LOCAL,
STARRED_PAGES,
STAR_PAGE,
DELETE_CACHE
DELETE_CACHE,
RENDER_BENCHMARK
};
explicit EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& title,
@@ -0,0 +1,42 @@
#include "EpubRenderBenchmarkActivity.h"
#include <GfxRenderer.h>
#include "components/UITheme.h"
#include "fontIds.h"
void EpubRenderBenchmarkActivity::onEnter() {
Activity::onEnter();
requestUpdate();
}
void EpubRenderBenchmarkActivity::loop() {
if (mappedInput.wasAnyPressed() || mappedInput.wasAnyReleased()) {
finish();
}
}
void EpubRenderBenchmarkActivity::render(RenderLock&&) {
renderer.clearScreen();
const Rect contentRect = UITheme::getContentRect(renderer, true, false);
const int titleY = contentRect.y + 12;
renderer.drawCenteredText(UI_12_FONT_ID, titleY, "Render Benchmark", true, EpdFontFamily::BOLD);
const int lineHeight = renderer.getLineHeight(UI_10_FONT_ID);
const int textTop = titleY + renderer.getLineHeight(UI_12_FONT_ID) + 14;
const int textWidth = contentRect.width - 20;
const auto lines = renderer.wrappedText(UI_10_FONT_ID, report.c_str(), textWidth, 48);
int y = textTop;
for (const auto& line : lines) {
if (y + lineHeight > contentRect.y + contentRect.height - 30) {
break;
}
renderer.drawText(UI_10_FONT_ID, contentRect.x + 10, y, line.c_str(), true);
y += lineHeight;
}
renderer.drawCenteredText(UI_10_FONT_ID, contentRect.y + contentRect.height - 18, "Press any button to close");
renderer.displayBuffer();
}
@@ -0,0 +1,18 @@
#pragma once
#include <string>
#include "activities/Activity.h"
class EpubRenderBenchmarkActivity final : public Activity {
public:
explicit EpubRenderBenchmarkActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string report)
: Activity("EpubRenderBenchmark", renderer, mappedInput), report(std::move(report)) {}
void onEnter() override;
void loop() override;
void render(RenderLock&&) override;
private:
std::string report;
};