Skip to content

Stencil blendingmode and some speed optimisations#4889

Merged
DedeHai merged 9 commits intowled:mainfrom
DedeHai:stencil_blendingmode
Mar 22, 2026
Merged

Stencil blendingmode and some speed optimisations#4889
DedeHai merged 9 commits intowled:mainfrom
DedeHai:stencil_blendingmode

Conversation

@DedeHai
Copy link
Collaborator

@DedeHai DedeHai commented Aug 30, 2025

as mentioned in #4550 there is no easy way to overlay text with the new blending modes. this addition fixes that.

I also made some slight optimisations to blendSegment() and found that the compiler is very unpredictable. I assume that is mostly due to pipelining and core register use. In general, replacing bit-field accessed variables with an alias or a pre-computed value is faster, but here it is not always the case. I tried different variations and this was the best performing one. Replacing any other repeated calculations in blendSegment() does not improve performance.

while flash size increases by a few hundred bytes, FPS improve by 3-5% depending on FX and layering used.

Summary by CodeRabbit

  • New Features

    • Added a "Stencil" blend mode for segments (background masking effect).
  • Bug Fixes

    • Blend mode selection now sends the actual mode value so chosen modes apply correctly.
    • Faster, more consistent blending across 1D/2D segments (mirror/reverse/push handling).
    • Unsupported blend modes now fall back to a safe default.
  • Notes

    • Blend-mode value handling simplified; non-standard codes may be accepted.

- better performance at the cost of about 700 bytes of flash
- Stencil overlay adds an FX on top but leaves the backgroundcolor transparent, this is useful for classic text overlays
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 30, 2025

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Renames blending-style macros to TRANSITION_*, introduces a per-pixel segblend fast path and a new "stencil" blend mode, updates UI to send explicit blend-mode values, simplifies JSON deserialization for segment blendMode, and adjusts inline comments and minor whitespace. No public API signatures changed.

Changes

Cohort / File(s) Summary
Blending core refactor
wled00/FX_fcn.cpp
Introduces segblend(t,b) fast path and BLENDMODES bound, adds _stencil() and _dummy() helpers, replaces previous blend invocations with segblend, updates clipping/push/coordinate logic, and switches transition/style checks to TRANSITION_*.
Header macro/inline comment updates
wled00/FX.h
Renames BLEND_STYLE_*TRANSITION_* and BLEND_STYLE_COUNTTRANSITION_COUNT; removes commented bitfield and updates Segment::blendMode / isPreviousMode() inline comments to reference new modes.
2D clipping condition updates
wled00/FX_2Dfcn.cpp
Switches BLEND_STYLE_* identifiers to TRANSITION_* in isPixelXYClipped transition/clipping conditionals; no logic or arithmetic changes.
Segment transition callsites
wled00/FX_fcn.cpp (setColor/setOpacity/setOption/setPalette, currentCCT/currentBri/beginDraw, service/blendSegment)
Updates transition fade checks to use TRANSITION_FADE, adjusts when transitions copy old segment vs. not, and uses new blending logic across these callsites.
UI: blend-mode option & transmission
wled00/data/index.js
Adds "Stencil" option (value=16) to seg{i}bm dropdown and changes setBm(s) to read the select value and send it as {"seg":{"id":s,"bm":value}}.
JSON deserialization
wled00/json.cpp
Replaced temporary/clamped parsing of bm with direct getVal(elem["bm"], seg.blendMode), delegating normalization to getVal.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • willmmiles
  • blazoncek
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 39.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 'Stencil blendingmode and some speed optimisations' accurately describes the main changes: adding a stencil blending mode and performance optimizations to the blending system.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Tip

CodeRabbit can generate a title for your PR based on the changes.

Add @coderabbitai placeholder anywhere in the title of your PR and CodeRabbit will replace it with a title based on the changes in the PR. You can change the placeholder by changing the reviews.auto_title_placeholder setting.

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

🧹 Nitpick comments (3)
wled00/FX.h (1)

459-459: Document exact bm codes, incl. Stencil (32) and Average (5).

Make the mapping explicit to avoid confusion and to reflect the UI: 0=top, 1=bottom, 2=add, 3=subtract, 4=difference, 5=average, 6=multiply, 7=divide, 8=lighten, 9=darken, 10=screen, 11=overlay, 12=hardlight, 13=softlight, 14=dodge, 15=burn, 32=stencil (uses colors[1] as background mask).

-    uint8_t   blendMode;          // segment blending modes: top, bottom, add, subtract, difference, multiply, divide, lighten, darken, screen, overlay, hardlight, softlight, dodge, burn, stencil
+    uint8_t   blendMode;          // segment blending modes (bm): 0=top,1=bottom,2=add,3=subtract,4=difference,5=average,6=multiply,7=divide,8=lighten,9=darken,10=screen,11=overlay,12=hardlight,13=softlight,14=dodge,15=burn,32=stencil (bg=colors[1])
wled00/data/index.js (1)

2362-2365: Send bm as a number, not a string.

Parse to int before sending to avoid accidental string types and keep consistency with other setters.

-	var value = gId(`seg${s}bm`).value;
-	var obj = {"seg": {"id": s, "bm": value}};
+	const value = parseInt(gId(`seg${s}bm`).value, 10);
+	const obj = {"seg": {"id": s, "bm": value}};
wled00/json.cpp (1)

