From 1ae53ca20b94165a26e8cd23c347cb6a83b60df1 Mon Sep 17 00:00:00 2001 From: Dave Allie Date: Wed, 17 Dec 2025 01:24:52 +1100 Subject: [PATCH] Use 6x8kB chunks instead of 1x48kB chunk for secondary display buffer --- lib/GfxRenderer/GfxRenderer.cpp | 92 +++++++++++++++++++++++++++++---- lib/GfxRenderer/GfxRenderer.h | 7 ++- 2 files changed, 88 insertions(+), 11 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index dc7c781..180ad74 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -180,33 +180,105 @@ void GfxRenderer::copyGrayscaleMsbBuffers() const { einkDisplay.copyGrayscaleMsb void GfxRenderer::displayGrayBuffer() const { einkDisplay.displayGrayBuffer(); } +void GfxRenderer::freeBwBufferChunks() { + for (auto& bwBufferChunk : bwBufferChunks) { + if (bwBufferChunk) { + free(bwBufferChunk); + bwBufferChunk = nullptr; + } + } +} + /** * This should be called before grayscale buffers are populated. * A `restoreBwBuffer` call should always follow the grayscale render if this method was called. + * Uses chunked allocation to avoid needing 48KB of contiguous memory. */ void GfxRenderer::storeBwBuffer() { - if (bwBuffer) { - Serial.printf("[%lu] [GFX] !! BW buffer already stored - this is likely a bug, freeing it\n", millis()); - free(bwBuffer); + const uint8_t* frameBuffer = einkDisplay.getFrameBuffer(); + + // Allocate and copy each chunk + for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) { + // Check if any chunks are already allocated + if (bwBufferChunks[i]) { + Serial.printf("[%lu] [GFX] !! BW buffer chunk %zu already stored - this is likely a bug, freeing chunk\n", + millis(), i); + free(bwBufferChunks[i]); + bwBufferChunks[i] = nullptr; + } + + const size_t offset = i * BW_BUFFER_CHUNK_SIZE; + bwBufferChunks[i] = static_cast(malloc(BW_BUFFER_CHUNK_SIZE)); + + if (!bwBufferChunks[i]) { + Serial.printf("[%lu] [GFX] !! Failed to allocate BW buffer chunk %zu (%zu bytes)\n", millis(), i, + BW_BUFFER_CHUNK_SIZE); + // Free previously allocated chunks + freeBwBufferChunks(); + return; + } + + memcpy(bwBufferChunks[i], frameBuffer + offset, BW_BUFFER_CHUNK_SIZE); } - bwBuffer = static_cast(malloc(EInkDisplay::BUFFER_SIZE)); - memcpy(bwBuffer, einkDisplay.getFrameBuffer(), EInkDisplay::BUFFER_SIZE); + Serial.printf("[%lu] [GFX] Stored BW buffer in %zu chunks (%zu bytes each)\n", millis(), BW_BUFFER_NUM_CHUNKS, + BW_BUFFER_CHUNK_SIZE); } /** * This can only be called if `storeBwBuffer` was called prior to the grayscale render. * It should be called to restore the BW buffer state after grayscale rendering is complete. + * Uses chunked restoration to match chunked storage. */ void GfxRenderer::restoreBwBuffer() { - if (!bwBuffer) { - Serial.printf("[%lu] [GFX] !! BW buffer not stored - this is likely a bug\n", millis()); + // Check if any all chunks are allocated + bool missingChunks = false; + for (const auto& bwBufferChunk : bwBufferChunks) { + if (!bwBufferChunk) { + missingChunks = true; + break; + } + } + + if (missingChunks) { + freeBwBufferChunks(); return; } - einkDisplay.cleanupGrayscaleBuffers(bwBuffer); - free(bwBuffer); - bwBuffer = nullptr; + uint8_t* frameBuffer = einkDisplay.getFrameBuffer(); + for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) { + // Check if chunk is missing + if (!bwBufferChunks[i]) { + Serial.printf("[%lu] [GFX] !! BW buffer chunks not stored - this is likely a bug\n", millis()); + freeBwBufferChunks(); + return; + } + + const size_t offset = i * BW_BUFFER_CHUNK_SIZE; + // bwBufferChunks[i] = static_cast(malloc(chunkSize)); + // + // if (!bwBufferChunks[i]) { + // Serial.printf("[%lu] [GFX] !! Failed to allocate BW buffer chunk %zu (%zu bytes)\n", millis(), i, chunkSize); + // // Free previously allocated chunks + // freeBwBufferChunks(); + // return; + // } + + memcpy(frameBuffer + offset, bwBufferChunks[i], BW_BUFFER_CHUNK_SIZE); + } + + // // Create array of chunk pointers for the SDK call + // const uint8_t* chunkPointers[BW_BUFFER_NUM_CHUNKS]; + // for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) { + // chunkPointers[i] = bwBufferChunks[i]; + // } + + // einkDisplay.cleanupGrayscaleBuffersChunked(chunkPointers, BW_BUFFER_CHUNK_SIZE, BW_BUFFER_NUM_CHUNKS); + + einkDisplay.cleanupGrayscaleBuffers(frameBuffer); + + freeBwBufferChunks(); + Serial.printf("[%lu] [GFX] Restored and freed BW buffer chunks\n", millis()); } void GfxRenderer::renderChar(const EpdFontFamily& fontFamily, const uint32_t cp, int* x, const int* y, diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index cb8b588..d84303d 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -10,12 +10,17 @@ class GfxRenderer { enum RenderMode { BW, GRAYSCALE_LSB, GRAYSCALE_MSB }; private: + static constexpr size_t BW_BUFFER_CHUNK_SIZE = 8000; // 8KB chunks to allow for non-contiguous memory + static constexpr size_t BW_BUFFER_NUM_CHUNKS = + (EInkDisplay::BUFFER_SIZE + BW_BUFFER_CHUNK_SIZE - 1) / BW_BUFFER_CHUNK_SIZE; + EInkDisplay& einkDisplay; RenderMode renderMode; - uint8_t* bwBuffer = nullptr; + uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr}; std::map fontMap; void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, const int* y, bool pixelState, EpdFontStyle style) const; + void freeBwBufferChunks(); public: explicit GfxRenderer(EInkDisplay& einkDisplay) : einkDisplay(einkDisplay), renderMode(BW) {}