fix: Add framebuffer release/realloc and improved lazy indexing (#2563)

This commit is contained in:
Justin Mitchell
2026-07-12 13:16:48 -04:00
committed by GitHub
parent 859f6cb0d5
commit 444d87de82
24 changed files with 10398 additions and 147 deletions
+42
View File
@@ -1,6 +1,7 @@
#include "GfxRenderer.h"
#include <BidiUtils.h>
#include <BuildScratch.h>
#include <FontDecompressor.h>
#include <HalGPIO.h>
#include <Logging.h>
@@ -91,6 +92,47 @@ void GfxRenderer::begin() {
bwBufferChunks.assign((frameBufferSize + BW_BUFFER_CHUNK_SIZE - 1) / BW_BUFFER_CHUNK_SIZE, nullptr);
}
void GfxRenderer::releaseFrameBufferForBuild() {
// Lend the framebuffer's bytes IN PLACE: the allocation is never freed, so
// it cannot move and repeated loans cannot fragment the heap (the previous
// free+realloc model measurably decayed the max contiguous block over a
// session). The bytes are deposited in the build-scratch registry so
// memory-hungry build phases (e.g. InflateStream's tinfl state + window)
// can claim them instead of allocating.
uint32_t size = 0;
uint8_t* scratch = display.lendFrameBufferStorage(&size);
frameBuffer = nullptr;
if (scratch) {
buildscratch::lend(scratch, size);
}
}
bool GfxRenderer::restoreFrameBufferAfterBuild() {
buildscratch::reclaim();
display.returnFrameBufferStorage(); // cannot fail: the allocation was never freed
frameBuffer = display.getFrameBuffer();
return frameBuffer != nullptr;
}
GfxRenderer::FrameBufferLoan::FrameBufferLoan(GfxRenderer& renderer) : renderer_(renderer) {
// Nesting guard: if the framebuffer is already lent out (an outer loan),
// stay inert so this end() cannot return storage the outer loan still owns.
if (!renderer_.hasFrameBuffer()) return;
renderer_.releaseFrameBufferForBuild();
active_ = true;
}
void GfxRenderer::FrameBufferLoan::end() {
if (!active_) return;
active_ = false;
if (!renderer_.restoreFrameBufferAfterBuild()) {
// Only reachable if the framebuffer never existed, which begin() already
// asserts against; kept as a backstop since running blind helps nobody.
LOG_ERR("GFX", "Framebuffer restore failed - restarting");
ESP.restart();
}
}
bool GfxRenderer::isFontCacheScanning() const { return fontCacheManager_ && fontCacheManager_->isScanning(); }
void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) {
+28
View File
@@ -250,6 +250,34 @@ class GfxRenderer {
// Font helpers
const uint8_t* getGlyphBitmap(const EpdFontData* fontData, const EpdGlyph* glyph) const;
// Lend the 48 KB framebuffer's bytes to a memory-hungry phase (chapter
// builds) WITHOUT freeing the allocation, so it never moves and repeated
// loans cannot fragment the heap. Between release and restore NOTHING may
// draw or display — the panel keeps showing its last refreshed image. The
// lent bytes are published via buildscratch::claim() for consumers like
// InflateStream. restore returns the buffer white, so the caller must
// redraw the full screen; it cannot fail (no allocation involved).
void releaseFrameBufferForBuild();
bool restoreFrameBufferAfterBuild();
bool hasFrameBuffer() const { return frameBuffer != nullptr; }
// RAII form of the loan above, for blocking build regions with early-return
// error paths: restores on scope exit (or explicitly via end()). Display the
// popup/screen the panel should hold BEFORE constructing one. Constructing
// while the framebuffer is already lent yields an inert loan (nesting-safe).
class FrameBufferLoan {
public:
explicit FrameBufferLoan(GfxRenderer& renderer);
~FrameBufferLoan() { end(); }
void end();
FrameBufferLoan(const FrameBufferLoan&) = delete;
FrameBufferLoan& operator=(const FrameBufferLoan&) = delete;
private:
GfxRenderer& renderer_;
bool active_ = false;
};
// Low level functions
uint8_t* getFrameBuffer() const;
size_t getBufferSize() const;