305-305: Validate bm to allowed set (0–15, 32).

Removing the clamp enables arbitrary values. segblend defaults to “Top”, but guarding here prevents unexpected UI/API inputs from drifting.

-  getVal(elem["bm"], seg.blendMode);
+  if (getVal(elem["bm"], seg.blendMode)) {
+    if (seg.blendMode != 32 && seg.blendMode > 15) seg.blendMode = 0; // fallback to Top
+  }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between da7f107 and 9c6ab89.

📒 Files selected for processing (4)
  • wled00/FX.h (1 hunks)
  • wled00/FX_fcn.cpp (6 hunks)
  • wled00/data/index.js (2 hunks)
  • wled00/json.cpp (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
wled00/data/**/*.{htm,html,css,js}

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

Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Files:

  • wled00/data/index.js
wled00/{*.cpp,!(html_*)*.h}

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

Use spaces (2 per level) for C++ source and header files

Files:

  • wled00/FX.h
  • wled00/json.cpp
  • wled00/FX_fcn.cpp
🧠 Learnings (2)
📚 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
📚 Learning: 2025-06-07T15:58:42.579Z
Learnt from: willmmiles
PR: wled/WLED#4712
File: wled00/FX_fcn.cpp:992-993
Timestamp: 2025-06-07T15:58:42.579Z
Learning: In WLED segments, the backing pixel buffer is always allocated to the full physical segment size, but effect rendering only uses a subset when mirror or grouping are enabled. For 2D segments, this subset is vWidth() * vHeight(), and for 1D segments it's vLength(). Color processing operations like fade_out should iterate over this rendering subset, not the full allocated buffer.

Applied to files:

  • wled00/FX_fcn.cpp
🧬 Code graph analysis (2)
wled00/json.cpp (1)
wled00/util.cpp (2)
  • getVal (66-83)
  • getVal (66-66)
wled00/FX_fcn.cpp (1)
wled00/colors.cpp (4)
  • color_add (28-61)
  • color_add (28-28)
  • color_blend (11-21)
  • color_blend (11-11)
⏰ 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). (18)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Environments (esp32c3dev)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_compat)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_160)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_160)
  • GitHub Check: wled_build / Build Environments (esp32dev)
  • GitHub Check: wled_build / Build Environments (esp32_eth)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_compat)
  • GitHub Check: wled_build / Build Environments (esp32_wrover)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full)
  • GitHub Check: wled_build / Build Environments (esp8266_2m_160)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (esp8266_2m_compat)
  • GitHub Check: wled_build / Build Environments (nodemcuv2)
  • GitHub Check: wled_build / Build Environments (esp8266_2m)
🔇 Additional comments (2)
wled00/data/index.js (1)

828-829: UI option for Stencil (32) looks good.

Matches FX_fcn implementation and JSON handling.

wled00/FX_fcn.cpp (1)

1345-1572: Consolidation into segblend + push/mirror logic: solid.

The restructure reduces branching and enables Stencil; coordinate transforms are gated and reused cleanly.

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

♻️ Duplicate comments (1)
wled00/FX_fcn.cpp (1)

1324-1324: Subtract mode green-channel bug fixed

The green channel now subtracts bottom from top like the others. Thanks for addressing the prior issue.

🧹 Nitpick comments (3)
wled00/FX_fcn.cpp (3)

1317-1343: segblend() consolidation: clear and fast path

Single switch-based routine reduces indirections and keeps hot code tight. Consider forcing placement/inlining for hot path:

  • Add IRAM attribute and inline hint to avoid flash stalls in tight per-pixel loops.
