From cb8c2f75ff2692a4bc5da8c216c26dad60903167 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Fri, 22 May 2026 16:35:37 +0200 Subject: [PATCH] Add X3 fast LUT option --- lib/GfxRenderer/GfxRenderer.h | 5 +++++ lib/I18n/translations/english.yaml | 1 + lib/hal/HalDisplay.cpp | 4 ++++ lib/hal/HalDisplay.h | 6 ++++++ open-x4-sdk | 2 +- src/CrossPointSettings.h | 5 +++++ src/SettingsList.h | 8 ++++++++ src/activities/reader/EpubReaderActivity.cpp | 18 +++++++++++++++--- 8 files changed, 45 insertions(+), 4 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index e9b7bfd9..dad90cdd 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -230,6 +230,11 @@ class GfxRenderer { void writeGrayscalePlaneStrip(bool lsbPlane, const uint8_t* scratch, int yStart, int numRows) const; bool supportsStripGrayscale() const; + // X3-only: trade AA visual fidelity for ~2.2 s faster page-flip wall clock. + // No effect on X4 (its single grayscale LUT already runs at ~500 ms). + void setFastGrayscaleLut(bool fast) const { display.setFastGrayscaleLut(fast); } + bool getFastGrayscaleLut() const { return display.getFastGrayscaleLut(); } + // Tiled grayscale strip target. While active, drawPixel(), clearScreen(), // fillPhysicalHSpanByte() and renderGlyphFast2Bit() operate on `scratch` // (panelWidthBytes * stripRows bytes, holding physical rows diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index bd4b1aec..d5e99b9e 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -97,6 +97,7 @@ STR_SLEEP_COVER_MODE: "Sleep Screen Cover Mode" STR_HIDE_BATTERY: "Hide Battery %" STR_EXTRA_SPACING: "Extra Paragraph Spacing" STR_TEXT_AA: "Text Anti-Aliasing" +STR_FAST_AA: "Fast AA (X3 only)" STR_TEXT_DARKNESS: "Text Darkness" STR_EXTRA_DARK: "Extra Dark" STR_MAX_DARK: "Maximum" diff --git a/lib/hal/HalDisplay.cpp b/lib/hal/HalDisplay.cpp index 6fda0c0f..93560384 100644 --- a/lib/hal/HalDisplay.cpp +++ b/lib/hal/HalDisplay.cpp @@ -104,6 +104,10 @@ void HalDisplay::writeGrayscalePlaneStrip(bool lsbPlane, const uint8_t* rows, ui bool HalDisplay::supportsStripGrayscale() const { return einkDisplay.supportsStripGrayscale(); } +void HalDisplay::setFastGrayscaleLut(bool fast) { einkDisplay.setFastGrayscaleLut(fast); } + +bool HalDisplay::getFastGrayscaleLut() const { return einkDisplay.getFastGrayscaleLut(); } + uint16_t HalDisplay::getDisplayWidth() const { return einkDisplay.getDisplayWidth(); } uint16_t HalDisplay::getDisplayHeight() const { return einkDisplay.getDisplayHeight(); } diff --git a/lib/hal/HalDisplay.h b/lib/hal/HalDisplay.h index 695b0991..bf016189 100644 --- a/lib/hal/HalDisplay.h +++ b/lib/hal/HalDisplay.h @@ -61,6 +61,12 @@ class HalDisplay { void writeGrayscalePlaneStrip(bool lsbPlane, const uint8_t* rows, uint16_t yStart, uint16_t numRows); bool supportsStripGrayscale() const; + // X3-only knob: pick between the OEM 53-frame grayscale LUT (default, slow + // and accurate) and the 7-frame community LUT (fast, slightly darker + // mid-tones). No effect on X4. See EInkDisplay::setFastGrayscaleLut. + void setFastGrayscaleLut(bool fast); + bool getFastGrayscaleLut() const; + // Runtime geometry passthrough uint16_t getDisplayWidth() const; uint16_t getDisplayHeight() const; diff --git a/open-x4-sdk b/open-x4-sdk index 15050cb2..5f4a46ea 160000 --- a/open-x4-sdk +++ b/open-x4-sdk @@ -1 +1 @@ -Subproject commit 15050cb2a801b1fe6bad5212818307df1b834c5c +Subproject commit 5f4a46ea3a3adeca8dd29aeb8fc023a82afdffdd diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 2fce1e4c..5cd6deed 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -223,6 +223,11 @@ class CrossPointSettings { // Text rendering settings uint8_t extraParagraphSpacing = 1; uint8_t textAntiAliasing = 1; + // X3-only: when on, the AA refresh uses the 7-frame community grayscale LUT + // (~130 ms panel time) instead of the OEM 53-frame LUT (~2.4 s). Mid-tones + // run slightly darker than X4. Matches what papyrix-reader has shipped since + // 2025-11. Default off preserves OEM-fidelity grays. No effect on X4. + uint8_t fastAntiAliasing = 0; // Text darkness (0 = normal, 1 = dark, 2 = extra dark). Default 1 preserves // historical AA rendering (both grayscale shades drawn in the MSB pass). uint8_t textDarkness = DARKNESS_DARK; diff --git a/src/SettingsList.h b/src/SettingsList.h index 5fc16b06..6fd8bfce 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -111,6 +111,14 @@ inline const std::vector list = { SettingInfo::Toggle(StrId::STR_TEXT_AA, &CrossPointSettings::textAntiAliasing, "textAntiAliasing", StrId::STR_CAT_READER) .withSubmenu(StrId::STR_MENU_READER_FONT), + // X3-only fast AA LUT toggle. Swaps the 53-frame OEM grayscale waveform + // (~2.4 s panel time, X4-accurate grays) for the 7-frame community LUT + // (~130 ms, mid-tones slightly darker). See open-x4-sdk + // EInkDisplay::setFastGrayscaleLut for trade-offs. + SettingInfo::Toggle(StrId::STR_FAST_AA, &CrossPointSettings::fastAntiAliasing, "fastAntiAliasing", + StrId::STR_CAT_READER) + .withSubmenu(StrId::STR_MENU_READER_FONT) + .withDeviceTarget(SettingDeviceTarget::X3), SettingInfo::Enum(StrId::STR_TEXT_DARKNESS, &CrossPointSettings::textDarkness, {StrId::STR_NORMAL, StrId::STR_DARK, StrId::STR_EXTRA_DARK, StrId::STR_MAX_DARK}, "textDarkness", StrId::STR_CAT_READER) diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 61ab7e95..beed9a2c 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -174,9 +174,15 @@ bool computePageDynamicYBand(const Page& page, const GfxRenderer& renderer, cons // renderCharImpl culls out-of-band glyphs before bitmap decode so the cost // stays close to one render. Only renderTextOnly() is called here, matching the // legacy AA pass — images and HRs do not participate in grayscale. -bool runTiledGrayscalePass(GfxRenderer& renderer, Page& page, int fontId, int marginLeft, int contentTop) { +bool runTiledGrayscalePass(GfxRenderer& renderer, Page& page, int fontId, int marginLeft, int contentTop, bool fastAA) { if (!renderer.supportsStripGrayscale()) return false; + // Push the SETTINGS toggle into the SDK before the AA refresh. No-op on X4; + // on X3 picks between OEM _gc (slow/accurate) and community _grayscale + // (fast/darker mid-tones). Re-applied per render so a settings change takes + // effect on the next page flip without rebooting. + renderer.setFastGrayscaleLut(fastAA); + // Strip height trades scratch size for the number of re-renders. Each render // pays layout + glyph-cull overhead even when bitmap decode is skipped, so // fewer/bigger bands win as long as the scratch fits. 240 rows × ~100 bytes @@ -2157,7 +2163,8 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or if (aaEnabledForThisRender) { logReaderMemSnapshot("tiled_gray_begin"); const auto tTiledBegin = millis(); - grayscaleDone = runTiledGrayscalePass(renderer, *page, getEffectiveReaderFontId(), orientedMarginLeft, contentTop); + grayscaleDone = runTiledGrayscalePass(renderer, *page, getEffectiveReaderFontId(), orientedMarginLeft, contentTop, + SETTINGS.fastAntiAliasing); if (grayscaleDone) { tiledGrayMs = millis() - tTiledBegin; fcm->logStats("tiled_gray"); @@ -2219,6 +2226,9 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or // grayscale rendering // TODO: Only do this if font supports it if (aaEnabledForThisRender && bwBufferStored) { + // Push fast-AA toggle into the SDK before the AA refresh (X3 only; no-op + // on X4). Mirrors what runTiledGrayscalePass() does. + renderer.setFastGrayscaleLut(SETTINGS.fastAntiAliasing); logReaderMemSnapshot("gray_lsb_begin"); renderer.clearScreen(0x00); renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB); @@ -2341,7 +2351,8 @@ void EpubReaderActivity::displayPreRenderedPage(const Page& page, const int orie const bool aaConfigured = SETTINGS.textAntiAliasing && !antiAliasingSuspendedLowMemory; if (aaConfigured) { Page& pageRef = const_cast(page); - if (runTiledGrayscalePass(renderer, pageRef, getEffectiveReaderFontId(), orientedMarginLeft, contentTop)) { + if (runTiledGrayscalePass(renderer, pageRef, getEffectiveReaderFontId(), orientedMarginLeft, contentTop, + SETTINGS.fastAntiAliasing)) { return; } @@ -2360,6 +2371,7 @@ void EpubReaderActivity::displayPreRenderedPage(const Page& page, const int orie const int snapshotTop = contentTop + bandTop; const int snapshotHeight = std::max(0, bandBottom - bandTop); if (renderer.storeBwBufferRect(contentLeft, snapshotTop, contentRight - contentLeft, snapshotHeight)) { + renderer.setFastGrayscaleLut(SETTINGS.fastAntiAliasing); renderer.clearScreen(0x00); renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB); page.renderTextOnly(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop);