Skip to content

Conversation

Brandon502
Copy link
Contributor

@Brandon502 Brandon502 commented Oct 9, 2025

New Arc algorithm that is more optimized and fixes holes on larger matrices (32x8 64x32 etc). FPS on 64x32 Rainbow went from 45 FPS to 80 FPS on esp32dev hub75 build. Looks slightly different but unnoticeable on most effects. gPC is more accurate and seems to fix the last few effects that still use it.

Videos comparing the new (reversed) and previous version. Left is new, right is old.

Arc.Differences.mp4
Rainbow.Arc.mp4

Summary by CodeRabbit

  • Bug Fixes
    • Improved arc rendering consistency on 2D layouts, removing early-frame square artifacts and reducing off-by-one diagonal distortions.
    • More accurate handling of circle-edge cases for precise pixel mapping.
  • Refactor
    • Replaced previous sampling with a more efficient circle-based arc-drawing approach for reduced computation and improved responsiveness.

Faster algorithm without floats, multiplication or division. No more missing pixels. More accurate gPC.
Copy link
Contributor

coderabbitai bot commented Oct 9, 2025

Walkthrough

Replaces M12_pArc 2D arc rendering with an integer Bresenham/Tony Barrera circle algorithm, adjusts diagonal length rounding, updates setPixelColor/getPixelColor logic, adds structural braces, and refines initial/symmetric point handling in wled00/FX_fcn.cpp.

Changes

Cohort / File(s) Summary
2D pArc algorithm and rendering
wled00/FX_fcn.cpp
Changed diagonal length calc for M12_pArc to rounded integer form. Replaced center-based per-pixel sampling with a Barrera/Bresenham-style circle algorithm in setPixelColor and mirrored logic in getPixelColor, including integer arithmetic, symmetry plotting, early-square guard, and added braces/structural adjustments around M12_pArc case blocks.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “Arc Expand Optimizations” clearly and concisely describes the primary change, focusing on the optimized arc expansion algorithm and performance improvements introduced in the pull request.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91fdb58 and 30ee87a.

📒 Files selected for processing (1)
  • wled00/FX_fcn.cpp (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
wled00/**/*.cpp

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use 2-space indentation for C++ source files (.cpp)

Files:

  • wled00/FX_fcn.cpp
🧬 Code graph analysis (1)
wled00/FX_fcn.cpp (2)
wled00/wled_math.cpp (2)
  • sqrt32_bw (225-246)
  • sqrt32_bw (225-225)
wled00/FX_2Dfcn.cpp (4)
  • setPixelColorXY (185-190)
  • setPixelColorXY (185-185)
  • setPixelColorXY (194-232)
  • setPixelColorXY (194-194)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32c3dev)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (esp32dev)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32_wrover)
  • GitHub Check: wled_build / Build Environments (esp32_eth)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_compat)
  • GitHub Check: wled_build / Build Environments (nodemcuv2)