-static uint32_t segblend(CRGBW tcol, CRGBW bcol, CRGBW bgcol, uint8_t blendMode) {
+IRAM_ATTR static inline uint32_t segblend(CRGBW tcol, CRGBW bcol, CRGBW bgcol, uint8_t blendMode) {

1339-1340: Stencil semantics: exact color match vs. tolerance

Equality on 32-bit color is correct for a pure stencil, but can be brittle if upstream effects introduce minor channel drift. If you see unexpected “holes,” consider (optional) channel-tolerance compare.

-    case 32: return tcol.color32 == bgcol.color32 ? bcol.color32 : tcol.color32; // Stencil
+    case 32: {
+      // Optional tolerance (e.g. 0..2) to avoid miss due to rounding/dithering
+      constexpr uint8_t tol = 0;
+      bool isBG =
+        (abs(int(tC.r)-int(bgcol.r)) <= tol) &&
+        (abs(int(tC.g)-int(bgcol.g)) <= tol) &&
+        (abs(int(tC.b)-int(bgcol.b)) <= tol) &&
+        (abs(int(tC.w)-int(bgcol.w)) <= tol);
+      return isBG ? bcol.color32 : tcol.color32;
+    }

1452-1463: Push offsets computed once; clean modulo handling

Efficient offset math and sign handling. Minor nit: make offset vars int to avoid implicit unsigned wrap on negatives before the modulo.

-    unsigned offsetX = (blendingStyle == BLEND_STYLE_PUSH_UP   || blendingStyle == BLEND_STYLE_PUSH_DOWN)  ? 0 : progInv * nCols / 0xFFFFU;
-    unsigned offsetY = (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_RIGHT) ? 0 : progInv * nRows / 0xFFFFU;
+    int offsetX = (blendingStyle == BLEND_STYLE_PUSH_UP   || blendingStyle == BLEND_STYLE_PUSH_DOWN)  ? 0 : int(progInv * nCols / 0xFFFFU);
+    int offsetY = (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_RIGHT) ? 0 : int(progInv * nRows / 0xFFFFU);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9c6ab89 and bfc9c5e.

📒 Files selected for processing (1)
  • wled00/FX_fcn.cpp (8 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
wled00/{*.cpp,!(html_*)*.h}

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

Use spaces (2 per level) for C++ source and header files

Files:

  • wled00/FX_fcn.cpp
🧠 Learnings (2)
📚 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
📚 Learning: 2025-06-07T15:58:42.579Z
Learnt from: willmmiles
PR: wled/WLED#4712
File: wled00/FX_fcn.cpp:992-993
Timestamp: 2025-06-07T15:58:42.579Z
Learning: In WLED segments, the backing pixel buffer is always allocated to the full physical segment size, but effect rendering only uses a subset when mirror or grouping are enabled. For 2D segments, this subset is vWidth() * vHeight(), and for 1D segments it's vLength(). Color processing operations like fade_out should iterate over this rendering subset, not the full allocated buffer.

Applied to files:

  • wled00/FX_fcn.cpp
🧬 Code graph analysis (1)
wled00/FX_fcn.cpp (1)
wled00/colors.cpp (4)
  • color_add (28-61)
  • color_add (28-28)
  • color_blend (11-21)
  • color_blend (11-11)
⏰ 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). (5)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp32dev)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
🔇 Additional comments (8)
wled00/FX_fcn.cpp (8)

371-374: Palette blend clamp looks good

The explicit 255 cap avoids pathological loops; safe and clear.


1345-1351: Propagating bgColor for stencil-enabled blending

Plumbing secondary color as background for modes that need it is correct; keeps segblend signature minimal.


1425-1447: 2D mirroring writes: order and bounds look correct

  • Single write + mirrored writes use segblend then opacity mix; consistent with 1D path.
  • CCT mirror updates are guarded and aligned with pixel writes. LGTM.

1455-1456: applyReverse/applyMirror flags gate transforms

Good micro-optimization to avoid unnecessary per-pixel conditionals when no reverse/transpose/mirror is active.


1474-1475: Push mapping before sampling old/new segments

Correct to apply push before sampling seg/segO so clipped/fade logic sees the shifted canvas consistently.


1492-1495: Reverse/transpose only when needed

Deferred transform after sampling avoids double-mapping. Clean and readable.


1518-1531: 1D path parity with 2D: segblend + opacity

Mirroring, wrapping with offset, and segblend usage match 2D semantics. Consistency reduces edge-case drift. LGTM.


1613-1615: Gamma application guard aligns with realtime override

Skipping gamma when ARLS requests it is correct. Note: per prior learnings, effect-level gamma shaping elsewhere is intentional—no changes needed here.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Aug 31, 2025

A few more comments:
Unfortunately the compact function pointer array had to go: while nice, compact and to my surprise still fast it did not allow blending to operate on a full color, which is necessary to determine if the color is equal to the background in the new mode. I made the new blend mode be number 32 so it is out of the way in case someone finds a smart way to move back to a function array, all my attempts at that failed: it either increased flash size or made it slow. The version I settled on here is faster than the original but it increases flash use due to inlining.

The use of CRGBW instead of uint32_t reduces flash size and increases speed, although how exactly eludes me: the two aliases tC / bC don't do anything but make code more compact to read and came from a version where I passed uint32_t and then cast that: if instead of aliases the arguments are used directly, codesize increases by 250bytes, no idea why having aliases makes the compiler happy.

one more peculiarity: when I did initial tests in a different branch (heap_handling), using "O2" optimization flag on blendSegment() the code size increased by about 1.5k, the speed gain was a few percent in FPS. If I do the same on the version of this PR, flash size increases by over 10k or even 20k on C3! Is there any simple explanation for that @willmmiles ?

@willmmiles
Copy link
Member

one more peculiarity: when I did initial tests in a different branch (heap_handling), using "O2" optimization flag on blendSegment() the code size increased by about 1.5k, the speed gain was a few percent in FPS. If I do the same on the version of this PR, flash size increases by over 10k or even 20k on C3! Is there any simple explanation for that @willmmiles ?

IIRC, often big changes in code size are caused by loop unrolling.

case 0: return tcol.color32; // Top
case 1: return bcol.color32; // Bottom
case 2: return color_add(tcol, bcol, false); // Add
case 3: c.r = _subtract(tC.r, bC.r); c.g = _subtract(tC.g, bC.g); c.b = _subtract(tC.b, bC.b); c.w = _subtract(tC.w, bC.w); break; // Subtract
Copy link
Member

Choose a reason for hiding this comment

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

A bit of a nitpick, but I'd recommend using an inline metafunction or a macro to do the expansion for each channel.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

that would clean up the code significantly, I will check how to do that here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ok done.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Aug 31, 2025

here is the new blending mode in action: Black Hole FX with blur enabled over a background:
image

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

🧹 Nitpick comments (9)
wled00/FX_fcn.cpp (9)

1295-1305: Minor naming/usage mismatch: “unused” helpers are compiled in.

_top, _bottom, _add are marked “function currently unused” and aren’t referenced. Consider removing or marking static inline constexpr behind a feature flag to save a few bytes.

- static uint8_t _top       (uint8_t a, uint8_t b) { return a; } // function currently unused
- static uint8_t _bottom    (uint8_t a, uint8_t b) { return b; } // function currently unused
- static uint8_t _add       (uint8_t a, uint8_t b) { unsigned t = a + b; return t > 255 ? 255 : t; } // function currently unused
+// #define WLED_DEBUG_BLEND_FUNCS 1
+#ifdef WLED_DEBUG_BLEND_FUNCS
+static inline constexpr uint8_t _top    (uint8_t a, uint8_t) { return a; }
+static inline constexpr uint8_t _bottom (uint8_t, uint8_t b) { return b; }
+static inline constexpr uint8_t _add    (uint8_t a, uint8_t b) { unsigned t = a + b; return t > 255 ? 255 : t; }
+#endif

1320-1321: Prefer an inline function over a macro for type-safety.

APPLY_BLEND relies on outer names (tC,bC,c) and hides types. An inline helper is safer and can still inline well.

-#define APPLY_BLEND(op) c.r = op(tC.r, bC.r); c.g = op(tC.g, bC.g); c.b = op(tC.b, bC.b); c.w = op(tC.w, bC.w)
+template<typename Op>
+static inline void applyBlendOp(CRGBW& out, const CRGBW& a, const CRGBW& b, Op op) {
+  out.r = op(a.r, b.r); out.g = op(a.g, b.g); out.b = op(a.b, b.b); out.w = op(a.w, b.w);
+}

Then:

-    case 3:  APPLY_BLEND(_subtract);    break;
+    case 3:  applyBlendOp(c, tC, bC, _subtract);    break;

1322-1348: segblend(): semantics and perf look solid; add force-inline and guard CRGBW conversions.

  • Good consolidation of blend modes; stencil (32) behaves as described.
  • To keep this hot path in IRAM and reduce call overhead, consider IRAM_ATTR and always_inline.
  • If color_add() only accepts uint32_t, relying on implicit CRGBW→u32 conversions is brittle across toolchains.
-static uint32_t segblend(CRGBW tcol, CRGBW bcol, CRGBW bgcol, uint8_t blendMode) {
+static inline __attribute__((always_inline)) IRAM_ATTR uint32_t segblend(CRGBW tcol, CRGBW bcol, CRGBW bgcol, uint8_t blendMode) {
   CRGBW tC(tcol); CRGBW bC(bcol); CRGBW c;
   switch (blendMode) {
-    case 2:  return color_add(tcol, bcol, false); // Add
+    case 2:  return color_add(tcol.color32, bcol.color32, false); // Add (explicit)

Verification ask: confirm CRGBW has implicit conversion where used, or make all calls explicit (.color32) to avoid accidental overload picks.


1342-1346: Stencil equality: consider optional tolerance to handle palette/gamma drift.

Exact 32-bit equality can fail if the top color is dithered or preprocessed. If needed later, add an optional “within N per-channel” compare guarded by a flag.

Would you like a tiny helper that compares channels within a tolerance (e.g., <=1) and is compiled out by default?


1430-1453: 2D mirroring path: index math and CCT writes look correct; avoid repeated segblend bottom fetch.

You recompute segblend(c, _pixels[idx*], ...) per mirror target (necessary because bottoms differ), but _pixels[idx*] is read twice per pixel (once for blend input, once again implicitly in color_blend). Micro-opt: read once per idx and reuse.

- _pixels[indx] = color_blend(_pixels[indx], segblend(c, _pixels[indx], bgColor, blendMode), o);
+ uint32_t base = _pixels[indx];
+ uint32_t mixed = segblend(c, base, bgColor, blendMode);
+ _pixels[indx] = color_blend(base, mixed, o);

Same pattern for idxMX/idxMY/idxMM.


1457-1468: Push offsets: clean and branchless; mask-based wrap is fine.

Minor readability nit: unify comment (“shift canvas”) once at the switch header.


1554-1563: On/Off transition workaround still relies on global bri/briT; add a brief comment.

Future readers may forget why FADE is special-cased vs others. One-line rationale would help.


1299-1319: Divide/Dodge/Burn edge cases are safe.

No divide-by-zero due to guards; semantics are consistent with your chosen definitions. Consider constexpr to help constant-fold.


1322-1348: Unit test coverage for bm=32 (stencil) and regressions on bm=3 suggested.

Given prior subtract-mode bug, add minimal tests (or a debug scene) to assert:

  • Subtract uses bottom-minus-top across all channels.
  • Stencil returns bottom when top==bg, otherwise top.

I can provide a small harness that feeds CRGBW tuples through segblend() and checks results; want me to drop that in test/?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bfc9c5e and ae76ae8.

📒 Files selected for processing (1)
  • wled00/FX_fcn.cpp (9 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
wled00/{*.cpp,!(html_*)*.h}

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

Use spaces (2 per level) for C++ source and header files

Files:

  • wled00/FX_fcn.cpp
🧠 Learnings (3)
📓 Common learnings
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.
📚 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
📚 Learning: 2025-06-07T15:58:42.579Z
Learnt from: willmmiles
PR: wled/WLED#4712
File: wled00/FX_fcn.cpp:992-993
Timestamp: 2025-06-07T15:58:42.579Z
Learning: In WLED segments, the backing pixel buffer is always allocated to the full physical segment size, but effect rendering only uses a subset when mirror or grouping are enabled. For 2D segments, this subset is vWidth() * vHeight(), and for 1D segments it's vLength(). Color processing operations like fade_out should iterate over this rendering subset, not the full allocated buffer.

Applied to files:

  • wled00/FX_fcn.cpp
🧬 Code graph analysis (1)
wled00/FX_fcn.cpp (1)
wled00/colors.cpp (4)
  • color_add (28-61)
  • color_add (28-28)
  • color_blend (11-21)
  • color_blend (11-11)
⏰ 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). (18)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_compat)
  • GitHub Check: wled_build / Build Environments (esp32c3dev)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32_eth)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_160)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (esp32_wrover)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Environments (esp8266_2m_160)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_compat)
  • GitHub Check: wled_build / Build Environments (esp32dev)
  • GitHub Check: wled_build / Build Environments (esp8266_2m_compat)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_160)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full)
  • GitHub Check: wled_build / Build Environments (nodemcuv2)
  • GitHub Check: wled_build / Build Environments (esp8266_2m)
