From 0c5dee3c62b4dcab03893c901e4662bcf33ae829 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Thu, 16 Apr 2026 21:57:03 +0200 Subject: [PATCH] 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 >**_ --- lib/GfxRenderer/GfxRenderer.cpp | 81 ++++++++++++++++++++++++++------- 1 file changed, 64 insertions(+), 17 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 2d685e5fe..a343badcc 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -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 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(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); } } }