From 98a5aa1f8969ccd317c9b45bf0fa84b6c82e167f Mon Sep 17 00:00:00 2001 From: Dave Allie Date: Wed, 17 Dec 2025 00:01:47 +1100 Subject: [PATCH] [EInkDisplay] Add basic implementation for displayWindow (#9) Adds a basic implementation of `displayWindow` which displays a subset of the current frame buffer to the screen. It is significantly less performant than a full screen reload, so it shouldn't be used unless the underlying content is hard to get/rerender. I've also spotted a little bit of ghosting, but it's not fully clear as to why that is. My only thought it the LUT and grayscale mode isn't fully disengaged or the RED RAM buffer isn't correctly setup (but afaict it should be populated correctly). Going to merge this to continue testing this feature out in the wild, but consider it experimental. --- .../display/EInkDisplay/include/EInkDisplay.h | 2 + libs/display/EInkDisplay/src/EInkDisplay.cpp | 76 +++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/libs/display/EInkDisplay/include/EInkDisplay.h b/libs/display/EInkDisplay/include/EInkDisplay.h index 16592af..10d832f 100644 --- a/libs/display/EInkDisplay/include/EInkDisplay.h +++ b/libs/display/EInkDisplay/include/EInkDisplay.h @@ -43,6 +43,8 @@ class EInkDisplay { #endif void displayBuffer(RefreshMode mode = FAST_REFRESH); + // EXPERIMENTAL: Windowed update - display only a rectangular region + void displayWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h); void displayGrayBuffer(bool turnOffScreen = false); void refreshDisplay(RefreshMode mode = FAST_REFRESH, bool turnOffScreen = false); diff --git a/libs/display/EInkDisplay/src/EInkDisplay.cpp b/libs/display/EInkDisplay/src/EInkDisplay.cpp index cc3a0b8..1f7e0ac 100644 --- a/libs/display/EInkDisplay/src/EInkDisplay.cpp +++ b/libs/display/EInkDisplay/src/EInkDisplay.cpp @@ -452,6 +452,82 @@ void EInkDisplay::displayBuffer(RefreshMode mode) { #endif } +// EXPERIMENTAL: Windowed update support +// Displays only a rectangular region of the frame buffer, preserving the rest of the screen. +// Requirements: x and w must be byte-aligned (multiples of 8 pixels) +void EInkDisplay::displayWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { + Serial.printf("[%lu] Displaying window at (%d,%d) size (%dx%d)\n", millis(), x, y, w, h); + + // Validate bounds + if (x + w > DISPLAY_WIDTH || y + h > DISPLAY_HEIGHT) { + Serial.printf("[%lu] ERROR: Window bounds exceed display dimensions!\n", millis()); + return; + } + + // Validate byte alignment + if (x % 8 != 0 || w % 8 != 0) { + Serial.printf("[%lu] ERROR: Window x and width must be byte-aligned (multiples of 8)!\n", millis()); + return; + } + + if (!frameBuffer) { + Serial.printf("[%lu] ERROR: Frame buffer not allocated!\n", millis()); + return; + } + + // displayWindow is not supported while the rest of the screen has grayscale content, revert it + if (inGrayscaleMode) { + inGrayscaleMode = false; + grayscaleRevert(); + } + + // Calculate window buffer size + const uint16_t windowWidthBytes = w / 8; + const uint32_t windowBufferSize = windowWidthBytes * h; + + Serial.printf("[%lu] Window buffer size: %lu bytes (%d x %d pixels)\n", millis(), windowBufferSize, w, h); + + // Allocate temporary buffer on stack + std::vector windowBuffer(windowBufferSize); + + // Extract window region from frame buffer + for (uint16_t row = 0; row < h; row++) { + const uint16_t srcY = y + row; + const uint16_t srcOffset = srcY * DISPLAY_WIDTH_BYTES + (x / 8); + const uint16_t dstOffset = row * windowWidthBytes; + memcpy(&windowBuffer[dstOffset], &frameBuffer[srcOffset], windowWidthBytes); + } + + // Configure RAM area for window + setRamArea(x, y, w, h); + + // Write to BW RAM (current frame) + writeRamBuffer(CMD_WRITE_RAM_BW, windowBuffer.data(), windowBufferSize); + +#ifndef EINK_DISPLAY_SINGLE_BUFFER_MODE + // Dual buffer: Extract window from frameBufferActive (previous frame) + std::vector previousWindowBuffer(windowBufferSize); + for (uint16_t row = 0; row < h; row++) { + const uint16_t srcY = y + row; + const uint16_t srcOffset = srcY * DISPLAY_WIDTH_BYTES + (x / 8); + const uint16_t dstOffset = row * windowWidthBytes; + memcpy(&previousWindowBuffer[dstOffset], &frameBufferActive[srcOffset], windowWidthBytes); + } + writeRamBuffer(CMD_WRITE_RAM_RED, previousWindowBuffer.data(), windowBufferSize); +#endif + + // Perform fast refresh + refreshDisplay(FAST_REFRESH); + +#ifdef EINK_DISPLAY_SINGLE_BUFFER_MODE + // Post-refresh: Sync RED RAM with current window (for next fast refresh) + setRamArea(x, y, w, h); + writeRamBuffer(CMD_WRITE_RAM_RED, windowBuffer.data(), windowBufferSize); +#endif + + Serial.printf("[%lu] Window display complete\n", millis()); +} + void EInkDisplay::displayGrayBuffer(const bool turnOffScreen) { drawGrayscale = false; inGrayscaleMode = true;