🔇 Additional comments (6)
wled00/FX_fcn.cpp (6)

372-375: Good safety clamp on palette blends.

Covers overflow in transition palette blending; no behavior concerns.


1350-1391: blendSegment(): clipping window math is correct; single-pixel segments fallback to FADE is a nice edge-case fix.

No issues; good use of dw/dh and inverted ranges for OUTSIDE_IN.


1460-1516: Reverse/transpose gating only when needed: good for perf; group expansion bounds are correct.

Use of groupLength() and min(...) avoids overruns. Looks good.


1531-1536: 1D mirroring path mirrors before primary write; offset wrap is correct.

No functional concerns; mirrors respect offset and segment bounds.


1575-1577: Restore global blendingStyle: good defensive reset.

Prevents leakage into overlay passes.


1618-1620: Gamma guard avoids work on black; confirm realtime/gamma combo.

Condition c > 0 && !(realtimeMode && arlsDisableGammaCorrection) is correct. Verify that “pure white with CCT-only segments” still passes as >0 (it does).

If you want, I can craft a quick bench to compare FPS with/without the c > 0 guard on typical scenes.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Sep 3, 2025

changed to draft, this PR needs to wait until WIP on transparency is done.

Copy link
Contributor

@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.

I've made my own version of "Stencil" blend mode (with three additional modes) without too much modifications (available in my fork).
Unfortunately code disparity from previous merges prohibits me from supplying a patch without too much effort.

