Faster refresh

This commit is contained in:
HarukiToreda committed 2026-10-16 01:25:28 -04:00
1 parent b3f408de95
commit acc5afd75b
5 files changed
+169 -44

No files matched your search

+98 -32
View File
@@ -210,6 +210,7 @@ class SafeFastEPD : public FASTEPD
if (!grayPulsePlanes)
return false;
memset(grayPulsePlanes, 0, planeBytes);
memset(grayActiveRows, 0, sizeof(grayActiveRows));
if (!baselineValid)
memset(_state.pCurrent, 0xFF, 960U * 540U / 2U);
changed = 0;
@@ -227,6 +228,10 @@ class SafeFastEPD : public FASTEPD
const uint8_t *next = image + y * (width / 2U);
memcpy(oldRow, current, rowBytes);
memcpy(newRow, next, rowBytes);
#if !INKHUD_BOOKREADER_PERF_LOG
if (baselineValid && memcmp(oldRow, newRow, rowBytes) == 0)
continue;
#endif
uint8_t *mask = grayPulsePlanes + (top + y) * 240U;
uint8_t pendingMask = 0;
bool rowChanged = false;
@@ -261,8 +266,10 @@ class SafeFastEPD : public FASTEPD
pendingMask = 0;
}
}
if (rowChanged)
if (rowChanged) {
grayActiveRows[top + y] = true;
memcpy(current, newRow, rowBytes);
}
}
#if INKHUD_BOOKREADER_PERF_LOG
for (unsigned packed = 0; packed < 256U; ++packed) {
@@ -331,6 +338,7 @@ class SafeFastEPD : public FASTEPD
uint8_t *grayPulsePlanes = nullptr;
uint8_t grayPairLookup[8][256] = {};
bool grayLookupReady = false;
bool grayActiveRows[540] = {};
#endif
bool dmaFault = false;
@@ -390,12 +398,17 @@ class SafeFastEPD : public FASTEPD
for (unsigned row = 0; row < 540; ++row) {
// Alternate buffers keep the active DMA row untouched.
uint8_t *out = _state.dma_buf + offset;
if (grayLookup) {
#if INKHUD_BOOKREADER_GRAYSCALE
if (grayLookup && grayActiveRows[row]) {
const uint8_t *source = _state.pCurrent + row * 480U;
const uint8_t *mask = plane + row * 240U;
for (unsigned x = 0; x < 240; ++x)
out[x] = mask[x] ? ((grayLookup[source[x * 2U]] << 4U) | grayLookup[source[x * 2U + 1U]]) & mask[x] : 0U;
} else if (plane)
} else if (grayLookup)
memset(out, 0, 240);
else
#endif
if (plane)
memcpy(out, plane + row * 240U, 240);
else
memset(out, 0, 240);
@@ -463,14 +476,71 @@ void ED047TC1::begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t pin_
if (failed)
LOG_WARN("ED047TC1 initial fullUpdate failed rc=%d", fullRc);
LOG_INFO("ED047TC1 panel profile: clock=20MHz partialPasses=4 fullPasses=5");
TaskHandle_t task = nullptr;
// Keep panel timing off the input/rendering core on dual-core boards.
const BaseType_t refreshCore = portNUM_PROCESSORS > 1 ? (xPortGetCoreID() ^ 1) : tskNO_AFFINITY;
if (xTaskCreatePinnedToCore(runUpdates, "eink-refresh", 8192, this, 2, &task, refreshCore) == pdPASS) {
updateTask = task;
LOG_INFO("ED047TC1 background refresh enabled core=%d", static_cast<int>(refreshCore));
} else
LOG_WARN("ED047TC1 refresh task unavailable; using blocking updates");
}
void ED047TC1::runUpdates(void *context)
{
auto *display = static_cast<ED047TC1 *>(context);
for (;;) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
display->updateResult = display->driveUpdate();
display->updateComplete.store(true, std::memory_order_release);
concurrency::mainDelay.interrupt();
}
}
void ED047TC1::queueUpdate(UpdateTypes type, bool grayscale, bool leavingGrayscale)
{
pendingType = !grayscale && !monochromeBaselineValid ? FULL : type;
pendingGrayscale = grayscale;
pendingLeavingGrayscale = leavingGrayscale;
monochromeBaselineValid = false;
#if INKHUD_BOOKREADER_GRAYSCALE
grayscaleBaselineValid = false;
#endif
updateStartedMs = Time::getMillis();
failed = false;
updateComplete.store(false, std::memory_order_relaxed);
beginPolling(5, 0);
if (updateTask)
xTaskNotifyGive(static_cast<TaskHandle_t>(updateTask));
else {
updateResult = driveUpdate();
updateComplete.store(true, std::memory_order_release);
}
}
bool ED047TC1::isUpdateDone()
{
if (!updateComplete.load(std::memory_order_acquire))
return false;
monochromeBaselineValid = !pendingGrayscale && updateResult == BBEP_SUCCESS;
#if INKHUD_BOOKREADER_GRAYSCALE
grayscaleBaselineValid = pendingGrayscale && updateResult == BBEP_SUCCESS;
#endif
if (static_cast<SafeFastEPD *>(epaper)->hasDmaFault())
panelReady = false;
failed = updateResult != BBEP_SUCCESS;
LOG_DEBUG("[EInkPerf] refresh complete gray=%u type=%u ms=%lu rc=%d", pendingGrayscale, static_cast<unsigned>(pendingType),
static_cast<unsigned long>(Time::getMillis() - updateStartedMs), updateResult);
return !failed;
}
void ED047TC1::update(uint8_t *imageData, UpdateTypes type)
{
if (!panelReady || !imageData)
if (!panelReady || !imageData || busy() || !updateComplete.load(std::memory_order_acquire))
return;
bool leavingGrayscale = false;
#if INKHUD_BOOKREADER_GRAYSCALE
const bool leavingGrayscale = epaper->getMode() == BB_MODE_4BPP;
leavingGrayscale = epaper->getMode() == BB_MODE_4BPP;
grayscaleBaselineValid = false;
#endif
@@ -503,39 +573,46 @@ void ED047TC1::update(uint8_t *imageData, UpdateTypes type)
memcpy(dstRow, srcRow, srcRowBytes);
}
int rc;
if (type == FULL || !monochromeBaselineValid) {
if (type != FULL && monochromeBaselineValid && memcmp(cur, epaper->previousBuffer(), dstRowBytes * dstTotalRows) == 0) {
failed = false;
return;
}
queueUpdate(type, false, leavingGrayscale);
}
int ED047TC1::driveUpdate()
{
#if INKHUD_BOOKREADER_GRAYSCALE
rc = epaper->fullUpdate(leavingGrayscale ? CLEAR_WHITE : CLEAR_SLOW, false);
#else
rc = epaper->fullUpdate(CLEAR_SLOW, false);
if (pendingGrayscale) {
const int rc = static_cast<SafeFastEPD *>(epaper)->updatePanelGray();
LOG_DEBUG("[EInkPerf] grayscale drive ms=%lu rc=%d", static_cast<unsigned long>(Time::getMillis() - updateStartedMs), rc);
return rc;
}
#endif
} else {
if (pendingType == FULL)
return epaper->fullUpdate(pendingLeavingGrayscale ? CLEAR_WHITE : CLEAR_SLOW, false);
else {
const uint32_t startMs = millis();
uint32_t blackToWhite = 0, whiteToBlack = 0;
const uint8_t *cur = epaper->currentBuffer();
const uint8_t *previous = epaper->previousBuffer();
for (size_t i = 0; i < dstRowBytes * dstTotalRows; ++i) {
for (size_t i = 0; i < 960U * 540U / 8U; ++i) {
blackToWhite += __builtin_popcount(static_cast<unsigned>(cur[i] & ~previous[i]));
whiteToBlack += __builtin_popcount(static_cast<unsigned>(~cur[i] & previous[i]));
}
auto *safeEpaper = static_cast<SafeFastEPD *>(epaper);
rc = safeEpaper->partialUpdateWithWhiteFinish(blackToWhite);
if (safeEpaper->hasDmaFault())
panelReady = false;
const int rc = safeEpaper->partialUpdateWithWhiteFinish(blackToWhite);
LOG_DEBUG("[EInkPerf] FAST blackToWhite=%lu whiteToBlack=%lu whiteExtraPasses=%u ms=%lu rc=%d",
static_cast<unsigned long>(blackToWhite), static_cast<unsigned long>(whiteToBlack),
blackToWhite ? SafeFastEPD::whiteFinishPasses : 0U, static_cast<unsigned long>(millis() - startMs), rc);
return rc;
}
monochromeBaselineValid = rc == BBEP_SUCCESS;
failed = !monochromeBaselineValid;
if (failed)
LOG_WARN("ED047TC1 monochrome update failed rc=%d", rc);
}
#if INKHUD_BOOKREADER_GRAYSCALE
void ED047TC1::updateGrayscale(uint8_t *imageData)
{
if (!panelReady || !imageData)
if (!panelReady || !imageData || busy() || !updateComplete.load(std::memory_order_acquire))
return;
#if INKHUD_BOOKREADER_PERF_LOG
const uint32_t startMs = millis();
@@ -572,19 +649,8 @@ void ED047TC1::updateGrayscale(uint8_t *imageData)
LOG_INFO("[BookReaderPerf] gray panel BEGIN clear=none waveform=panel-native shades=16 baseline=%u "
"changed=%lu prepareMs=%lu",
!establishBaseline, static_cast<unsigned long>(changedPixels), static_cast<unsigned long>(millis() - startMs));
const uint32_t driveStartMs = millis();
#endif
const int rc = safeEpaper->updatePanelGray();
if (safeEpaper->hasDmaFault())
panelReady = false;
#if INKHUD_BOOKREADER_PERF_LOG
LOG_INFO("[BookReaderPerf] gray panel END rc=%d driveMs=%lu totalMs=%lu", rc,
static_cast<unsigned long>(millis() - driveStartMs), static_cast<unsigned long>(millis() - startMs));
#endif
failed = rc != BBEP_SUCCESS;
grayscaleBaselineValid = !failed;
if (failed)
LOG_WARN("ED047TC1 grayscale update failed rc=%d", rc);
queueUpdate(FAST, true);
}
#endif
+12 -1
View File
@@ -31,6 +31,7 @@
#include "configuration.h"
#include "./EInk.h"
#include <atomic>
// Forward declare to avoid pulling FastEPD into all translation units
class FASTEPD;
@@ -83,12 +84,22 @@ class ED047TC1 : public EInk
#endif
protected:
bool isUpdateDone() override { return true; } // FastEPD updates are blocking
bool isUpdateDone() override;
private:
FASTEPD *epaper = nullptr;
bool panelReady = false;
bool monochromeBaselineValid = false;
void *updateTask = nullptr;
std::atomic<bool> updateComplete{true};
UpdateTypes pendingType = FAST;
bool pendingGrayscale = false;
bool pendingLeavingGrayscale = false;
int updateResult = 0;
uint32_t updateStartedMs = 0;
static void runUpdates(void *context);
void queueUpdate(UpdateTypes type, bool grayscale, bool leavingGrayscale = false);
int driveUpdate();
#if INKHUD_BOOKREADER_GRAYSCALE
bool grayscaleBaselineValid = false;
#endif
@@ -1021,6 +1021,10 @@ void copyMonoBitmapInto(const std::vector<uint8_t> &src, uint16_t srcWidth, uint
return;
}
}
#if INKHUD_BOOKREADER_GRAYSCALE
InkHUD::copyReaderGrayBitmap(src.data(), InkHUD::readerBitmapStride(srcWidth), srcWidth, srcHeight, dst.data(),
InkHUD::readerBitmapStride(dstWidth), dstWidth, dstHeight, dstLeft, dstTop);
#else
for (uint16_t sy = 0; sy < srcHeight; sy++) {
const int32_t dy = static_cast<int32_t>(dstTop) + sy;
if (dy < 0 || dy >= dstHeight) {
@@ -1031,16 +1035,12 @@ void copyMonoBitmapInto(const std::vector<uint8_t> &src, uint16_t srcWidth, uint
if (dx < 0 || dx >= dstWidth) {
continue;
}
#if INKHUD_BOOKREADER_GRAYSCALE
InkHUD::setReaderGray(dst.data(), InkHUD::readerBitmapStride(dstWidth), dx, dy,
InkHUD::readerGrayAt(src.data(), InkHUD::readerBitmapStride(srcWidth), sx, sy));
#else
if (monoBitmapBlackAt(src, srcWidth, sx, sy)) {
setMonoBitmapBlack(dst, dstWidth, static_cast<uint16_t>(dx), static_cast<uint16_t>(dy), true);
}
#endif
}
}
#endif
}
size_t renderedImageCacheBytes()
@@ -4476,7 +4476,7 @@ int32_t InkHUD::BookReaderApplet::runOnce()
return concurrency::OSThread::disable();
}
if (imageInteractionPending) {
if (readerInputPending() || !Throttle::hasElapsed(lastReaderInputMs, 150U))
if (readerInputPending() || !Throttle::hasElapsed(lastReaderInputMs, 25U))
return 25;
imageInteractionPending = false;
BR_PERF_LOG("image interaction settled zoom=%u pan=%d,%d", imageZoomPermille, imagePanX, imagePanY);
@@ -6409,7 +6409,7 @@ void InkHUD::BookReaderApplet::queueImageInteractionUpdate(bool panOnly)
imageInteractionPending = true;
lastReaderInputMs = Time::skipZero(Time::getMillis());
concurrency::OSThread::enabled = true;
setIntervalFromNow(150);
setIntervalFromNow(25);
}
int32_t InkHUD::BookReaderApplet::estimateImageLineSpan(const std::string &imagePath, uint16_t contentWidth,
@@ -6464,6 +6464,7 @@ bool InkHUD::BookReaderApplet::drawInlineImage(const std::string &imagePath, int
BR_LOAD_SCOPE("image-load");
[[maybe_unused]] uint32_t probeMs = 0;
[[maybe_unused]] uint32_t decodeMs = 0;
uint32_t scaleMs = 0;
if (imagePath.empty() || maxWidth == 0U || maxHeight == 0U) {
return false;
}
@@ -6478,7 +6479,8 @@ bool InkHUD::BookReaderApplet::drawInlineImage(const std::string &imagePath, int
const std::string cacheKey = imageCacheKey(imagePath, maxWidth, maxHeight, zoomPermille, inlineRaster);
if (!preloadOnly && useRenderedCache && !monoBitmap &&
drawRenderedImageCache(this, cacheKey, left, top, maxWidth, maxHeight, zoomPermille, panX, panY, clipBottom)) {
BR_PERF_LOG("image rendered-cache HIT path=%s", imagePath.c_str());
LOG_DEBUG("[BookReaderPerf] cached image zoom=%u ms=%lu", zoomPermille,
static_cast<unsigned long>(millis() - tImageStartMs));
noteBookReaderLoadActivity(true);
return true;
}
@@ -6790,8 +6792,10 @@ bool InkHUD::BookReaderApplet::drawInlineImage(const std::string &imagePath, int
gDecodedImage.ready = true;
if (!preloadOnly) {
BR_LOAD_SCOPE("image-source-scale");
const uint32_t scaleStartMs = millis();
rendered = scaleSourceBitmap(gDecodedImage.bitmap, gDecodedImage.width, gDecodedImage.height, drawWidth, drawHeight,
*monoBitmap);
scaleMs = millis() - scaleStartMs;
}
}
if (!rendered) {
@@ -6819,6 +6823,12 @@ bool InkHUD::BookReaderApplet::drawInlineImage(const std::string &imagePath, int
static_cast<unsigned>(drawHeight), static_cast<unsigned>(cache ? cache->bitmap.size() : 0U));
drawRenderedImageCache(this, cacheKey, left, top, maxWidth, maxHeight, requestedZoom, panX, panY, clipBottom);
}
if (!preloadOnly)
LOG_DEBUG(
"[BookReaderPerf] image zoom=%u sourceHit=%u src=%ux%u dst=%ux%u probe=%lums decode=%lums scale=%lums total=%lums",
requestedZoom, reuseSource, gDecodedImage.width, gDecodedImage.height, drawWidth, drawHeight,
static_cast<unsigned long>(probeMs), static_cast<unsigned long>(decodeMs), static_cast<unsigned long>(scaleMs),
static_cast<unsigned long>(millis() - tImageStartMs));
noteBookReaderLoadActivity(true);
return rendered;
#endif
@@ -4,6 +4,7 @@
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <vector>
#if INKHUD_BOOKREADER_GRAYSCALE && defined(ARCH_ESP32) && defined(__GNUC__)
@@ -70,6 +71,41 @@ inline void setReaderGray(uint8_t *data, size_t stride, uint16_t x, uint16_t y,
const unsigned shift = (x & 1U) ? 0U : 4U;
byte = (byte & ~(15U << shift)) | ((ink & 15U) << shift);
}
inline void copyReaderGrayBitmap(const uint8_t *source, size_t sourceStride, uint16_t sourceWidth, uint16_t sourceHeight,
uint8_t *destination, size_t destinationStride, uint16_t destinationWidth,
uint16_t destinationHeight, int16_t left, int16_t top)
{
const int32_t firstX = std::max<int32_t>(0, -static_cast<int32_t>(left));
const int32_t firstY = std::max<int32_t>(0, -static_cast<int32_t>(top));
const int32_t endX = std::min<int32_t>(sourceWidth, static_cast<int32_t>(destinationWidth) - left);
const int32_t endY = std::min<int32_t>(sourceHeight, static_cast<int32_t>(destinationHeight) - top);
if (firstX >= endX || firstY >= endY)
return;
for (int32_t y = firstY; y < endY; ++y) {
const uint8_t *row = source + y * sourceStride;
uint8_t *out = destination + (top + y) * destinationStride + (left + firstX) / 2U;
int32_t x = firstX;
if ((left + x) & 1U) {
*out = (*out & 0xF0U) | readerGrayAt(row, sourceStride, x, 0);
++out;
++x;
}
const int32_t pairs = (endX - x) / 2;
const uint8_t *in = row + x / 2U;
if (!(x & 1U)) {
std::memcpy(out, in, pairs);
out += pairs;
} else {
// Shift adjacent bytes when source and destination nibble alignment differs.
for (int32_t i = 0; i < pairs; ++i)
*out++ = static_cast<uint8_t>((in[i] << 4U) | (in[i + 1] >> 4U));
}
x += pairs * 2;
if (x < endX)
*out = (*out & 0x0FU) | (readerGrayAt(row, sourceStride, x, 0) << 4U);
}
}
#endif
inline bool rasterizeInlineRgb565Block(const uint16_t *pixels, int x, int y, int width, int height, int pixelStride, uint16_t sw,
@@ -197,8 +233,9 @@ inline bool scaleSourceBitmap(const std::vector<uint8_t> &source, uint16_t sw, u
return true;
}
const size_t stride = readerBitmapStride(dw);
destination.assign(stride * dh, 0);
#if INKHUD_BOOKREADER_GRAYSCALE
// Every output byte is assigned below, including an odd-width row's padding nibble.
destination.resize(stride * dh);
std::vector<uint16_t> columns(static_cast<size_t>(dw) + 1U);
for (uint32_t x = 0; x <= dw; ++x)
columns[x] = x * sw / dw;
@@ -315,6 +352,7 @@ inline bool scaleSourceBitmap(const std::vector<uint8_t> &source, uint16_t sw, u
}
return true;
#else
destination.assign(stride * dh, 0);
if (dw < sw || dh < sh) {
// Preserve source-pixel coverage when reducing fine lines and screentones.
constexpr uint8_t bayer[] = {0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5};
+2 -2
View File
@@ -537,7 +537,7 @@ int32_t InkHUD::Renderer::runOnce()
#else
if (requested)
#endif
return 250UL;
return 25UL;
else
return OSThread::disable();
}
@@ -734,7 +734,7 @@ void InkHUD::Renderer::render(bool async)
maintainRenderActivity();
if (keepAwakeUntilUpdateComplete) {
OSThread::enabled = true;
OSThread::setIntervalFromNow(250UL);
OSThread::setIntervalFromNow(25UL);
}
#endif
}