Tiled grayscale rendering to drop the storeBwBuffer peak largest contiguous free block (the value that actually drives OOM on the C3) from ~114 KB to ~82-90 KB. This renders each grayscale plane band-by-band into a small (~8 KB) scratch and streams each band straight to controller RAM (community-sdk writeGrayscalePlaneStrip), leaving the BW framebuffer intact. No save, no restore; controller RAM is re-synced for the next differential turn directly from the live framebuffer. Three writers honor the active band target so per-band re-rendering stays cheap and correct: - drawPixel (text) redirects writes to the band scratch and clips to it. - renderCharImpl skips glyphs whose physical y-extent is outside the band before the bitmap decode (glyphIntersectsStrip), so the per-band re-render doesn't pay N x glyph decode. - DirectPixelWriter (images) writes the band scratch via getWriteTarget instead of the framebuffer. Without this, image pixels wrote the live BW frame directly and cleanup re-synced that corruption, leaving thin outlines after navigating away from an image. Controller specifics live in the SDK (X4 setRamArea windowing, X3 PTL); the reader checks supportsStripGrayscale() and is otherwise controller-agnostic. Measured on hardware (X4 and X3, text and images, visually correct): - Grayscale scratch ~8 KB vs ~50 KB save; largest contiguous free block held at full size during grayscale instead of dropping ~25-32 KB. - X4 text page about +25 ms/page; X3 page time is dominated by its intrinsic grayscale refresh, not tiling. Depends on community-sdk #13 (the writeGrayscalePlaneStrip API). The submodule bump here points at that branch, so until #13 merges the submodule won't resolve from upstream and CI will fail there; keeping this a draft until then. Will rebase onto master and re-point the submodule to the merged SDK commit once #13 lands. Did you use AI tools to help write this code? partial
106 lines
3.9 KiB
C++
106 lines
3.9 KiB
C++
#include <HalDisplay.h>
|
|
#include <HalGPIO.h>
|
|
|
|
// Global HalDisplay instance
|
|
HalDisplay display;
|
|
|
|
#define SD_SPI_MISO 7
|
|
|
|
HalDisplay::HalDisplay() : einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY) {}
|
|
|
|
HalDisplay::~HalDisplay() {}
|
|
|
|
void HalDisplay::begin(bool seamless) {
|
|
// Set X3-specific panel mode before initializing.
|
|
if (gpio.deviceIsX3()) {
|
|
einkDisplay.setDisplayX3();
|
|
}
|
|
|
|
einkDisplay.begin();
|
|
|
|
if (seamless) {
|
|
// Defuse the SDK's X3 _x3InitialFullSyncsRemaining counter (no-op on X4)
|
|
// so the first paint isn't promoted to FULL (~770ms). Skips the wakeup-
|
|
// gated requestResync() below for the same reason.
|
|
einkDisplay.skipInitialResync();
|
|
return;
|
|
}
|
|
// Request resync after specific wakeup events to ensure clean display state.
|
|
const auto wakeupReason = gpio.getWakeupReason();
|
|
if (wakeupReason == HalGPIO::WakeupReason::PowerButton || wakeupReason == HalGPIO::WakeupReason::AfterFlash ||
|
|
wakeupReason == HalGPIO::WakeupReason::Other) {
|
|
einkDisplay.requestResync();
|
|
}
|
|
}
|
|
|
|
void HalDisplay::clearScreen(uint8_t color) const { einkDisplay.clearScreen(color); }
|
|
|
|
void HalDisplay::drawImage(const uint8_t* imageData, uint16_t x, uint16_t y, uint16_t w, uint16_t h,
|
|
bool fromProgmem) const {
|
|
einkDisplay.drawImage(imageData, x, y, w, h, fromProgmem);
|
|
}
|
|
|
|
void HalDisplay::drawImageTransparent(const uint8_t* imageData, uint16_t x, uint16_t y, uint16_t w, uint16_t h,
|
|
bool fromProgmem) const {
|
|
einkDisplay.drawImageTransparent(imageData, x, y, w, h, fromProgmem);
|
|
}
|
|
|
|
EInkDisplay::RefreshMode convertRefreshMode(HalDisplay::RefreshMode mode) {
|
|
switch (mode) {
|
|
case HalDisplay::FULL_REFRESH:
|
|
return EInkDisplay::FULL_REFRESH;
|
|
case HalDisplay::HALF_REFRESH:
|
|
return EInkDisplay::HALF_REFRESH;
|
|
case HalDisplay::FAST_REFRESH:
|
|
default:
|
|
return EInkDisplay::FAST_REFRESH;
|
|
}
|
|
}
|
|
|
|
void HalDisplay::displayBuffer(HalDisplay::RefreshMode mode, bool turnOffScreen) {
|
|
if (gpio.deviceIsX3() && mode == RefreshMode::HALF_REFRESH) {
|
|
einkDisplay.requestResync(1);
|
|
}
|
|
|
|
einkDisplay.displayBuffer(convertRefreshMode(mode), turnOffScreen);
|
|
}
|
|
|
|
void HalDisplay::refreshDisplay(HalDisplay::RefreshMode mode, bool turnOffScreen) {
|
|
if (gpio.deviceIsX3() && mode == RefreshMode::HALF_REFRESH) {
|
|
einkDisplay.requestResync(1);
|
|
}
|
|
|
|
einkDisplay.refreshDisplay(convertRefreshMode(mode), turnOffScreen);
|
|
}
|
|
|
|
void HalDisplay::deepSleep() { einkDisplay.deepSleep(); }
|
|
|
|
uint8_t* HalDisplay::getFrameBuffer() const { return einkDisplay.getFrameBuffer(); }
|
|
|
|
void HalDisplay::copyGrayscaleBuffers(const uint8_t* lsbBuffer, const uint8_t* msbBuffer) {
|
|
einkDisplay.copyGrayscaleBuffers(lsbBuffer, msbBuffer);
|
|
}
|
|
|
|
void HalDisplay::copyGrayscaleLsbBuffers(const uint8_t* lsbBuffer) { einkDisplay.copyGrayscaleLsbBuffers(lsbBuffer); }
|
|
|
|
void HalDisplay::copyGrayscaleMsbBuffers(const uint8_t* msbBuffer) { einkDisplay.copyGrayscaleMsbBuffers(msbBuffer); }
|
|
|
|
void HalDisplay::cleanupGrayscaleBuffers(const uint8_t* bwBuffer) { einkDisplay.cleanupGrayscaleBuffers(bwBuffer); }
|
|
|
|
void HalDisplay::displayGrayBuffer(bool turnOffScreen) { einkDisplay.displayGrayBuffer(turnOffScreen); }
|
|
|
|
void HalDisplay::writeGrayscalePlaneStrip(bool lsbPlane, const uint8_t* rows, uint16_t yStart, uint16_t numRows) {
|
|
einkDisplay.writeGrayscalePlaneStrip(lsbPlane ? EInkDisplay::GRAY_PLANE_LSB : EInkDisplay::GRAY_PLANE_MSB, rows,
|
|
yStart, numRows);
|
|
}
|
|
|
|
bool HalDisplay::supportsStripGrayscale() const { return einkDisplay.supportsStripGrayscale(); }
|
|
|
|
uint16_t HalDisplay::getDisplayWidth() const { return einkDisplay.getDisplayWidth(); }
|
|
|
|
uint16_t HalDisplay::getDisplayHeight() const { return einkDisplay.getDisplayHeight(); }
|
|
|
|
uint16_t HalDisplay::getDisplayWidthBytes() const { return einkDisplay.getDisplayWidthBytes(); }
|
|
|
|
uint32_t HalDisplay::getBufferSize() const { return einkDisplay.getBufferSize(); }
|