refactor: Refactor drawArc / fillArc for faster execution (#1540)

## Summary

* **What is the goal of this PR?** Replace the o(r^2) routines with a
o(r) scanline logic - will make fillArc roughly 50% faster and drawArc
roughly 5x faster. Still probably unnoticeable.
* **What changes are included?**

## Additional Context


---

### 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:
jpirnay
2026-04-16 22:57:03 +03:00
committed by GitHub
parent 81ae9dd779
commit 0c5dee3c62
+64 -17
View File
@@ -343,17 +343,40 @@ void GfxRenderer::drawArc(const int maxRadius, const int cx, const int cy, const
const int lineWidth, const bool state) const {
const int stroke = std::min(lineWidth, maxRadius);
const int innerRadius = std::max(maxRadius - stroke, 0);
const int outerRadiusSq = maxRadius * maxRadius;
const int outerRadius = maxRadius;
if (outerRadius <= 0) {
return;
}
const int outerRadiusSq = outerRadius * outerRadius;
const int innerRadiusSq = innerRadius * innerRadius;
for (int dy = 0; dy <= maxRadius; ++dy) {
for (int dx = 0; dx <= maxRadius; ++dx) {
const int distSq = dx * dx + dy * dy;
if (distSq > outerRadiusSq || distSq < innerRadiusSq) {
continue;
}
const int px = cx + xDir * dx;
const int py = cy + yDir * dy;
drawPixel(px, py, state);
int xOuter = outerRadius;
int xInner = innerRadius;
for (int dy = 0; dy <= outerRadius; ++dy) {
while (xOuter > 0 && (xOuter * xOuter + dy * dy) > outerRadiusSq) {
--xOuter;
}
// Keep the smallest x that still lies outside/at the inner radius,
// i.e. (x^2 + y^2) >= innerRadiusSq.
while (xInner > 0 && ((xInner - 1) * (xInner - 1) + dy * dy) >= innerRadiusSq) {
--xInner;
}
if (xOuter < xInner) {
continue;
}
const int x0 = cx + xDir * xInner;
const int x1 = cx + xDir * xOuter;
const int left = std::min(x0, x1);
const int width = std::abs(x1 - x0) + 1;
const int py = cy + yDir * dy;
if (width > 0) {
fillRect(left, py, width, 1, state);
}
}
};
@@ -472,15 +495,39 @@ void GfxRenderer::fillRectDither(const int x, const int y, const int width, cons
template <Color color>
void GfxRenderer::fillArc(const int maxRadius, const int cx, const int cy, const int xDir, const int yDir) const {
if (maxRadius <= 0) return;
if constexpr (color == Color::Clear) {
return;
}
const int radiusSq = maxRadius * maxRadius;
// Avoid sqrt by scanning from outer radius inward while y grows.
int x = maxRadius;
for (int dy = 0; dy <= maxRadius; ++dy) {
for (int dx = 0; dx <= maxRadius; ++dx) {
const int distSq = dx * dx + dy * dy;
const int px = cx + xDir * dx;
const int py = cy + yDir * dy;
if (distSq <= radiusSq) {
drawPixelDither<color>(px, py);
}
while (x > 0 && (x * x + dy * dy) > radiusSq) {
--x;
}
if (x < 0) break;
const int py = cy + yDir * dy;
if (py < 0 || py >= getScreenHeight()) continue;
int x0 = cx;
int x1 = cx + xDir * x;
if (x0 > x1) std::swap(x0, x1);
const int width = x1 - x0 + 1;
if (width <= 0) continue;
if constexpr (color == Color::Black) {
fillRect(x0, py, width, 1, true);
} else if constexpr (color == Color::White) {
fillRect(x0, py, width, 1, false);
} else {
// LightGray / DarkGray: use existing dithered fill path.
fillRectDither(x0, py, width, 1, color);
}
}
}