Otherwise, I (personally) dislike the fact that Stencil mode uses constant 32 and the way selection of blending modes is implemented. I have not tested this code.

@github-actions
Copy link

github-actions bot commented Feb 5, 2026

Hey! This pull request has been open for quite some time without any new comments now. It will be closed automatically in a week if no further activity occurs.
Thank you for contributing to WLED! ❤️

@github-actions github-actions bot added the stale This issue will be closed soon because of prolonged inactivity label Feb 5, 2026
@DedeHai DedeHai added keep This issue will never become stale/closed automatically and removed stale This issue will be closed soon because of prolonged inactivity labels Feb 5, 2026
@DedeHai DedeHai marked this pull request as ready for review March 21, 2026 17:34
@DedeHai
Copy link
Collaborator Author

DedeHai commented Mar 21, 2026

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 21, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wled00/FX_fcn.cpp`:
- Around line 1500-1511: Diagonal push modes (BLEND_STYLE_PUSH_TL/TR/BR/BL)
never set pushOffsetX/pushOffsetY so they act like swipes instead of pushes;
update the switch in FX_fcn.cpp (around the switch handling blendingStyle) to
add cases for BLEND_STYLE_PUSH_TL, BLEND_STYLE_PUSH_TR, BLEND_STYLE_PUSH_BL and
BLEND_STYLE_PUSH_BR and set pushOffsetX and pushOffsetY using the existing
offsetX/offsetY logic (e.g., TL => pushOffsetX = -offsetX + nCols; pushOffsetY =
-offsetY + nRows; TR => pushOffsetX = offsetX; pushOffsetY = -offsetY + nRows;
BR => pushOffsetX = offsetX; pushOffsetY = offsetY; BL => pushOffsetX = -offsetX
+ nCols; pushOffsetY = offsetY) so diagonal pushes actually shift the canvas.
- Around line 1380-1399: The stencil blend (case 16) currently treats any
non-zero top pixel as opaque and ignores the segment background; change it to
compare against the segment background color instead by using
topSegment.colors[1] (bgColor) as the transparent key: ensure bgColor is
declared before the segblend lambda (or otherwise captured) and replace the
condition in blendMode case 16 from "t ? t : b" to "t != bgColor ? t : b" so
true stencil transparency uses the segment background rather than black.

In `@wled00/json.cpp`:
- Line 310: The call getVal(elem["bm"], seg.blendMode) writes integer JSON
values directly into seg.blendMode and you must restore the post-read clamp to
prevent out-of-range values from wrapping; after the getVal(...) call add a
bounds check that constrains seg.blendMode to a maximum (use a shared constant
like SEG_BLENDMODE_MAX declared in a header alongside the blend-mode
declarations) so seg.blendMode = min(seg.blendMode, SEG_BLENDMODE_MAX) (or
equivalent clamp).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a6cb62e9-3bba-45b5-a74c-64ae9d1fda35

📥 Commits

Reviewing files that changed from the base of the PR and between ae76ae8 and 828b6ea.

📒 Files selected for processing (4)
  • wled00/FX.h
  • wled00/FX_fcn.cpp
  • wled00/data/index.js
  • wled00/json.cpp
✅ Files skipped from review due to trivial changes (2)
  • wled00/data/index.js
  • wled00/FX.h

Copy link
Member

@softhack007 softhack007 left a comment

Choose a reason for hiding this comment

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

Looks good to me. 3% speedup is a nice result, too :-)
I am not (yet) an expert for the 16.0 rendering logic, but overall your changes make sense to me 👍

@softhack007
Copy link
Member

@coderabbitai re-review all.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 21, 2026

@softhack007 Sure, I'll re-review all the changes in PR #4889 now.


🧠 Learnings used
Learnt from: DedeHai
Repo: wled/WLED PR: 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.

Learnt from: DedeHai
Repo: wled/WLED PR: 4889
File: wled00/json.cpp:310-310
Timestamp: 2026-03-21T18:12:05.548Z
Learning: In WLED's `deserializeSegment()` (wled00/json.cpp), the blend mode field `seg.blendMode` is intentionally written without a post-read clamp (`getVal(elem["bm"], seg.blendMode)`). Out-of-range or unsupported blend mode values are handled safely in `WS2812FX::blendSegment()` (wled00/FX_fcn.cpp), which defaults to mode 0 for any unsupported value via a bounds check against the `BLENDMODES` constant. Do not flag the missing clamp in deserializeSegment as a bug in future reviews.

Learnt from: DedeHai
Repo: wled/WLED PR: 4889
File: wled00/FX_fcn.cpp:1380-1399
Timestamp: 2026-03-21T17:47:51.119Z
Learning: In WLED's stencil blend mode (case 16 in `WS2812FX::blendSegment()`, `wled00/FX_fcn.cpp`), the transparency key is intentionally hardcoded to black (`t ? t : b`), not the segment's background color (`colors[1]`). This is a deliberate design choice to avoid additional parameters and overhead. It is the user's responsibility to not use a non-black background in combination with stencil mode. Do not flag this as a bug in future reviews.

Learnt from: DedeHai
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2026-02-22T20:40:46.281Z
Learning: At low brightness (<30), color_blend() suffers from integer rounding that destroys RGB channel ratios, causing visible hue shifts during transitions (e.g., greenish tints). This can be fixed by applying hue preservation logic similar to color_fade()'s video mode: add 1 to channels that are non-zero and > 13% of max channel `(channel << 5) > maxc`. This preserves color ratios and prevents premature channel zeroing at low brightness without affecting performance at normal brightness levels.

Learnt from: DedeHai
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2026-01-13T21:23:35.514Z
Learning: In WLED, the global `paletteBlend` variable (wled.h:603) and the `WS2812FX::paletteBlend` member (FX.h:940) are duplicates without synchronization code. The global is loaded/saved in cfg.cpp and set via UI in set.cpp, but never copied to the strip member. This is the only such case in the codebase; other settings are either strip-only members (autoSegments, correctWB, cctFromRgb, isMatrix) or global-only (gammaCorrectCol/Bri/Val, blendingStyle).

Learnt from: DedeHai
Repo: wled/WLED PR: 5434
File: wled00/colors.cpp:79-83
Timestamp: 2026-03-21T11:43:34.505Z
Learning: In WLED's `color_fade()` (wled00/colors.cpp), the `video=true` path computes `maxc = (maxc>>2) + 1` and only preserves a channel if its value strictly exceeds that threshold (`r > maxc`, etc.). This means very dark non-zero channels (e.g., r==1 or r==2) can be dropped to zero in video mode. This is intentional: it prevents flickering in corner regions of gradients and is an accepted design compromise. Do not flag this as a bug in future reviews.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2026-03-15T13:21:32.008Z
Learning: In WLED code reviews (and generally), when a PR discussion involves "is this worth doing?" uncertainty about a proposed reliability, safety, or data-integrity mechanism (e.g. CRC checks, backups, power-loss protection), suggest performing a software FMEA (Failure Mode and Effects Analysis). The FMEA should: enumerate failure modes for the feared event, assess each mitigation's effectiveness per failure mode, note common-cause/common-mode failures, and rate credibility for the typical WLED use case. This was successfully used in PR `#5421` to evaluate CRC-based config file protection and was well-received by the WLED maintainers (softhack007 explicitly requested this be remembered for future reviews).

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:11.994Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with appropriate defined constants when those constants are meaningful in the context of the PR. For example, the hardcoded value 32 should be replaced with WLED_MAX_SEGNAME_LEN when it represents a segment name length limit. This improves code maintainability and reduces the risk of inconsistencies.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-12-28T14:06:48.772Z
Learning: In WLED post-commit ee9ac947, the rendering pipeline uses per-segment buffers and per-pixel bus updates. Unmapped (0xFFFF) mapping entries are now skipped in WS2812FX::show() (no “clear to black”), which can leave physical gap LEDs with stale/random colors unless they are explicitly cleared. This is a behavior change from pre-0.16 where a full physical buffer was effectively refreshed each frame.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5109
File: wled00/FX.cpp:3174-3343
Timestamp: 2025-11-27T06:33:11.436Z
Learning: WLED Ants effect (wled00/FX.cpp): The author prefers the current velocity initialization using hw_random16(1000, 5000)/5000.0f, resulting in an effective range of ~3.6–10.0 (with VELOCITY_MIN=2.0, VELOCITY_MAX=10.0), and wants the code kept as-is with comments updated to document this behavior. Avoid suggesting changes to span the full 2.0–10.0 range in future reviews.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:30.955Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with defined constants when meaningful constants exist in the codebase. For example, suggest replacing hardcoded "32" with WLED_MAX_SEGNAME_LEN if the context relates to segment name length limits.
✅ Actions performed