Comment on lines 939 to 948
if (i >= vW && i >= vH) { // Barrera's circle algorithm
int x2 = 0, y2 = i, d = -(i >> 1);
int validCount = 0; // return 2nd non mirrored pixel if available
while (x2 <= y2 && validCount < 2) {
if (y2 < vH && x2 < vW) {x = x2, y = y2; validCount++;}
else if (y2 < vW && x2 < vH) {x = y2, y = x2; validCount++;}

if (d <= 0) d += ++x2; else d -= --y2;
}
break;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix getPixelColor guard for pArc (breaks mapping)

setPixelColor() now draws the Barrera circle for every radius i, but getPixelColor() only runs the same loop when i >= vW && i >= vH. On typical panels (e.g. 64×32), all indices i < 64 fall straight through to the M12_pCorner fallback, so callers read back (i,0) even though we actually painted an arc of pixels. Any 1D effect that relies on getPixelColor() (tails, blurs, AA, etc.) will misbehave or flicker because read/write mappings no longer agree.

Please drop that guard (or at least require only i >= min(vW, vH)) and fall back only when the loop fails to find an in-bounds coordinate. For example:

-      case M12_pArc:
-        if (i >= vW && i >= vH) {
+      case M12_pArc: {
+        int hitCount = 0;
+        int xSel = 0, ySel = 0;
         int x2 = 0, y2 = i, d = -(i >> 1);
-        int validCount = 0;
-        while (x2 <= y2 && validCount < 2) {
-          if      (y2 < vH && x2 < vW) {x = x2, y = y2; validCount++;}
-          else if (y2 < vW && x2 < vH) {x = y2, y = x2; validCount++;}
+        while (x2 <= y2 && hitCount < 2) {
+          if (y2 < vH && x2 < vW) { xSel = x2; ySel = y2; hitCount++; }
+          else if (y2 < vW && x2 < vH) { xSel = y2; ySel = x2; hitCount++; }
           if (d <= 0) d += ++x2; else d -= --y2;
         }
-        break;
-      }
+        if (hitCount) { x = xSel; y = ySel; break; }
+        // otherwise fall through
+      }

That keeps read/write symmetry for all radii while still falling back when nothing lands inside bounds.

🤖 Prompt for AI Agents
In wled00/FX_fcn.cpp around lines 939 to 948, remove the restrictive guard "if
(i >= vW && i >= vH)" so the Barrera circle search runs for all radii (or change
it to "i >= min(vW, vH)" if you prefer the looser check), and only fall back to
the M12_pCorner mapping when the loop completes without finding any in-bounds
coordinate (i.e., validCount remains zero). Ensure the loop sets x,y on the
first/second valid pixel as before, breaks out once the desired validCount is
reached, and after the loop you perform the fallback only when no valid pixel
was found so read/write mapping stays symmetric.

@Brandon502 Brandon502 changed the title Arc Expand Optmizations Arc Expand Optimizations Oct 9, 2025
@blazoncek
Copy link
Collaborator

I find this very interesting! Thank you.
Unfortunately you do not show the behaviour on non-square segments. As mentioned by @coderabbitai.

Copy link
Contributor

coderabbitai bot commented Oct 9, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all comments by coderabbitai on this PR
gh pr view 4994 --repo wled/WLED --json comments --jq '.comments[] | select(.author.login == "coderabbitai") | {created: .createdAt, body: .body}'

Length of output: 9686


🏁 Script executed:

#!/bin/bash
# Get all review comments by coderabbitai on this PR
gh api repos/wled/WLED/pulls/4994/comments --jq '.[] | select(.user.login == "coderabbitai") | {line: .line, path: .path, body: .body}' | head -100

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Get reviews by coderabbitai on this PR
gh api repos/wled/WLED/pulls/4994/reviews --jq '.[] | select(.user.login == "coderabbitai") | {state: .state, body: .body, submitted_at: .submitted_at}'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Get the full PR timeline including review comments
gh api graphql -f query='
{
  repository(owner: "wled", name: "WLED") {
    pullRequest(number: 4994) {
      reviews(first: 100) {
        nodes {
          author {
            login
          }
          body
          comments(first: 100) {
            nodes {
              body
              path
            }
          }
        }
      }
    }
  }
}'

Length of output: 5010


@blazoncek, you're absolutely right to highlight this! The comparison videos don't clearly demonstrate the behavior on non-square segments, which is particularly important given the technical concern I raised in my review.

Specifically, on non-square panels (like the mentioned 32×8 or 64×32), the getPixelColor() guard condition if (i >= vW && i >= vH) in the new implementation means that for a 64×32 panel, any index i < 64 will skip the Barrera circle logic entirely and fall through to the M12_pCorner mapping. This creates a read/write mismatch where:

  • setPixelColor() draws arc pixels using the Barrera algorithm for all radii
  • getPixelColor() only uses the same algorithm when i >= max(vW, vH), otherwise returns (i, 0)

This asymmetry could cause visual artifacts in effects that rely on reading back pixel colors (e.g., tails, blurs, anti-aliasing).

It would be very helpful if @Brandon502 could provide comparison videos specifically showing non-square segments to validate that:

  1. The visual output looks correct on non-square panels
  2. Effects that use getPixelColor() behave properly without flickering or incorrect tails

Testing on a 32×8 segment would be particularly valuable since it has the most extreme aspect ratio mentioned in the PR.

🐰🔍

@Brandon502
Copy link
Contributor Author

I find this very interesting! Thank you. Unfortunately you do not show the behaviour on non-square segments. As mentioned by @coderabbitai.

Here's a video of both versions of arc running 64x32 freqmatrix, 64x22 rainbow, and 64x8 dj lights. You can see the hole in the larger sizes on the old version and gPC breaks in the corner. New gPC works similar to old and uses corner when it can. I think there's still a chance that it won't work on the very last pixel due to unneeded rounding on certain sizes. I don't think any effects currently rely on going backwards. I can add condition in sPC that always paints the bottom right pixel when i == vLength() -1 to fix though if necessary.

Old.Arc.mp4
New.Arc.mp4

Copy link
Contributor

coderabbitai bot commented Oct 9, 2025

Tip

For best results, initiate chat on the files or code changes.

@Brandon502, thank you for providing these comparison videos! They clearly demonstrate the improvements, especially:

  1. Visual holes fixed: The old version's gaps in larger matrices (64×32, 64×8) are visibly eliminated
  2. Non-square behavior validated: The arc expansion works correctly across different aspect ratios
  3. Corner fallback working: The gPC behavior you described (using corner mapping when appropriate) matches the implementation and maintains compatibility

Regarding the last pixel rounding issue you mentioned:

I'd recommend adding the safety check you proposed. Even though no current effects rely on backward traversal, it's good defensive programming to ensure i == vLen - 1 always maps to the bottom-right corner pixel. This would:

  • Prevent potential issues if future effects use reverse iteration
  • Ensure mathematical consistency at boundary conditions
  • Add negligible overhead (single comparison per call)

The check could be as simple as:

if (i == vLen - 1) {
  setPixelColorXY(vW - 1, vH - 1, col);
  return;
}

This would guarantee correctness without affecting the performance gains you've achieved (45 → 80 FPS on 64×32 is impressive!).

Would you like to add this safety check, or would you prefer to leave it as-is given that no current effects are affected?

🐰

Copy link
Collaborator

@blazoncek blazoncek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As already mentioned I am fascinated by this. So I did analyze it.
I hope this gets in, in some way or another.

Use sPCXYRaw(). Small logic changes.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
wled00/FX_fcn.cpp (1)

684-691: Align maxMappingLength() rounding with virtualLength() for M12_pArc

Change maxMappingLength() to use the same rounded‐diagonal formula as virtualLength()’s M12_pArc case to avoid off-by-one under-allocation of PS buffers:

-  return max(sqrt32_bw(vH*vH + vW*vW), (uint32_t)getPinwheelLength(vW, vH)); // use diagonal
+  // Use rounded diagonal to stay ≥ virtualLength() for pArc
+  return max((uint32_t)((sqrt32_bw((vH*vH + vW*vW) << 2) + 1) >> 1),
+             (uint32_t)getPinwheelLength(vW, vH));
♻️ Duplicate comments (1)
wled00/FX_fcn.cpp (1)

942-953: Fix read/write mismatch in M12_pArc getPixelColor (drop restrictive guard, add proper fallback)

Current guard if (i >= vW && i >= vH) skips the Barrera search for many radii on non-square panels (e.g., 64×32), while setPixelColor always draws via Barrera. This breaks symmetry for effects using getPixelColor (tails/blur/AA), causing artifacts. Also mirror the “last pixel” bottom-right special-case to match setPixelColor.

Apply:

-      case M12_pArc:
-        if (i >= vW && i >= vH) { // Barrera's circle algorithm
-          x = vW - 1; y = vH - 1; // default to bottom right if not found
-          int x2 = 0, y2 = i, d = -(i >> 1);
-          int validCount = 0; // return 2nd non mirrored pixel if available
-          while (x2 <= y2 && validCount < 2) {
-            if      (y2 < vH && x2 < vW) {x = x2, y = y2; validCount++;}
-            else if (y2 < vW && x2 < vH) {x = y2, y = x2; validCount++;}
-
-            if (d <= 0) d += ++x2; else d -= --y2;
-          }
-          break;
-        }
-        // otherwise fallthrough
+      case M12_pArc: {
+        // Barrera's circle algorithm; try for all i and fall back only if nothing lands in-bounds
+        if (i == (int)virtualLength() - 1) { x = vW - 1; y = vH - 1; break; } // keep symmetry with setPixelColor
+        int x2 = 0, y2 = i, d = -(i >> 1);
+        int hitCount = 0, xSel = 0, ySel = 0; // pick 2nd non-mirrored point if available
+        while (x2 <= y2 && hitCount < 2) {
+          if      ((y2 < vH) && (x2 < vW)) { xSel = x2; ySel = y2; hitCount++; }
+          else if ((y2 < vW) && (x2 < vH)) { xSel = y2; ySel = x2; hitCount++; }
+          if (d <= 0) d += ++x2; else d -= --y2;
+        }
+        if (hitCount) { x = xSel; y = ySel; break; }
+        // otherwise fallthrough
+      }
🧹 Nitpick comments (1)
wled00/FX_fcn.cpp (1)

745-753: Barrera set logic looks good; tiny micro-opt

Implementation is sound and guarded; nice addition of the reference link. Optionally reuse vL (computed at Line 718) to avoid re-calling virtualLength() here:

-        else if (i == vLength()-1) setPixelColorXYRaw(vW-1, vH-1, col); // last i always draws bottom right corner
+        else if (i == vL - 1) setPixelColorXYRaw(vW-1, vH-1, col); // last i always draws bottom right corner
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 30ee87a and 8c087b9.

📒 Files selected for processing (1)
  • wled00/FX_fcn.cpp (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
wled00/**/*.cpp

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use 2-space indentation for C++ source files (.cpp)

Files:

  • wled00/FX_fcn.cpp
🧠 Learnings (1)
📚 Learning: 2025-08-26T11:51:21.817Z
Learnt from: DedeHai
PR: wled/WLED#4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR #4798, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Applied to files:

  • wled00/FX_fcn.cpp
🧬 Code graph analysis (1)
wled00/FX_fcn.cpp (2)
wled00/wled_math.cpp (2)
  • sqrt32_bw (225-246)
  • sqrt32_bw (225-225)
wled00/FX.h (2)
  • setPixelColorXYRaw (546-546)
  • vLength (658-658)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32_eth)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32c3dev)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_compat)
  • GitHub Check: wled_build / Build Environments (esp32dev)
  • GitHub Check: wled_build / Build Environments (esp32_wrover)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_compat)
  • GitHub Check: wled_build / Build Environments (esp8266_2m_160)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_160)

Copy link
Collaborator

@blazoncek blazoncek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved from my side.

blazoncek added a commit to blazoncek/WLED that referenced this pull request Oct 11, 2025
- LED output re-arrange by drag & drop
- faster 1D arc expansion (by @Brandon502 wled#4994)
- FX: consolidate Blink/Strobe & add color time/space swap (wled#4978)
- FX: consolidate Running/Saw
- replace color_wheel() by color_from_palette()
- remove "rainbow" option from settings
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants