Prerender the next page
This commit is contained in:
@@ -1516,12 +1516,25 @@ bool EpubReaderActivity::stepPageState(const bool isForwardTurn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderActivity::pageTurn(bool isForwardTurn) {
|
void EpubReaderActivity::pageTurn(bool isForwardTurn) {
|
||||||
|
if (isForwardTurn && section && preRenderedPage.ready && preRenderedPage.spineIndex == currentSpineIndex &&
|
||||||
|
preRenderedPage.pageIndex == section->currentPage + 1) {
|
||||||
|
// Fast path: the frame buffer already holds the next page content. Advance state here on the
|
||||||
|
// loop task, then hand off to render() via usePreRenderedBuffer — all display work (status
|
||||||
|
// bar, flush, AA pass) stays on the render task where it belongs. No RenderLock acquired here.
|
||||||
|
section->currentPage = preRenderedPage.pageIndex;
|
||||||
|
preRenderedPage.ready = false;
|
||||||
|
usePreRenderedBuffer = true;
|
||||||
|
sessionPagesAdvanced++;
|
||||||
|
lastPageTurnTime = millis();
|
||||||
|
requestUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!stepPageState(isForwardTurn)) {
|
if (!stepPageState(isForwardTurn)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Track real progress within this session so auto-push-on-close can ignore brief
|
|
||||||
// book inspections. Counts both directions — the user is engaging with the book either way.
|
|
||||||
sessionPagesAdvanced++;
|
sessionPagesAdvanced++;
|
||||||
|
preRenderedPage.ready = false;
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1624,6 +1637,67 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
|||||||
lastRenderStats.largestFreeBlockBefore = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_DEFAULT);
|
lastRenderStats.largestFreeBlockBefore = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_DEFAULT);
|
||||||
showTruncatedSectionHintThisRender = false;
|
showTruncatedSectionHintThisRender = false;
|
||||||
|
|
||||||
|
// Capture and clear all pre-render flags before any state checks.
|
||||||
|
// - isPreRenderPass: render next page content only (no status bar, no flush).
|
||||||
|
// - isBufferDisplayPass: frame buffer already has content; just add status bar and flush.
|
||||||
|
// - anything else: normal full render; discard any stale pre-render state.
|
||||||
|
const bool isPreRenderPass = pendingPreRender;
|
||||||
|
const bool isBufferDisplayPass = usePreRenderedBuffer;
|
||||||
|
pendingPreRender = false;
|
||||||
|
usePreRenderedBuffer = false;
|
||||||
|
if (!isPreRenderPass && !isBufferDisplayPass) {
|
||||||
|
preRenderedPage.ready = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast-display pass: frame buffer holds pre-rendered content; superimpose live status bar,
|
||||||
|
// flush to display, and run the AA pass — all on the render task with no SD font re-read.
|
||||||
|
if (isBufferDisplayPass) {
|
||||||
|
if (section) {
|
||||||
|
auto p = section->loadPageFromSectionFile();
|
||||||
|
if (p && !p->hasImages()) {
|
||||||
|
currentPageFootnotes = std::move(p->footnotes);
|
||||||
|
displayPreRenderedPage(*p, orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
||||||
|
|
||||||
|
pendingProgressSave.spineIndex = currentSpineIndex;
|
||||||
|
pendingProgressSave.page = section->currentPage;
|
||||||
|
pendingProgressSave.pageCount = section->pageCount;
|
||||||
|
pendingProgressSave.pending.store(true, std::memory_order_release);
|
||||||
|
|
||||||
|
if (section->currentPage + 1 < section->pageCount) {
|
||||||
|
pendingPreRender = true;
|
||||||
|
requestUpdate();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Page load failed or was an image page — fall through to full render.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-render pass: render next page content into the frame buffer (no status bar, no flush).
|
||||||
|
if (isPreRenderPass) {
|
||||||
|
if (section && !preRenderedPage.ready) {
|
||||||
|
const int nextPage = section->currentPage + 1;
|
||||||
|
if (nextPage < section->pageCount) {
|
||||||
|
const uint32_t freeHeap = esp_get_free_heap_size();
|
||||||
|
const uint32_t contigHeap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_DEFAULT);
|
||||||
|
if (freeHeap >= SILENT_INDEX_MIN_FREE_HEAP_BYTES && contigHeap >= SILENT_INDEX_MIN_CONTIG_HEAP_BYTES) {
|
||||||
|
const int savedPage = section->currentPage;
|
||||||
|
section->currentPage = nextPage;
|
||||||
|
auto p = section->loadPageFromSectionFile();
|
||||||
|
section->currentPage = savedPage;
|
||||||
|
if (p && !p->hasImages()) {
|
||||||
|
section->currentPage = nextPage;
|
||||||
|
renderPageContentOnly(*p, orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
||||||
|
section->currentPage = savedPage;
|
||||||
|
preRenderedPage = {true, currentSpineIndex, nextPage};
|
||||||
|
LOG_DBG("ERS", "Pre-rendered page %d/%d", nextPage, section->pageCount - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!section) {
|
if (!section) {
|
||||||
if (currentSpineIndex < 0 || currentSpineIndex >= spineCount) {
|
if (currentSpineIndex < 0 || currentSpineIndex >= spineCount) {
|
||||||
LOG_ERR("ERS", "Render rejected invalid spine index %d (valid 0..%d)", currentSpineIndex, spineCount - 1);
|
LOG_ERR("ERS", "Render rejected invalid spine index %d (valid 0..%d)", currentSpineIndex, spineCount - 1);
|
||||||
@@ -1774,6 +1848,13 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
|||||||
pendingScreenshot = false;
|
pendingScreenshot = false;
|
||||||
ScreenshotUtil::takeScreenshot(renderer);
|
ScreenshotUtil::takeScreenshot(renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Schedule a pre-render of the next page into the frame buffer so the next forward
|
||||||
|
// page turn can skip the render pass and go straight to displayBuffer().
|
||||||
|
if (!preRenderedPage.ready && section->currentPage + 1 < section->pageCount) {
|
||||||
|
pendingPreRender = true;
|
||||||
|
requestUpdate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderActivity::silentIndexNextChapterIfNeeded(const uint16_t viewportWidth, const uint16_t viewportHeight) {
|
void EpubReaderActivity::silentIndexNextChapterIfNeeded(const uint16_t viewportWidth, const uint16_t viewportHeight) {
|
||||||
@@ -2055,6 +2136,77 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EpubReaderActivity::renderPageContentOnly(Page& page, const int orientedMarginTop, const int orientedMarginRight,
|
||||||
|
const int orientedMarginBottom, const int orientedMarginLeft) {
|
||||||
|
auto* fcm = renderer.getFontCacheManager();
|
||||||
|
fcm->resetStats();
|
||||||
|
|
||||||
|
const int viewportHeight = std::max(0, renderer.getScreenHeight() - orientedMarginTop - orientedMarginBottom);
|
||||||
|
const int contentTop = orientedMarginTop + getImageOnlyPageYOffset(page, viewportHeight);
|
||||||
|
|
||||||
|
auto scope = fcm->createPrewarmScope();
|
||||||
|
page.renderTextOnly(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop);
|
||||||
|
scope.endScanAndPrewarm();
|
||||||
|
|
||||||
|
renderer.clearScreen();
|
||||||
|
page.render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop);
|
||||||
|
// Status bar intentionally omitted — superimposed at display time with live values.
|
||||||
|
}
|
||||||
|
|
||||||
|
void EpubReaderActivity::displayPreRenderedPage(Page& page, const int orientedMarginTop, const int orientedMarginRight,
|
||||||
|
const int orientedMarginBottom, const int orientedMarginLeft) {
|
||||||
|
const int viewportHeight = std::max(0, renderer.getScreenHeight() - orientedMarginTop - orientedMarginBottom);
|
||||||
|
const int contentTop = orientedMarginTop + getImageOnlyPageYOffset(page, viewportHeight);
|
||||||
|
|
||||||
|
renderStatusBar();
|
||||||
|
|
||||||
|
// Pre-rendered pages are text-only (image pages are excluded from pre-rendering), so we
|
||||||
|
// always go through the normal refresh cycle — no imagePageWithAA or forceHalfRefresh paths.
|
||||||
|
const bool forceHalfRefreshThisPage = pendingHalfRefreshAfterImagePage && SETTINGS.halfRefreshAfterImagePage;
|
||||||
|
pendingHalfRefreshAfterImagePage = false;
|
||||||
|
if (forceHalfRefreshThisPage) {
|
||||||
|
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
|
||||||
|
pagesUntilFullRefresh = SETTINGS.getRefreshFrequency();
|
||||||
|
} else {
|
||||||
|
ReaderUtils::displayWithRefreshCycle(renderer, pagesUntilFullRefresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grayscale AA pass (same as normal render — BW snapshot, gray LSB+MSB, restore).
|
||||||
|
const bool aaConfigured = SETTINGS.textAntiAliasing && !antiAliasingSuspendedLowMemory;
|
||||||
|
if (aaConfigured) {
|
||||||
|
const uint32_t freeHeap = esp_get_free_heap_size();
|
||||||
|
const uint32_t contigHeap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_DEFAULT);
|
||||||
|
if (freeHeap >= BW_SNAPSHOT_MIN_FREE_HEAP_BYTES && contigHeap >= BW_SNAPSHOT_MIN_CONTIG_HEAP_BYTES) {
|
||||||
|
const int contentLeft = orientedMarginLeft;
|
||||||
|
const int contentRight = std::max(contentLeft, renderer.getScreenWidth() - orientedMarginRight);
|
||||||
|
const int contentBottom = std::max(contentTop, renderer.getScreenHeight() - orientedMarginBottom);
|
||||||
|
int bandTop = 0;
|
||||||
|
int bandBottom = std::max(0, contentBottom - contentTop);
|
||||||
|
if (!computePageDynamicYBand(page, renderer, getEffectiveReaderFontId(), bandBottom, &bandTop, &bandBottom)) {
|
||||||
|
bandTop = 0;
|
||||||
|
bandBottom = std::max(0, contentBottom - contentTop);
|
||||||
|
}
|
||||||
|
const int snapshotTop = contentTop + bandTop;
|
||||||
|
const int snapshotHeight = std::max(0, bandBottom - bandTop);
|
||||||
|
if (renderer.storeBwBufferRect(contentLeft, snapshotTop, contentRight - contentLeft, snapshotHeight)) {
|
||||||
|
renderer.clearScreen(0x00);
|
||||||
|
renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB);
|
||||||
|
page.renderTextOnly(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop);
|
||||||
|
renderer.copyGrayscaleLsbBuffers();
|
||||||
|
|
||||||
|
renderer.clearScreen(0x00);
|
||||||
|
renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB);
|
||||||
|
page.renderTextOnly(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop);
|
||||||
|
renderer.copyGrayscaleMsbBuffers();
|
||||||
|
|
||||||
|
renderer.displayGrayBuffer();
|
||||||
|
renderer.setRenderMode(GfxRenderer::BW);
|
||||||
|
renderer.restoreBwBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void EpubReaderActivity::renderStatusBar() const {
|
void EpubReaderActivity::renderStatusBar() const {
|
||||||
// Calculate progress in book
|
// Calculate progress in book
|
||||||
const int currentPage = section->currentPage + 1;
|
const int currentPage = section->currentPage + 1;
|
||||||
|
|||||||
@@ -189,6 +189,20 @@ class EpubReaderActivity final : public Activity {
|
|||||||
uint32_t maxFreeHeapAfter = 0;
|
uint32_t maxFreeHeapAfter = 0;
|
||||||
};
|
};
|
||||||
LastRenderStats lastRenderStats;
|
LastRenderStats lastRenderStats;
|
||||||
|
// Pre-rendered next page: frame buffer holds page content (no status bar) ready to display.
|
||||||
|
// Set by the pre-render pass in render(); consumed and cleared by the fast path in pageTurn().
|
||||||
|
// Invalidated (ready=false) on any navigation that is not a simple forward page turn.
|
||||||
|
struct PreRenderedPage {
|
||||||
|
bool ready = false;
|
||||||
|
int spineIndex = -1;
|
||||||
|
int pageIndex = -1;
|
||||||
|
};
|
||||||
|
PreRenderedPage preRenderedPage;
|
||||||
|
// Set by render() after a normal page render to request a pre-render of the next page.
|
||||||
|
bool pendingPreRender = false;
|
||||||
|
// Set by pageTurn() fast path to tell render() the frame buffer already holds the next page
|
||||||
|
// content and only the status bar + display flush are needed.
|
||||||
|
bool usePreRenderedBuffer = false;
|
||||||
// Progress save is posted by render() and consumed by loop() to keep SD I/O off the render task.
|
// Progress save is posted by render() and consumed by loop() to keep SD I/O off the render task.
|
||||||
// render() writes spineIndex/page/pageCount then sets pending with release semantics so loop()
|
// render() writes spineIndex/page/pageCount then sets pending with release semantics so loop()
|
||||||
// sees a coherent snapshot when it observes pending==true via acquire.
|
// sees a coherent snapshot when it observes pending==true via acquire.
|
||||||
@@ -232,6 +246,16 @@ class EpubReaderActivity final : public Activity {
|
|||||||
|
|
||||||
void renderContents(std::unique_ptr<Page> page, int orientedMarginTop, int orientedMarginRight,
|
void renderContents(std::unique_ptr<Page> page, int orientedMarginTop, int orientedMarginRight,
|
||||||
int orientedMarginBottom, int orientedMarginLeft);
|
int orientedMarginBottom, int orientedMarginLeft);
|
||||||
|
// Renders page content into the frame buffer (prewarm + BW pass) without drawing the status bar
|
||||||
|
// or flushing to the display. Used by the pre-render pass so the status bar can be superimposed
|
||||||
|
// at display time with live values (clock, battery).
|
||||||
|
void renderPageContentOnly(Page& page, int orientedMarginTop, int orientedMarginRight, int orientedMarginBottom,
|
||||||
|
int orientedMarginLeft);
|
||||||
|
// Draws the status bar over the current frame buffer and flushes to the display.
|
||||||
|
// Handles the refresh cycle and grayscale AA pass. page must be the same page
|
||||||
|
// that was last rendered into the buffer (needed for image AA re-render).
|
||||||
|
void displayPreRenderedPage(Page& page, int orientedMarginTop, int orientedMarginRight, int orientedMarginBottom,
|
||||||
|
int orientedMarginLeft);
|
||||||
void renderStatusBar() const;
|
void renderStatusBar() const;
|
||||||
void silentIndexNextChapterIfNeeded(uint16_t viewportWidth, uint16_t viewportHeight);
|
void silentIndexNextChapterIfNeeded(uint16_t viewportWidth, uint16_t viewportHeight);
|
||||||
void saveProgress(int spineIndex, int currentPage, int pageCount);
|
void saveProgress(int spineIndex, int currentPage, int pageCount);
|
||||||
|
|||||||
Reference in New Issue
Block a user