Merge pull request #54 from jpirnay/chore-debug-renderer

refactor: use atomic access to state fields
This commit is contained in:
jpirnay
2026-04-10 12:54:42 +02:00
committed by GitHub
2 changed files with 65 additions and 37 deletions
+44 -23
View File
@@ -797,7 +797,7 @@ void GfxRenderer::drawPixel(const int x, const int y, const bool state) const {
int phyY = 0; int phyY = 0;
// Note: this call should be inlined for better performance // Note: this call should be inlined for better performance
rotateCoordinates(orientation, x, y, &phyX, &phyY, panelWidth, panelHeight); rotateCoordinates(getOrientation(), x, y, &phyX, &phyY, panelWidth, panelHeight);
// Bounds checking against runtime panel dimensions // Bounds checking against runtime panel dimensions
if (phyX < 0 || phyX >= panelWidth || phyY < 0 || phyY >= panelHeight) { if (phyX < 0 || phyX >= panelWidth || phyY < 0 || phyY >= panelHeight) {
@@ -860,6 +860,7 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha
return; return;
} }
const auto& font = fontIt->second; const auto& font = fontIt->second;
const auto renderModeSnapshot = getRenderMode();
uint32_t cp; uint32_t cp;
uint32_t prevCp = 0; uint32_t prevCp = 0;
@@ -870,7 +871,7 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha
const int raiseBy = combiningMark::raiseAboveBase(combiningGlyph->top, combiningGlyph->height, lastBaseTop); const int raiseBy = combiningMark::raiseAboveBase(combiningGlyph->top, combiningGlyph->height, lastBaseTop);
const int combiningX = combiningMark::centerOver(lastBaseX, lastBaseLeft, lastBaseWidth, combiningGlyph->left, const int combiningX = combiningMark::centerOver(lastBaseX, lastBaseLeft, lastBaseWidth, combiningGlyph->left,
combiningGlyph->width); combiningGlyph->width);
renderCharImpl<TextRotation::None>(*this, renderMode, font, cp, combiningX, yPos - raiseBy, black, style); renderCharImpl<TextRotation::None>(*this, renderModeSnapshot, font, cp, combiningX, yPos - raiseBy, black, style);
continue; continue;
} }
@@ -902,7 +903,7 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha
lastBaseAdvanceFP = glyph->advanceX; lastBaseAdvanceFP = glyph->advanceX;
prevAdvanceFP = lastBaseAdvanceFP; prevAdvanceFP = lastBaseAdvanceFP;
renderCharImpl<TextRotation::None>(*this, renderMode, font, cp, lastBaseX, yPos, black, style); renderCharImpl<TextRotation::None>(*this, renderModeSnapshot, font, cp, lastBaseX, yPos, black, style);
prevCp = cp; prevCp = cp;
} }
} }
@@ -914,7 +915,7 @@ void GfxRenderer::drawLine(int x1, int y1, int x2, int y2, const bool state) con
std::swap(y1, y2); std::swap(y1, y2);
} }
// In Portrait/PortraitInverted a logical vertical line maps to a physical horizontal span. // In Portrait/PortraitInverted a logical vertical line maps to a physical horizontal span.
switch (orientation) { switch (getOrientation()) {
case Portrait: case Portrait:
fillPhysicalHSpan(HalDisplay::DISPLAY_HEIGHT - 1 - x1, y1, y2, state); fillPhysicalHSpan(HalDisplay::DISPLAY_HEIGHT - 1 - x1, y1, y2, state);
return; return;
@@ -930,7 +931,7 @@ void GfxRenderer::drawLine(int x1, int y1, int x2, int y2, const bool state) con
std::swap(x1, x2); std::swap(x1, x2);
} }
// In Landscape a logical horizontal line maps to a physical horizontal span. // In Landscape a logical horizontal line maps to a physical horizontal span.
switch (orientation) { switch (getOrientation()) {
case LandscapeCounterClockwise: case LandscapeCounterClockwise:
fillPhysicalHSpan(y1, x1, x2, state); fillPhysicalHSpan(y1, x1, x2, state);
return; return;
@@ -1144,7 +1145,7 @@ void GfxRenderer::fillRect(const int x, const int y, const int width, const int
// For each orientation, one logical dimension maps to a constant physical row, allowing the // For each orientation, one logical dimension maps to a constant physical row, allowing the
// perpendicular dimension to be written as a byte-level span — eliminating per-pixel overhead. // perpendicular dimension to be written as a byte-level span — eliminating per-pixel overhead.
switch (orientation) { switch (getOrientation()) {
case Portrait: case Portrait:
// Logical column x → physical row (479-x); logical y range → physical x span // Logical column x → physical row (479-x); logical y range → physical x span
for (int lx = x; lx < x + width; lx++) { for (int lx = x; lx < x + width; lx++) {
@@ -1212,7 +1213,7 @@ void GfxRenderer::fillRectDither(const int x, const int y, const int width, cons
// Byte patterns (phyY even / phyY odd): // Byte patterns (phyY even / phyY odd):
// Portrait / PortraitInverted: 0xAA / 0x55 // Portrait / PortraitInverted: 0xAA / 0x55
// LandscapeCW / LandscapeCCW: 0x55 / 0xAA // LandscapeCW / LandscapeCCW: 0x55 / 0xAA
switch (orientation) { switch (getOrientation()) {
case Portrait: case Portrait:
for (int lx = x; lx < x + width; lx++) { for (int lx = x; lx < x + width; lx++) {
const int phyY = HalDisplay::DISPLAY_HEIGHT - 1 - lx; const int phyY = HalDisplay::DISPLAY_HEIGHT - 1 - lx;
@@ -1251,7 +1252,7 @@ void GfxRenderer::fillRectDither(const int x, const int y, const int width, cons
// PortraitInverted: 0xAA / 0xFF (skip) // PortraitInverted: 0xAA / 0xFF (skip)
// LandscapeCCW: 0x55 / 0xFF (skip) // LandscapeCCW: 0x55 / 0xFF (skip)
// LandscapeCW: 0xFF (skip) / 0xAA // LandscapeCW: 0xFF (skip) / 0xAA
switch (orientation) { switch (getOrientation()) {
case Portrait: case Portrait:
for (int lx = x; lx < x + width; lx++) { for (int lx = x; lx < x + width; lx++) {
const int phyY = HalDisplay::DISPLAY_HEIGHT - 1 - lx; const int phyY = HalDisplay::DISPLAY_HEIGHT - 1 - lx;
@@ -1399,11 +1400,12 @@ void GfxRenderer::fillRoundedRect(const int x, const int y, const int width, con
} }
void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const { void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const {
const auto currentOrientation = getOrientation();
int rotatedX = 0; int rotatedX = 0;
int rotatedY = 0; int rotatedY = 0;
rotateCoordinates(orientation, x, y, &rotatedX, &rotatedY, panelWidth, panelHeight); rotateCoordinates(currentOrientation, x, y, &rotatedX, &rotatedY, panelWidth, panelHeight);
// Rotate origin corner // Rotate origin corner
switch (orientation) { switch (currentOrientation) {
case Portrait: case Portrait:
rotatedY = rotatedY - height; rotatedY = rotatedY - height;
break; break;
@@ -1476,6 +1478,7 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con
return; return;
} }
const auto renderModeSnapshot = getRenderMode();
for (int bmpY = 0; bmpY < (bitmap.getHeight() - cropPixY); bmpY++) { for (int bmpY = 0; bmpY < (bitmap.getHeight() - cropPixY); bmpY++) {
// The BMP's (0, 0) is the bottom-left corner (if the height is positive, top-left if negative). // The BMP's (0, 0) is the bottom-left corner (if the height is positive, top-left if negative).
// Screen's (0, 0) is the top-left corner. // Screen's (0, 0) is the top-left corner.
@@ -1519,11 +1522,11 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con
const uint8_t val = outputRow[bmpX / 4] >> (6 - ((bmpX * 2) % 8)) & 0x3; const uint8_t val = outputRow[bmpX / 4] >> (6 - ((bmpX * 2) % 8)) & 0x3;
if (renderMode == BW && val < 3) { if (renderModeSnapshot == BW && val < 3) {
drawPixel(screenX, screenY); drawPixel(screenX, screenY);
} else if (renderMode == GRAYSCALE_MSB && (val == 1 || val == 2)) { } else if (renderModeSnapshot == GRAYSCALE_MSB && (val == 1 || val == 2)) {
drawPixel(screenX, screenY, false); drawPixel(screenX, screenY, false);
} else if (renderMode == GRAYSCALE_LSB && val == 1) { } else if (renderModeSnapshot == GRAYSCALE_LSB && val == 1) {
drawPixel(screenX, screenY, false); drawPixel(screenX, screenY, false);
} }
} }
@@ -1684,17 +1687,35 @@ void GfxRenderer::invertScreen() const {
} }
} }
static constexpr unsigned int encodeRefreshMode(const HalDisplay::RefreshMode mode) {
return static_cast<unsigned int>(mode) + 1u;
}
static constexpr HalDisplay::RefreshMode decodeRefreshMode(const unsigned int value) {
return static_cast<HalDisplay::RefreshMode>(value - 1u);
}
void GfxRenderer::setNextDisplayRefreshMode(const HalDisplay::RefreshMode refreshMode) const { void GfxRenderer::setNextDisplayRefreshMode(const HalDisplay::RefreshMode refreshMode) const {
useNextRefreshOverride = true; refreshOverride.store(encodeRefreshMode(refreshMode), std::memory_order_release);
nextRefreshOverride = refreshMode;
} }
void GfxRenderer::displayBuffer(const HalDisplay::RefreshMode refreshMode) const { void GfxRenderer::displayBuffer(const HalDisplay::RefreshMode refreshMode) const {
const auto effectiveMode = useNextRefreshOverride ? nextRefreshOverride : refreshMode; auto effectiveMode = refreshMode;
useNextRefreshOverride = false; unsigned int overrideValue = refreshOverride.load(std::memory_order_acquire);
if (overrideValue != REFRESH_OVERRIDE_NONE) {
unsigned int expected = overrideValue;
if (refreshOverride.compare_exchange_strong(expected, REFRESH_OVERRIDE_NONE, std::memory_order_acq_rel,
std::memory_order_acquire)) {
effectiveMode = decodeRefreshMode(overrideValue);
} else if (expected != REFRESH_OVERRIDE_NONE) {
effectiveMode = decodeRefreshMode(expected);
refreshOverride.store(REFRESH_OVERRIDE_NONE, std::memory_order_release);
}
}
auto elapsed = millis() - start_ms; auto elapsed = millis() - start_ms;
LOG_DBG("GFX", "Time = %lu ms from clearScreen to displayBuffer", elapsed); LOG_DBG("GFX", "Time = %lu ms from clearScreen to displayBuffer", elapsed);
display.displayBuffer(effectiveMode, fadingFix); display.displayBuffer(effectiveMode, fadingFix.load(std::memory_order_relaxed));
} }
std::string GfxRenderer::truncatedText(const int fontId, const char* text, const int maxWidth, std::string GfxRenderer::truncatedText(const int fontId, const char* text, const int maxWidth,
@@ -1783,7 +1804,7 @@ std::vector<std::string> GfxRenderer::wrappedText(const int fontId, const char*
// Note: Internal driver treats screen in command orientation; this library exposes a logical orientation // Note: Internal driver treats screen in command orientation; this library exposes a logical orientation
int GfxRenderer::getScreenWidth() const { int GfxRenderer::getScreenWidth() const {
switch (orientation) { switch (getOrientation()) {
case Portrait: case Portrait:
case PortraitInverted: case PortraitInverted:
// 480px wide in portrait logical coordinates // 480px wide in portrait logical coordinates
@@ -1797,7 +1818,7 @@ int GfxRenderer::getScreenWidth() const {
} }
int GfxRenderer::getScreenHeight() const { int GfxRenderer::getScreenHeight() const {
switch (orientation) { switch (getOrientation()) {
case Portrait: case Portrait:
case PortraitInverted: case PortraitInverted:
// 800px tall in portrait logical coordinates // 800px tall in portrait logical coordinates
@@ -1943,7 +1964,7 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y
const int combiningX = x - raiseBy; const int combiningX = x - raiseBy;
const int combiningY = combiningMark::centerOverRotated90CW(lastBaseY, lastBaseLeft, lastBaseWidth, const int combiningY = combiningMark::centerOverRotated90CW(lastBaseY, lastBaseLeft, lastBaseWidth,
combiningGlyph->left, combiningGlyph->width); combiningGlyph->left, combiningGlyph->width);
renderCharImpl<TextRotation::Rotated90CW>(*this, renderMode, font, cp, combiningX, combiningY, black, style); renderCharImpl<TextRotation::Rotated90CW>(*this, getRenderMode(), font, cp, combiningX, combiningY, black, style);
continue; continue;
} }
@@ -1974,7 +1995,7 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y
lastBaseAdvanceFP = glyph->advanceX; lastBaseAdvanceFP = glyph->advanceX;
prevAdvanceFP = lastBaseAdvanceFP; prevAdvanceFP = lastBaseAdvanceFP;
renderCharImpl<TextRotation::Rotated90CW>(*this, renderMode, font, cp, x, lastBaseY, black, style); renderCharImpl<TextRotation::Rotated90CW>(*this, getRenderMode(), font, cp, x, lastBaseY, black, style);
prevCp = cp; prevCp = cp;
} }
} }
@@ -2083,7 +2104,7 @@ void GfxRenderer::cleanupGrayscaleWithFrameBuffer() const {
} }
void GfxRenderer::getOrientedViewableTRBL(int* outTop, int* outRight, int* outBottom, int* outLeft) const { void GfxRenderer::getOrientedViewableTRBL(int* outTop, int* outRight, int* outBottom, int* outLeft) const {
switch (orientation) { switch (getOrientation()) {
case Portrait: case Portrait:
*outTop = VIEWABLE_MARGIN_TOP; *outTop = VIEWABLE_MARGIN_TOP;
*outRight = VIEWABLE_MARGIN_RIGHT; *outRight = VIEWABLE_MARGIN_RIGHT;
+21 -14
View File
@@ -5,6 +5,7 @@
class FontCacheManager; class FontCacheManager;
#include <atomic>
#include <cstring> #include <cstring>
#include <map> #include <map>
#include <string> #include <string>
@@ -30,12 +31,14 @@ class GfxRenderer {
private: private:
static constexpr size_t BW_BUFFER_CHUNK_SIZE = 8000; // 8KB chunks to allow for non-contiguous memory static constexpr size_t BW_BUFFER_CHUNK_SIZE = 8000; // 8KB chunks to allow for non-contiguous memory
static constexpr unsigned int REFRESH_OVERRIDE_NONE = 0;
HalDisplay& display; HalDisplay& display;
RenderMode renderMode; std::atomic<int> renderMode;
Orientation orientation; std::atomic<int> orientation;
bool fadingFix; std::atomic<bool> fadingFix;
// Text darkness for 2-bit grayscale glyph rendering. // Text darkness for 2-bit grayscale glyph rendering.
std::atomic<uint8_t> textDarkness;
// 0 = Normal — true 4-level AA (raw=1 → light gray, raw=2 → dark gray) // 0 = Normal — true 4-level AA (raw=1 → light gray, raw=2 → dark gray)
// 1 = Dark — historical default; raw=2 collapses to black // 1 = Dark — historical default; raw=2 collapses to black
// 2 = Extra Dark — both AA shades go black in the grayscale plane // 2 = Extra Dark — both AA shades go black in the grayscale plane
@@ -46,7 +49,6 @@ class GfxRenderer {
// 1-bit fonts and the BW pass are unchanged. Default is 1 to preserve historical // 1-bit fonts and the BW pass are unchanged. Default is 1 to preserve historical
// rendering. See drawMaskFor2BitMode() in GfxRenderer.cpp for the per-level // rendering. See drawMaskFor2BitMode() in GfxRenderer.cpp for the per-level
// pixel breakdown and a worked example glyph. // pixel breakdown and a worked example glyph.
uint8_t textDarkness = 1;
uint8_t* frameBuffer = nullptr; uint8_t* frameBuffer = nullptr;
uint16_t panelWidth = HalDisplay::DISPLAY_WIDTH; uint16_t panelWidth = HalDisplay::DISPLAY_WIDTH;
uint16_t panelHeight = HalDisplay::DISPLAY_HEIGHT; uint16_t panelHeight = HalDisplay::DISPLAY_HEIGHT;
@@ -59,8 +61,7 @@ class GfxRenderer {
// recording to the (non-const) FontCacheManager. Same pragmatic compromise // recording to the (non-const) FontCacheManager. Same pragmatic compromise
// as before, concentrated in a single pointer instead of four fields. // as before, concentrated in a single pointer instead of four fields.
mutable FontCacheManager* fontCacheManager_ = nullptr; mutable FontCacheManager* fontCacheManager_ = nullptr;
mutable bool useNextRefreshOverride = false; mutable std::atomic<unsigned int> refreshOverride = REFRESH_OVERRIDE_NONE;
mutable HalDisplay::RefreshMode nextRefreshOverride = HalDisplay::FAST_REFRESH;
void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, int* y, bool pixelState, void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, int* y, bool pixelState,
EpdFontFamily::Style style) const; EpdFontFamily::Style style) const;
@@ -80,7 +81,11 @@ class GfxRenderer {
public: public:
explicit GfxRenderer(HalDisplay& halDisplay) explicit GfxRenderer(HalDisplay& halDisplay)
: display(halDisplay), renderMode(BW), orientation(Portrait), fadingFix(false) {} : display(halDisplay),
renderMode(static_cast<int>(BW)),
orientation(static_cast<int>(Portrait)),
fadingFix(false),
textDarkness(1) {}
~GfxRenderer() { freeBwBufferChunks(); } ~GfxRenderer() { freeBwBufferChunks(); }
static constexpr int VIEWABLE_MARGIN_TOP = 9; static constexpr int VIEWABLE_MARGIN_TOP = 9;
@@ -96,11 +101,11 @@ class GfxRenderer {
const std::map<int, EpdFontFamily>& getFontMap() const { return fontMap; } const std::map<int, EpdFontFamily>& getFontMap() const { return fontMap; }
// Orientation control (affects logical width/height and coordinate transforms) // Orientation control (affects logical width/height and coordinate transforms)
void setOrientation(const Orientation o) { orientation = o; } void setOrientation(const Orientation o) { orientation.store(static_cast<int>(o), std::memory_order_relaxed); }
Orientation getOrientation() const { return orientation; } Orientation getOrientation() const { return static_cast<Orientation>(orientation.load(std::memory_order_relaxed)); }
// Fading fix control // Fading fix control
void setFadingFix(const bool enabled) { fadingFix = enabled; } void setFadingFix(const bool enabled) { fadingFix.store(enabled, std::memory_order_relaxed); }
// Screen ops // Screen ops
int getScreenWidth() const; int getScreenWidth() const;
@@ -165,16 +170,18 @@ class GfxRenderer {
int getTextHeight(int fontId) const; int getTextHeight(int fontId) const;
// Grayscale functions // Grayscale functions
void setRenderMode(const RenderMode mode) { this->renderMode = mode; } void setRenderMode(const RenderMode mode) {
RenderMode getRenderMode() const { return renderMode; } this->renderMode.store(static_cast<int>(mode), std::memory_order_relaxed);
}
RenderMode getRenderMode() const { return static_cast<RenderMode>(renderMode.load(std::memory_order_relaxed)); }
// Text darkness control: // Text darkness control:
// 0 = Normal, 1 = Dark, 2 = Extra Dark, 3 = Maximum. // 0 = Normal, 1 = Dark, 2 = Extra Dark, 3 = Maximum.
// Only affects anti-aliased pixels in 2-bit (grayscale) glyph rendering; // Only affects anti-aliased pixels in 2-bit (grayscale) glyph rendering;
// 1-bit fonts and the BW pass are unchanged. See drawMaskFor2BitMode() in // 1-bit fonts and the BW pass are unchanged. See drawMaskFor2BitMode() in
// GfxRenderer.cpp for the per-level pixel breakdown and a worked example. // GfxRenderer.cpp for the per-level pixel breakdown and a worked example.
void setTextDarkness(const uint8_t d) { textDarkness = d; } void setTextDarkness(const uint8_t d) { textDarkness.store(d, std::memory_order_relaxed); }
uint8_t getTextDarkness() const { return textDarkness; } uint8_t getTextDarkness() const { return static_cast<uint8_t>(textDarkness.load(std::memory_order_relaxed)); }
void copyGrayscaleLsbBuffers() const; void copyGrayscaleLsbBuffers() const;
void copyGrayscaleMsbBuffers() const; void copyGrayscaleMsbBuffers() const;
void displayGrayBuffer() const; void displayGrayBuffer() const;