Add X3 fast LUT option

This commit is contained in:
jpirnay
2026-05-22 16:35:37 +02:00
parent 9f34adcf99
commit cb8c2f75ff
8 changed files with 45 additions and 4 deletions
+5
View File
@@ -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
+1
View File
@@ -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"
+4
View File
@@ -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(); }
+6
View File
@@ -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;
+5
View File
@@ -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;
+8
View File
@@ -111,6 +111,14 @@ inline const std::vector<SettingInfo> 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)
+15 -3
View File
@@ -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> 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> 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&>(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);