feat: optimize fillRectDither (#737)
## Summary
This PR optimizes the `fillRectDither` function, making it as fast as a
normal `fillRect`
Testing code:
```cpp
{
auto start_t = millis();
renderer.fillRectDither(0, 0, renderer.getScreenWidth(), renderer.getScreenHeight(), Color::LightGray);
auto elapsed = millis() - start_t;
Serial.printf("[%lu] [ ] Test fillRectDither drawn in %lu ms\n", millis(), elapsed);
}
{
auto start_t = millis();
renderer.fillRect(0, 0, renderer.getScreenWidth(), renderer.getScreenHeight(), true);
auto elapsed = millis() - start_t;
Serial.printf("[%lu] [ ] Test fillRect drawn in %lu ms\n", millis(), elapsed);
}
```
Before:
```
[1125] [ ] Test fillRectDither drawn in 327 ms
[1347] [ ] Test fillRect drawn in 222 ms
```
After:
```
[1065] [ ] Test fillRectDither drawn in 238 ms
[1287] [ ] Test fillRect drawn in 222 ms
```
## Visual validation
Before:
<img width="415" height="216" alt="Screenshot 2026-02-07 at 01 04 19"
src="https://github.com/user-attachments/assets/5802dbba-187b-4d2b-a359-1318d3932d38"
/>
After:
<img width="420" height="191" alt="Screenshot 2026-02-07 at 01 36 30"
src="https://github.com/user-attachments/assets/3c3c8e14-3f3a-4205-be78-6ed771dcddf4"
/>
## Details
The original version is quite slow because it does quite a lot of
computations. A single pixel needs around 20 instructions just to know
if it's black or white:
<img width="1170" height="693" alt="Screenshot 2026-02-07 at 00 15 54"
src="https://github.com/user-attachments/assets/7c5a55e7-0598-4340-8b7b-17307d7921cb"
/>
With the new, templated and more light-weight approach, each pixel takes
only 3-4 instructions, the modulo operator is translated into bitwise
ops:
<img width="1175" height="682" alt="Screenshot 2026-02-07 at 01 47 51"
src="https://github.com/user-attachments/assets/4ec2cf74-6cc0-4b5b-87d5-831563ef164f"
/>
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? **NO**
This commit is contained in:
committed by
Dave Allie
parent
75b0ed7781
commit
b45eaf7ded
@@ -237,54 +237,56 @@ void GfxRenderer::fillRect(const int x, const int y, const int width, const int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr uint8_t bayer4x4[4][4] = {
|
// NOTE: Those are in critical path, and need to be templated to avoid runtime checks for every pixel.
|
||||||
{0, 8, 2, 10},
|
// Any branching must be done outside the loops to avoid performance degradation.
|
||||||
{12, 4, 14, 6},
|
template <>
|
||||||
{3, 11, 1, 9},
|
void GfxRenderer::drawPixelDither<Color::Clear>(const int x, const int y) const {
|
||||||
{15, 7, 13, 5},
|
// Do nothing
|
||||||
};
|
}
|
||||||
static constexpr int matrixSize = 4;
|
|
||||||
static constexpr int matrixLevels = matrixSize * matrixSize;
|
template <>
|
||||||
|
void GfxRenderer::drawPixelDither<Color::Black>(const int x, const int y) const {
|
||||||
void GfxRenderer::drawPixelDither(const int x, const int y, Color color) const {
|
drawPixel(x, y, true);
|
||||||
if (color == Color::Clear) {
|
}
|
||||||
} else if (color == Color::Black) {
|
|
||||||
drawPixel(x, y, true);
|
template <>
|
||||||
} else if (color == Color::White) {
|
void GfxRenderer::drawPixelDither<Color::White>(const int x, const int y) const {
|
||||||
drawPixel(x, y, false);
|
drawPixel(x, y, false);
|
||||||
} else {
|
}
|
||||||
// Use dithering
|
|
||||||
const int greyLevel = static_cast<int>(color) - 1; // 0-15
|
template <>
|
||||||
const int normalizedGrey = (greyLevel * 255) / (matrixLevels - 1);
|
void GfxRenderer::drawPixelDither<Color::LightGray>(const int x, const int y) const {
|
||||||
const int clampedGrey = std::max(0, std::min(normalizedGrey, 255));
|
drawPixel(x, y, x % 2 == 0 && y % 2 == 0);
|
||||||
const int threshold = (clampedGrey * (matrixLevels + 1)) / 256;
|
}
|
||||||
|
|
||||||
const int matrixX = x & (matrixSize - 1);
|
template <>
|
||||||
const int matrixY = y & (matrixSize - 1);
|
void GfxRenderer::drawPixelDither<Color::DarkGray>(const int x, const int y) const {
|
||||||
const uint8_t patternValue = bayer4x4[matrixY][matrixX];
|
drawPixel(x, y, (x + y) % 2 == 0); // TODO: maybe find a better pattern?
|
||||||
const bool black = patternValue < threshold;
|
|
||||||
drawPixel(x, y, black);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use Bayer matrix 4x4 dithering to fill the rectangle with a grey level
|
|
||||||
void GfxRenderer::fillRectDither(const int x, const int y, const int width, const int height, Color color) const {
|
void GfxRenderer::fillRectDither(const int x, const int y, const int width, const int height, Color color) const {
|
||||||
if (color == Color::Clear) {
|
if (color == Color::Clear) {
|
||||||
} else if (color == Color::Black) {
|
} else if (color == Color::Black) {
|
||||||
fillRect(x, y, width, height, true);
|
fillRect(x, y, width, height, true);
|
||||||
} else if (color == Color::White) {
|
} else if (color == Color::White) {
|
||||||
fillRect(x, y, width, height, false);
|
fillRect(x, y, width, height, false);
|
||||||
} else {
|
} else if (color == Color::LightGray) {
|
||||||
for (int fillY = y; fillY < y + height; fillY++) {
|
for (int fillY = y; fillY < y + height; fillY++) {
|
||||||
for (int fillX = x; fillX < x + width; fillX++) {
|
for (int fillX = x; fillX < x + width; fillX++) {
|
||||||
drawPixelDither(fillX, fillY, color);
|
drawPixelDither<Color::LightGray>(fillX, fillY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (color == Color::DarkGray) {
|
||||||
|
for (int fillY = y; fillY < y + height; fillY++) {
|
||||||
|
for (int fillX = x; fillX < x + width; fillX++) {
|
||||||
|
drawPixelDither<Color::DarkGray>(fillX, fillY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GfxRenderer::fillArc(const int maxRadius, const int cx, const int cy, const int xDir, const int yDir,
|
template <Color color>
|
||||||
Color color) const {
|
void GfxRenderer::fillArc(const int maxRadius, const int cx, const int cy, const int xDir, const int yDir) const {
|
||||||
const int radiusSq = maxRadius * maxRadius;
|
const int radiusSq = maxRadius * maxRadius;
|
||||||
for (int dy = 0; dy <= maxRadius; ++dy) {
|
for (int dy = 0; dy <= maxRadius; ++dy) {
|
||||||
for (int dx = 0; dx <= maxRadius; ++dx) {
|
for (int dx = 0; dx <= maxRadius; ++dx) {
|
||||||
@@ -292,7 +294,7 @@ void GfxRenderer::fillArc(const int maxRadius, const int cx, const int cy, const
|
|||||||
const int px = cx + xDir * dx;
|
const int px = cx + xDir * dx;
|
||||||
const int py = cy + yDir * dy;
|
const int py = cy + yDir * dy;
|
||||||
if (distSq <= radiusSq) {
|
if (distSq <= radiusSq) {
|
||||||
drawPixelDither(px, py, color);
|
drawPixelDither<color>(px, py);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,26 +329,45 @@ void GfxRenderer::fillRoundedRect(const int x, const int y, const int width, con
|
|||||||
fillRectDither(x + width - maxRadius - 1, y + maxRadius + 1, maxRadius + 1, verticalHeight, color);
|
fillRectDither(x + width - maxRadius - 1, y + maxRadius + 1, maxRadius + 1, verticalHeight, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto fillArcTemplated = [this](int maxRadius, int cx, int cy, int xDir, int yDir, Color color) {
|
||||||
|
switch (color) {
|
||||||
|
case Color::Clear:
|
||||||
|
break;
|
||||||
|
case Color::Black:
|
||||||
|
fillArc<Color::Black>(maxRadius, cx, cy, xDir, yDir);
|
||||||
|
break;
|
||||||
|
case Color::White:
|
||||||
|
fillArc<Color::White>(maxRadius, cx, cy, xDir, yDir);
|
||||||
|
break;
|
||||||
|
case Color::LightGray:
|
||||||
|
fillArc<Color::LightGray>(maxRadius, cx, cy, xDir, yDir);
|
||||||
|
break;
|
||||||
|
case Color::DarkGray:
|
||||||
|
fillArc<Color::DarkGray>(maxRadius, cx, cy, xDir, yDir);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (roundTopLeft) {
|
if (roundTopLeft) {
|
||||||
fillArc(maxRadius, x + maxRadius, y + maxRadius, -1, -1, color);
|
fillArcTemplated(maxRadius, x + maxRadius, y + maxRadius, -1, -1, color);
|
||||||
} else {
|
} else {
|
||||||
fillRectDither(x, y, maxRadius + 1, maxRadius + 1, color);
|
fillRectDither(x, y, maxRadius + 1, maxRadius + 1, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (roundTopRight) {
|
if (roundTopRight) {
|
||||||
fillArc(maxRadius, x + width - maxRadius - 1, y + maxRadius, 1, -1, color);
|
fillArcTemplated(maxRadius, x + width - maxRadius - 1, y + maxRadius, 1, -1, color);
|
||||||
} else {
|
} else {
|
||||||
fillRectDither(x + width - maxRadius - 1, y, maxRadius + 1, maxRadius + 1, color);
|
fillRectDither(x + width - maxRadius - 1, y, maxRadius + 1, maxRadius + 1, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (roundBottomRight) {
|
if (roundBottomRight) {
|
||||||
fillArc(maxRadius, x + width - maxRadius - 1, y + height - maxRadius - 1, 1, 1, color);
|
fillArcTemplated(maxRadius, x + width - maxRadius - 1, y + height - maxRadius - 1, 1, 1, color);
|
||||||
} else {
|
} else {
|
||||||
fillRectDither(x + width - maxRadius - 1, y + height - maxRadius - 1, maxRadius + 1, maxRadius + 1, color);
|
fillRectDither(x + width - maxRadius - 1, y + height - maxRadius - 1, maxRadius + 1, maxRadius + 1, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (roundBottomLeft) {
|
if (roundBottomLeft) {
|
||||||
fillArc(maxRadius, x + maxRadius, y + height - maxRadius - 1, -1, 1, color);
|
fillArcTemplated(maxRadius, x + maxRadius, y + height - maxRadius - 1, -1, 1, color);
|
||||||
} else {
|
} else {
|
||||||
fillRectDither(x, y + height - maxRadius - 1, maxRadius + 1, maxRadius + 1, color);
|
fillRectDither(x, y + height - maxRadius - 1, maxRadius + 1, maxRadius + 1, color);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,8 +39,10 @@ class GfxRenderer {
|
|||||||
EpdFontFamily::Style style) const;
|
EpdFontFamily::Style style) const;
|
||||||
void freeBwBufferChunks();
|
void freeBwBufferChunks();
|
||||||
void rotateCoordinates(int x, int y, int* rotatedX, int* rotatedY) const;
|
void rotateCoordinates(int x, int y, int* rotatedX, int* rotatedY) const;
|
||||||
void drawPixelDither(int x, int y, Color color) const;
|
template <Color color>
|
||||||
void fillArc(int maxRadius, int cx, int cy, int xDir, int yDir, Color color) const;
|
void drawPixelDither(int x, int y) const;
|
||||||
|
template <Color color>
|
||||||
|
void fillArc(int maxRadius, int cx, int cy, int xDir, int yDir) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit GfxRenderer(HalDisplay& halDisplay)
|
explicit GfxRenderer(HalDisplay& halDisplay)
|
||||||
|
|||||||
Reference in New Issue
Block a user