Full review triggered.

const int length = topSegment.length(); // physical segment length (counts all pixels in 2D segment)
const int width = topSegment.width();
const int height = topSegment.height();
const uint32_t bgColor = topSegment.colors[1]; // background color for blend modes that need it (e.g. stencil)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

TODO: remove unused variable

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

♻️ Duplicate comments (1)
wled00/FX_fcn.cpp (1)

1506-1511: ⚠️ Potential issue | 🟠 Major

Diagonal push styles still fall back to swipe behavior.

BLEND_STYLE_PUSH_TL/TR/BR/BL already get diagonal clip rects above, but this switch never assigns their pushOffsetX/Y. They still reach Lines 1521-1522 with zero offsets, so the diagonal variants do not actually push the canvas.

🛠️ Suggested fix
     switch (blendingStyle) {
       case BLEND_STYLE_PUSH_RIGHT: pushOffsetX = offsetX; break;
       case BLEND_STYLE_PUSH_LEFT:  pushOffsetX = -offsetX + nCols; break;
       case BLEND_STYLE_PUSH_DOWN:  pushOffsetY = offsetY; break;
       case BLEND_STYLE_PUSH_UP:    pushOffsetY = -offsetY + nRows; break;
+      case BLEND_STYLE_PUSH_TL:    pushOffsetX = offsetX;          pushOffsetY = offsetY; break;
+      case BLEND_STYLE_PUSH_TR:    pushOffsetX = -offsetX + nCols; pushOffsetY = offsetY; break;
+      case BLEND_STYLE_PUSH_BR:    pushOffsetX = -offsetX + nCols; pushOffsetY = -offsetY + nRows; break;
+      case BLEND_STYLE_PUSH_BL:    pushOffsetX = offsetX;          pushOffsetY = -offsetY + nRows; break;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX_fcn.cpp` around lines 1506 - 1511, The diagonal push variants
(BLEND_STYLE_PUSH_TL, BLEND_STYLE_PUSH_TR, BLEND_STYLE_PUSH_BR,
BLEND_STYLE_PUSH_BL) are never assigning pushOffsetX/pushOffsetY in the
blendingStyle switch, so they fall back to swipe behavior; update the switch in
FX_fcn.cpp to set pushOffsetX and pushOffsetY for those four diagonal cases
using the same offset logic as the cardinal directions (e.g., TL: pushOffsetX =
-offsetX + nCols; pushOffsetY = -offsetY + nRows; TR: pushOffsetX = offsetX;
pushOffsetY = -offsetY + nRows; BR: pushOffsetX = offsetX; pushOffsetY =
offsetY; BL: pushOffsetX = -offsetX + nCols; pushOffsetY = offsetY) so the later
push logic that reads pushOffsetX/pushOffsetY actually shifts the canvas for
diagonal styles.
🧹 Nitpick comments (2)
wled00/data/index.js (1)

801-801: Consider replacing the hardcoded stencil mode ID with a named constant.

Using a named constant for 16 makes future mode-ID changes safer and easier to track.

♻️ Proposed refactor
+const BLEND_MODE_STENCIL = 16;
...
-              `<option value="16" ${inst.bm==16?' selected':''}>Stencil</option>`+
+              `<option value="${BLEND_MODE_STENCIL}" ${inst.bm==BLEND_MODE_STENCIL?' selected':''}>Stencil</option>`+

Based on learnings: In WLED code reviews, when code is modified or added, look for "magic numbers" and suggest replacing them with defined constants when meaningful constants exist.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/data/index.js` at line 801, The option string uses a magic number 16
for the "Stencil" mode; replace this literal with a named constant (e.g.,
MODE_STENCIL) so mode IDs are maintainable and discoverable. Define the constant
in the module or a shared constants file, then update the template that builds
the option (the expression checking inst.bm and the option value) to use that
constant instead of 16; ensure any other places comparing or emitting mode ID 16
are updated to reference MODE_STENCIL as well.
wled00/FX_fcn.cpp (1)

1367-1388: Replace the raw blend-mode ids/count with named constants.

17 and 16 now need to stay aligned across the clamp, the fast-path switch, and the UI values. Giving them names here will make the next blend-mode addition much less fragile.

♻️ Suggested cleanup
-#define BLENDMODES  17 // number of blend modes must match "bm" in index.js, all cases must be handled in segblend() @ blendSegment()
+constexpr uint8_t BLEND_MODE_STENCIL = 16;
+constexpr uint8_t BLEND_MODE_COUNT   = 17; // must stay aligned with the UI values
...
-  const size_t blendMode = topSegment.blendMode < BLENDMODES ? topSegment.blendMode : 0; // default to top if unsupported mode
+  const size_t blendMode = topSegment.blendMode < BLEND_MODE_COUNT ? topSegment.blendMode : 0; // default to top if unsupported mode
...
-      case 16: return t ? t : b;           // stencil (use top layer if not black, else bottom)
+      case BLEND_MODE_STENCIL: return t ? t : b; // stencil (use top layer if not black, else bottom)

Based on learnings, in WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with defined constants when meaningful constants exist in the codebase.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX_fcn.cpp` around lines 1367 - 1388, Replace the magic numbers used
for blend-mode counts/ids with named constants: introduce/use a constexpr or
`#define` (e.g., BLEND_MODE_COUNT or BLEND_MODE_STENCIL_ID) and replace literal 17
and 16 in the BLENDMODES define, the clamp for topSegment.blendMode, and the
fast-path switch cases inside WS2812FX::blendSegment (references: BLENDMODES,
blendSegment, blendMode, segblend, and the switch case labeled 16). Ensure the
new constants are defined near related blend-mode enums/definitions so all
comparisons and comments (clamp fallback and "stencil" case) use the named
symbols instead of raw numbers to keep them in sync when adding new modes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wled00/FX.h`:
- Line 465: The inline comment for the member variable blendMode in FX.h lists
supported segment blending modes but omits "average", causing the comment to
diverge from actual supported modes; update the comment on uint8_t blendMode to
include "average" among the listed modes so the comment accurately reflects
supported blending options (refer to the symbol blendMode in the FX.h
struct/class when making the change).

---

Duplicate comments:
In `@wled00/FX_fcn.cpp`:
- Around line 1506-1511: The diagonal push variants (BLEND_STYLE_PUSH_TL,
BLEND_STYLE_PUSH_TR, BLEND_STYLE_PUSH_BR, BLEND_STYLE_PUSH_BL) are never
assigning pushOffsetX/pushOffsetY in the blendingStyle switch, so they fall back
to swipe behavior; update the switch in FX_fcn.cpp to set pushOffsetX and
pushOffsetY for those four diagonal cases using the same offset logic as the
cardinal directions (e.g., TL: pushOffsetX = -offsetX + nCols; pushOffsetY =
-offsetY + nRows; TR: pushOffsetX = offsetX; pushOffsetY = -offsetY + nRows; BR:
pushOffsetX = offsetX; pushOffsetY = offsetY; BL: pushOffsetX = -offsetX +
nCols; pushOffsetY = offsetY) so the later push logic that reads
pushOffsetX/pushOffsetY actually shifts the canvas for diagonal styles.

---

Nitpick comments:
In `@wled00/data/index.js`:
- Line 801: The option string uses a magic number 16 for the "Stencil" mode;
replace this literal with a named constant (e.g., MODE_STENCIL) so mode IDs are
maintainable and discoverable. Define the constant in the module or a shared
constants file, then update the template that builds the option (the expression
checking inst.bm and the option value) to use that constant instead of 16;
ensure any other places comparing or emitting mode ID 16 are updated to
reference MODE_STENCIL as well.

In `@wled00/FX_fcn.cpp`:
- Around line 1367-1388: Replace the magic numbers used for blend-mode
counts/ids with named constants: introduce/use a constexpr or `#define` (e.g.,
BLEND_MODE_COUNT or BLEND_MODE_STENCIL_ID) and replace literal 17 and 16 in the
BLENDMODES define, the clamp for topSegment.blendMode, and the fast-path switch
cases inside WS2812FX::blendSegment (references: BLENDMODES, blendSegment,
blendMode, segblend, and the switch case labeled 16). Ensure the new constants
are defined near related blend-mode enums/definitions so all comparisons and
comments (clamp fallback and "stencil" case) use the named symbols instead of
raw numbers to keep them in sync when adding new modes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ecdf17c1-7ac0-4dfb-86d9-4ef07d392c5b

📥 Commits

Reviewing files that changed from the base of the PR and between 7a635c8 and 828b6ea.

📒 Files selected for processing (4)
  • wled00/FX.h
  • wled00/FX_fcn.cpp
  • wled00/data/index.js
  • wled00/json.cpp

@DedeHai
Copy link
Collaborator Author

DedeHai commented Mar 22, 2026

@softhack007 thanks for reviewing. I am not sure those FPS improvements are still accurate with the latest implementation but I did check the blending function and it is a solid 10% faster - since the rendering "bottle neck" currently is the "pass pixels to the bus" (in my test: rendering FX: 1-2m, rendering two layers to main buffer (this PR) 3ms, rendering main buffer to two buses: 6ms
when adding several layers, there is an improvement, using a single layer it is almost insignificant when looking at the full render pipeline, i.e. it shaves off about 0.4us of a 10ms frame (rough numbers of my test cases)

coderabbitai[bot]

This comment was marked as outdated.

@DedeHai DedeHai merged commit 9cf3a7d into wled:main Mar 22, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

keep This issue will never become stale/closed automatically

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants