Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Soap FX optimization #4543

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open

Conversation

DedeHai
Copy link
Collaborator

@DedeHai DedeHai commented Feb 7, 2025

  • saves 600 bytes of flash
  • 2% speed increase

Summary by CodeRabbit

  • New Features
    • Expanded the range of lighting effects with over 50 new visualizations, including dynamic patterns, engaging animations, 2D displays, and audio-reactive modes for a richer user experience.
  • Refactor
    • Optimized performance for smoother transitions and improved overall responsiveness.

@DedeHai DedeHai added effect optimization re-working an existing feature to be faster, or use less memory labels Feb 7, 2025
@DedeHai DedeHai requested a review from blazoncek February 7, 2025 10:15
@blazoncek
Copy link
Collaborator

Please use new lambda XY() as it simplifies review and prevents errors.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Feb 7, 2025

it does, just not in the first commit ;)

wled00/FX.cpp Outdated
}
CRGB PixelA = CRGB::Black;
if ((zD >= 0) && (zD < colrow)) PixelA = isRow ? SEGMENT.getPixelColorXY(zD, i) : SEGMENT.getPixelColorXY(i, zD);
else PixelA = ColorFromPalette(SEGPALETTE, ~noise3d[isRow ? (abs(zD)%cols) + i*cols : i + (abs(zD)%rows)*cols] * 3);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I was referring to this where XY() is used to get the index of array.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I did that as a speed improvement, but can revert back.

- slight increase in code size, speed is the same but better readability.
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.

You could push it a bit further. 😉
Perhaps using std::swap()?

wled00/FX.cpp Outdated Show resolved Hide resolved
wled00/FX.cpp Outdated Show resolved Hide resolved
- moved local variables into function
- made coordinates an array
- amplitude can now be changed by user (default setting is a slight increase to original which cannot be avoided without complicated logic or default slider setting)
@blazoncek
Copy link
Collaborator

I was thinking about this to improve readability:

static void soapPixels(bool isRows, uint32_t *pixels, uint8_t *noise3d) {
  const int cols = SEG_W;
  const int rows = SEG_H;
  const auto XY = [&](int x, int y) { return (x%cols) + (y%rows) * cols; };
  const int hor = isRows ? rows : cols;
  const int ver = isRows ? cols : rows;
  const int amplitude = max(1, ((ver >= 16) ? (ver-8)/8 : 1) * (1 + SEGMENT.custom1) >> 6);

  CRGBW ledsbuff[ver];

  for (int y = 0; y < hor; y++) {
    int amount   = ((int)noise3d[isRows ? y*cols : y] - 128) * 2 * amplitude + (128 - SEGMENT.custom2)*2;
    int delta    = abs(amount) >> 8;
    int fraction = abs(amount) & 255;
    for (int x = 0; x < ver; x++) {
      int zD;
      int zF;
      if (amount < 0) {
        zD = x - delta;
        zF = zD - 1;
      } else {
        zD = x + delta;
        zF = zD + 1;
      }
      const int indxA = isRows ? XY(zD,y) : XY(y,zD);
      const int indxB = isRows ? XY(zF,y) : XY(y,zF);
      const int noisA = isRows ? XY(abs(zD),y) : XY(y,abs(zD));
      const int noisB = isRows ? XY(abs(zF),y) : XY(y,abs(zF));
      CRGB PixelA;
      CRGB PixelB;
      if ((zD >= 0) && (zD < ver)) PixelA = pixels[indxA];
      else                         PixelA = ColorFromPalette(SEGPALETTE, ~noise3d[noisA]*3);
      if ((zF >= 0) && (zF < ver)) PixelB = pixels[indxB];
      else                         PixelB = ColorFromPalette(SEGPALETTE, ~noise3d[noisB]*3);
      ledsbuff[x] = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction)));
    }
    for (int x = 0; x < ver; x++) {
      if (isRows) SEGMENT.setPixelColorXY(x, y, pixels[XY(x,y)] = ledsbuff[x]);
      else        SEGMENT.setPixelColorXY(y, x, pixels[XY(y,x)] = ledsbuff[x]);
    }
  }
}

Where pixels[] is an array of previous pixel values since getPixelColor() does not work with gaps.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Feb 9, 2025

A local buffer would also help with speed, so your call. I think you should be able to push to this PR (or merge this and update later?)
However: making the buffer CRGBW is wasteful on RAM, W is never used and CRGB is used in the function anyway so lots of back and forth conversions, better make the buffer CRGB which converts only once (in sPC).

calling the variables hor/ver and the indices x/y even though they get flipped around (hor beeing actually vertical y coordinates and y actually being x direction if doing columns) may be more confusing than helpful.

@blazoncek
Copy link
Collaborator

I hope I have not ruined it. This produced smallest footprint on my system.

wled00/FX.cpp Outdated
const auto abs = [](int x) { return x<0 ? -x : x; };
const int tRC = isRow ? rows : cols; // transpose if isRow
const int tCR = isRow ? cols : rows; // transpose if isRow
const int amplitude = 2 * ((tCR >= 16) ? (tCR-8) : 8) / (1 + ((255 - SEGMENT.custom1) >> 5));
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I had >> 5 in the beginning but found it matches less with the original look on default setting. if you don't mind that, this is better.

edit: I tested this and it does change the look as well as the original logic, on high custom1 values it looks very weird. maybe you can keep my logic using max(1, xxxxx) as it will match better with the initial
amplitude = (cols >= 16) ? (cols-8)/8 : 1;

Copy link
Collaborator

Choose a reason for hiding this comment

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

Go ahead, I did not have much time to compare now-and-then but from the top of my head this was "original" if custom1==0.
I would avoid max() or min() nd just try to find a sweet spot.

Copy link
Collaborator

@blazoncek blazoncek Feb 10, 2025

Choose a reason for hiding this comment

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

I find this the best compromise:

const int  amplitude = 1 + tCR / ((33 - SEGMENT.custom3) >> 1));

And is close to original source from @St3p40 while using slider to boost amplitude.
It may require default value of 4-8 to be equal to original.

NOTE: changed custom1 to custom3 as there is no need for 255 values.

wled00/FX.cpp Show resolved Hide resolved
wled00/FX.cpp Show resolved Hide resolved
wled00/FX.cpp Show resolved Hide resolved
@netmindz
Copy link
Member

netmindz commented Feb 9, 2025

@coderabbitai review

Copy link

coderabbitai bot commented Feb 9, 2025

✅ 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

coderabbitai bot commented Feb 9, 2025

Walkthrough

The update expands the functionality of FX.cpp by introducing a variety of new effect modes, including standard, audio-reactive, and 2D matrix-based displays. The changes involve adding new effect functions with associated metadata, reorganizing the effect data structures for improved performance and maintainability, and incorporating additional utility functions to support the new visualizations.

Changes

Files Change Summary
wled00/FX.cpp Added new effect modes (e.g., "Solid Pattern", "Tri Static Pattern", "Spots", "Glitter", "Bouncing Balls", etc.) and multiple audio-reactive effects.
wled00/FX.cpp Introduced various 2D effects (e.g., "Plasma Rotozoom", "Spaceships", "Floating Blobs", "Scrolling Text", etc.).
wled00/FX.cpp Reorganized and optimized effect data structures by incorporating function pointers and metadata strings for each effect.
wled00/FX.cpp Integrated new utility functions (e.g., sin_gap(), triwave16(), tristate_square8(), etc.) to support the implementation of the effects.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant WS2812FX
    participant EffectFunction
    User->>WS2812FX: Select new effect mode
    WS2812FX->>WS2812FX: Update effect function pointer based on selection
    WS2812FX->>EffectFunction: Invoke selected effect function
    EffectFunction-->>WS2812FX: Return computed LED pattern
    WS2812FX-->>User: Update LED output with new pattern
Loading
✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@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 (1)
wled00/FX.cpp (1)

7523-7570: Improved mode_2Dsoap implementation

The mode_2Dsoap function has been enhanced with:

  1. Better memory allocation handling
  2. More efficient data structure usage
  3. Clearer variable naming
  4. Proper const correctness

The changes improve both performance and maintainability while preserving the original functionality.

Consider adding comments explaining the magic numbers used in the calculations:

// Scale factor for noise coordinates
const uint32_t scale32_x = 160000U/cols;
const uint32_t scale32_y = 160000U/rows;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c71753 and 2473065.

📒 Files selected for processing (1)
  • wled00/FX.cpp (2 hunks)
🔇 Additional comments (5)
wled00/FX.cpp (5)

7571-7571: Updated mode metadata string

The mode metadata string has been updated to reflect the new parameters and defaults.


7900-7901: Added new 2D effects

Two new 2D effects have been added:

  1. Octopus effect
  2. Soap effect

The effects are properly integrated into the effects list and follow the established patterns.


7902-7903: Added Waving Cell effect

The Waving Cell effect has been added with proper metadata and implementation.


7904-7905: Added Akemi effect with audio support

The Akemi effect has been added with audio visualization capabilities and proper metadata.


7906-7907: End of effect definitions

The file properly closes the effect definitions section and maintains code organization.

wled00/FX.cpp Outdated
Comment on lines 7470 to 7522
//Idea from https://www.youtube.com/watch?v=DiHBgITrZck&ab_channel=StefanPetrick
// adapted for WLED by @blazoncek
// adapted for WLED by @blazoncek, optimization by @dedehai
static void soapPixels(bool isRow, uint8_t *noise3d, CRGB *pixels) {
const int cols = SEG_W;
const int rows = SEG_H;
const auto XY = [&](int x, int y) { return x + y * cols; };
const auto abs = [](int x) { return x<0 ? -x : x; };
const int tRC = isRow ? rows : cols; // transpose if isRow
const int tCR = isRow ? cols : rows; // transpose if isRow
const int amplitude = 2 * ((tCR >= 16) ? (tCR-8) : 8) / (1 + ((255 - SEGMENT.custom1) >> 5));
const int shift = 0; //(128 - SEGMENT.custom2)*2;

CRGB ledsbuff[tCR];

for (int i = 0; i < tRC; i++) {
int amount = ((int)noise3d[isRow ? i*cols : i] - 128) * amplitude + shift; // use first row/column: XY(0,i)/XY(i,0)
int delta = abs(amount) >> 8;
int fraction = abs(amount) & 255;
for (int j = 0; j < tCR; j++) {
int zD, zF;
if (amount < 0) {
zD = j - delta;
zF = zD - 1;
} else {
zD = j + delta;
zF = zD + 1;
}
int yA = abs(zD);
int yB = abs(zF);
int xA = i;
int xB = i;
if (isRow) {
std::swap(xA,yA);
std::swap(xB,yB);
}
const int indxA = XY(xA,yA);
const int indxB = XY(xB,yB);
CRGB PixelA;
CRGB PixelB;
if ((zD >= 0) && (zD < tCR)) PixelA = pixels[indxA];
else PixelA = ColorFromPalette(SEGPALETTE, ~noise3d[indxA]*3);
if ((zF >= 0) && (zF < tCR)) PixelB = pixels[indxB];
else PixelB = ColorFromPalette(SEGPALETTE, ~noise3d[indxB]*3);
ledsbuff[j] = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction)));
}
for (int j = 0; j < tCR; j++) {
CRGB c = ledsbuff[j];
if (isRow) std::swap(j,i);
SEGMENT.setPixelColorXY(i, j, pixels[XY(i,j)] = c);
if (isRow) std::swap(j,i);
}
}
}
Copy link

@coderabbitai coderabbitai bot Feb 9, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Significant optimization of soapPixels function

The soapPixels function has been optimized and refactored to:

  1. Use a lambda function for XY coordinate calculation
  2. Add helper lambda for absolute value calculation
  3. Improve readability by using more descriptive variable names
  4. Reduce code duplication by extracting common calculations
  5. Use const references where possible to avoid copies

Consider further optimizing by:

  1. Using std::abs instead of custom abs lambda
  2. Making the function static since it's only used internally
  3. Using constexpr for compile-time constants
-  const auto abs  = [](int x) { return x<0 ? -x : x; };
+  using std::abs;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
//Idea from https://www.youtube.com/watch?v=DiHBgITrZck&ab_channel=StefanPetrick
// adapted for WLED by @blazoncek
// adapted for WLED by @blazoncek, optimization by @dedehai
static void soapPixels(bool isRow, uint8_t *noise3d, CRGB *pixels) {
const int cols = SEG_W;
const int rows = SEG_H;
const auto XY = [&](int x, int y) { return x + y * cols; };
const auto abs = [](int x) { return x<0 ? -x : x; };
const int tRC = isRow ? rows : cols; // transpose if isRow
const int tCR = isRow ? cols : rows; // transpose if isRow
const int amplitude = 2 * ((tCR >= 16) ? (tCR-8) : 8) / (1 + ((255 - SEGMENT.custom1) >> 5));
const int shift = 0; //(128 - SEGMENT.custom2)*2;
CRGB ledsbuff[tCR];
for (int i = 0; i < tRC; i++) {
int amount = ((int)noise3d[isRow ? i*cols : i] - 128) * amplitude + shift; // use first row/column: XY(0,i)/XY(i,0)
int delta = abs(amount) >> 8;
int fraction = abs(amount) & 255;
for (int j = 0; j < tCR; j++) {
int zD, zF;
if (amount < 0) {
zD = j - delta;
zF = zD - 1;
} else {
zD = j + delta;
zF = zD + 1;
}
int yA = abs(zD);
int yB = abs(zF);
int xA = i;
int xB = i;
if (isRow) {
std::swap(xA,yA);
std::swap(xB,yB);
}
const int indxA = XY(xA,yA);
const int indxB = XY(xB,yB);
CRGB PixelA;
CRGB PixelB;
if ((zD >= 0) && (zD < tCR)) PixelA = pixels[indxA];
else PixelA = ColorFromPalette(SEGPALETTE, ~noise3d[indxA]*3);
if ((zF >= 0) && (zF < tCR)) PixelB = pixels[indxB];
else PixelB = ColorFromPalette(SEGPALETTE, ~noise3d[indxB]*3);
ledsbuff[j] = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction)));
}
for (int j = 0; j < tCR; j++) {
CRGB c = ledsbuff[j];
if (isRow) std::swap(j,i);
SEGMENT.setPixelColorXY(i, j, pixels[XY(i,j)] = c);
if (isRow) std::swap(j,i);
}
}
}
//Idea from https://www.youtube.com/watch?v=DiHBgITrZck&ab_channel=StefanPetrick
// adapted for WLED by @blazoncek, optimization by @dedehai
static void soapPixels(bool isRow, uint8_t *noise3d, CRGB *pixels) {
const int cols = SEG_W;
const int rows = SEG_H;
const auto XY = [&](int x, int y) { return x + y * cols; };
using std::abs;
const int tRC = isRow ? rows : cols; // transpose if isRow
const int tCR = isRow ? cols : rows; // transpose if isRow
const int amplitude = 2 * ((tCR >= 16) ? (tCR-8) : 8) / (1 + ((255 - SEGMENT.custom1) >> 5));
const int shift = 0; //(128 - SEGMENT.custom2)*2;
CRGB ledsbuff[tCR];
for (int i = 0; i < tRC; i++) {
int amount = ((int)noise3d[isRow ? i*cols : i] - 128) * amplitude + shift; // use first row/column: XY(0,i)/XY(i,0)
int delta = abs(amount) >> 8;
int fraction = abs(amount) & 255;
for (int j = 0; j < tCR; j++) {
int zD, zF;
if (amount < 0) {
zD = j - delta;
zF = zD - 1;
} else {
zD = j + delta;
zF = zD + 1;
}
int yA = abs(zD);
int yB = abs(zF);
int xA = i;
int xB = i;
if (isRow) {
std::swap(xA, yA);
std::swap(xB, yB);
}
const int indxA = XY(xA, yA);
const int indxB = XY(xB, yB);
CRGB PixelA;
CRGB PixelB;
if ((zD >= 0) && (zD < tCR)) PixelA = pixels[indxA];
else PixelA = ColorFromPalette(SEGPALETTE, ~noise3d[indxA] * 3);
if ((zF >= 0) && (zF < tCR)) PixelB = pixels[indxB];
else PixelB = ColorFromPalette(SEGPALETTE, ~noise3d[indxB] * 3);
ledsbuff[j] = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction)));
}
for (int j = 0; j < tCR; j++) {
CRGB c = ledsbuff[j];
if (isRow) std::swap(j, i);
SEGMENT.setPixelColorXY(i, j, pixels[XY(i, j)] = c);
if (isRow) std::swap(j, i);
}
}
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

abs lambda produces smaller code than std::abs

Copy link

Choose a reason for hiding this comment

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

@blazoncek Ah, thank you for the correction! I'll retract my suggestion about std::abs.


✏️ Learnings added
Learnt from: blazoncek
PR: wled-dev/WLED#4543
File: wled00/FX.cpp:7470-7522
Timestamp: 2025-02-10T06:05:54.253Z
Learning: In WLED, using a lambda for abs() function (e.g. [](int x){return x<0 ? -x : x;}) produces smaller code size compared to std::abs and is preferred for optimization.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link

@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.cpp (3)

7524-7570: Improved mode_2Dsoap implementation

The mode_2Dsoap function has been optimized with:

  1. Better memory allocation handling
  2. Clearer variable naming
  3. Improved data structure organization
  4. Added const correctness

Consider further improvements:

-  const size_t segSize = SEGMENT.width() * SEGMENT.height(); 
-  const size_t dataSize = segSize * (sizeof(uint8_t) + sizeof(CRGB));
+  // Pre-calculate sizes to avoid repeated multiplication
+  const size_t segSize = static_cast<size_t>(cols) * rows;
+  const size_t dataSize = segSize * (sizeof(uint8_t) + sizeof(CRGB));

-  uint8_t  *noise3d    = reinterpret_cast<uint8_t*>(SEGENV.data);
-  CRGB     *pixels     = reinterpret_cast<CRGB*>(SEGENV.data + segSize * sizeof(uint8_t));
+  // Use structured binding for cleaner data access
+  struct SoapData {
+    uint8_t noise3d[]; 
+    CRGB pixels[];
+    uint32_t coords[3];
+  };
+  auto* data = reinterpret_cast<SoapData*>(SEGENV.data);

7577-7629: Optimized mode_2Doctopus implementation

The mode_2Doctopus function has been improved with:

  1. Better memory management
  2. Cleaner coordinate calculations
  3. Improved palette handling
  4. Added const correctness

Consider adding:

+  // Pre-calculate common values
+  const int C_X = (cols / 2) + ((SEGMENT.custom1 - 128)*cols)/255;
+  const int C_Y = (rows / 2) + ((SEGMENT.custom2 - 128)*rows)/255;
+  const float t = strip.now * (SEGMENT.speed + 1) / 8.0f;

+  // Cache palette colors
+  CRGB colorCache[256];
+  for (int i = 0; i < 256; i++) {
+    colorCache[i] = ColorFromPalette(SEGPALETTE, i);
+  }

7635-7656: Optimized mode_2Dwavingcell implementation

The function could be optimized by:

-  uint32_t t = (strip.now*(SEGMENT.speed + 1))>>3;
-  uint32_t aX = SEGMENT.custom1/16 + 9;
-  uint32_t aY = SEGMENT.custom2/16 + 1;
-  uint32_t aZ = SEGMENT.custom3 + 1;

+  // Pre-calculate constants
+  const uint32_t t = (strip.now*(SEGMENT.speed + 1))>>3;
+  const uint32_t aX = SEGMENT.custom1/16 + 9;
+  const uint32_t aY = SEGMENT.custom2/16 + 1;
+  const uint32_t aZ = SEGMENT.custom3 + 1;
+  
+  // Cache palette colors
+  CRGB colorCache[256];
+  for (int i = 0; i < 256; i++) {
+    colorCache[i] = ColorFromPalette(SEGPALETTE, i);
+  }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2cc7366 and bdec873.

📒 Files selected for processing (1)
  • wled00/FX.cpp (2 hunks)
🧰 Additional context used
📓 Learnings (1)
wled00/FX.cpp (1)

undefined

<retrieved_learning>
Learnt from: blazoncek
PR: #4543
File: wled00/FX.cpp:7470-7522
Timestamp: 2025-02-10T06:05:54.513Z
Learning: In WLED, using a lambda for abs() function (e.g. [](int x){return x<0 ? -x : x;}) produces smaller code size compared to std::abs and is preferred for optimization.
</retrieved_learning>

⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: wled_build / Build Enviornments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Enviornments (esp32s3dev_8MB_opi)
  • GitHub Check: wled_build / Build Enviornments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Enviornments (esp32dev_V4)
  • GitHub Check: wled_build / Build Enviornments (esp8266_2m_160)
  • GitHub Check: wled_build / Build Enviornments (esp8266_2m)
🔇 Additional comments (3)
wled00/FX.cpp (3)

7573-7574: Improved code documentation

Good addition of source attribution and credit to original author.


7669-7686: Improved addEffect implementation

Good implementation of the addEffect function with:

  1. Proper bounds checking
  2. Clear error handling
  3. Support for finding empty slots
  4. Protection against overwriting existing effects

7688-7907: Comprehensive effect registration

Well-organized setupEffectData function that:

  1. Properly initializes all effects
  2. Groups effects logically (1D, 2D, audio)
  3. Uses clear naming conventions
  4. Maintains proper ordering

wled00/FX.cpp Outdated
Comment on lines 7470 to 7522
//Idea from https://www.youtube.com/watch?v=DiHBgITrZck&ab_channel=StefanPetrick
// adapted for WLED by @blazoncek
// adapted for WLED by @blazoncek, optimization by @dedehai
static void soapPixels(bool isRow, uint8_t *noise3d, CRGB *pixels) {
const int cols = SEG_W;
const int rows = SEG_H;
const auto XY = [&](int x, int y) { return (x%cols) + (y%rows) * cols; };
const auto abs = [](int x) { return x<0 ? -x : x; };
const int tRC = isRow ? rows : cols; // transpose if isRow
const int tCR = isRow ? cols : rows; // transpose if isRow
const int amplitude = max(1, (tCR - 8) >> 3) * (1 + (SEGMENT.custom1 >> 5));
const int shift = 0; //(128 - SEGMENT.custom2)*2;

CRGB ledsbuff[tCR];

for (int i = 0; i < tRC; i++) {
int amount = ((int)noise3d[isRow ? i*cols : i] - 128) * amplitude + shift; // use first row/column: XY(0,i)/XY(i,0)
int delta = abs(amount) >> 8;
int fraction = abs(amount) & 255;
for (int j = 0; j < tCR; j++) {
int zD, zF;
if (amount < 0) {
zD = j - delta;
zF = zD - 1;
} else {
zD = j + delta;
zF = zD + 1;
}
int yA = abs(zD);
int yB = abs(zF);
int xA = i;
int xB = i;
if (isRow) {
std::swap(xA,yA);
std::swap(xB,yB);
}
const int indxA = XY(xA,yA);
const int indxB = XY(xB,yB);
CRGB PixelA;
CRGB PixelB;
if ((zD >= 0) && (zD < tCR)) PixelA = pixels[indxA];
else PixelA = ColorFromPalette(SEGPALETTE, ~noise3d[indxA]*3);
if ((zF >= 0) && (zF < tCR)) PixelB = pixels[indxB];
else PixelB = ColorFromPalette(SEGPALETTE, ~noise3d[indxB]*3);
ledsbuff[j] = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction)));
}
for (int j = 0; j < tCR; j++) {
CRGB c = ledsbuff[j];
if (isRow) std::swap(j,i);
SEGMENT.setPixelColorXY(i, j, pixels[XY(i,j)] = c);
if (isRow) std::swap(j,i);
}
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Significant optimization of soapPixels function

The soapPixels function has been optimized and refactored to:

  1. Use a lambda function for XY coordinate calculation
  2. Add helper lambda for absolute value calculation
  3. Improve readability by using more descriptive variable names
  4. Reduce code duplication by extracting common calculations
  5. Use const references where possible to avoid copies

The function could be further optimized by:

  1. Using a local buffer for intermediate results to avoid repeated pixel reads/writes
  2. Avoiding repeated XY calculations by caching results
  3. Reducing branching in the inner loop

Here's a suggested optimization:

-  CRGB PixelA;
-  CRGB PixelB;
-  if ((zD >= 0) && (zD < tCR)) PixelA = pixels[indxA];
-  else                         PixelA = ColorFromPalette(SEGPALETTE, ~noise3d[indxA]*3);
-  if ((zF >= 0) && (zF < tCR)) PixelB = pixels[indxB];
-  else                         PixelB = ColorFromPalette(SEGPALETTE, ~noise3d[indxB]*3);
-  ledsbuff[j] = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction)));

+  // Cache palette lookups
+  CRGB PixelA = (zD >= 0 && zD < tCR) ? pixels[indxA] : ColorFromPalette(SEGPALETTE, ~noise3d[indxA]*3);
+  CRGB PixelB = (zF >= 0 && zF < tCR) ? pixels[indxB] : ColorFromPalette(SEGPALETTE, ~noise3d[indxB]*3);
+  
+  // Pre-calculate scale factors
+  uint8_t scaleA = ease8InOutApprox(255 - fraction);
+  uint8_t scaleB = ease8InOutApprox(fraction);
+  
+  // Blend in one step
+  ledsbuff[j] = PixelA.nscale8(scaleA) + PixelB.nscale8(scaleB);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
//Idea from https://www.youtube.com/watch?v=DiHBgITrZck&ab_channel=StefanPetrick
// adapted for WLED by @blazoncek
// adapted for WLED by @blazoncek, optimization by @dedehai
static void soapPixels(bool isRow, uint8_t *noise3d, CRGB *pixels) {
const int cols = SEG_W;
const int rows = SEG_H;
const auto XY = [&](int x, int y) { return (x%cols) + (y%rows) * cols; };
const auto abs = [](int x) { return x<0 ? -x : x; };
const int tRC = isRow ? rows : cols; // transpose if isRow
const int tCR = isRow ? cols : rows; // transpose if isRow
const int amplitude = max(1, (tCR - 8) >> 3) * (1 + (SEGMENT.custom1 >> 5));
const int shift = 0; //(128 - SEGMENT.custom2)*2;
CRGB ledsbuff[tCR];
for (int i = 0; i < tRC; i++) {
int amount = ((int)noise3d[isRow ? i*cols : i] - 128) * amplitude + shift; // use first row/column: XY(0,i)/XY(i,0)
int delta = abs(amount) >> 8;
int fraction = abs(amount) & 255;
for (int j = 0; j < tCR; j++) {
int zD, zF;
if (amount < 0) {
zD = j - delta;
zF = zD - 1;
} else {
zD = j + delta;
zF = zD + 1;
}
int yA = abs(zD);
int yB = abs(zF);
int xA = i;
int xB = i;
if (isRow) {
std::swap(xA,yA);
std::swap(xB,yB);
}
const int indxA = XY(xA,yA);
const int indxB = XY(xB,yB);
CRGB PixelA;
CRGB PixelB;
if ((zD >= 0) && (zD < tCR)) PixelA = pixels[indxA];
else PixelA = ColorFromPalette(SEGPALETTE, ~noise3d[indxA]*3);
if ((zF >= 0) && (zF < tCR)) PixelB = pixels[indxB];
else PixelB = ColorFromPalette(SEGPALETTE, ~noise3d[indxB]*3);
ledsbuff[j] = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction)));
}
for (int j = 0; j < tCR; j++) {
CRGB c = ledsbuff[j];
if (isRow) std::swap(j,i);
SEGMENT.setPixelColorXY(i, j, pixels[XY(i,j)] = c);
if (isRow) std::swap(j,i);
}
}
}
static void soapPixels(bool isRow, uint8_t *noise3d, CRGB *pixels) {
const int cols = SEG_W;
const int rows = SEG_H;
const auto XY = [&](int x, int y) { return (x % cols) + (y % rows) * cols; };
const auto abs = [](int x) { return x < 0 ? -x : x; };
const int tRC = isRow ? rows : cols; // transpose if isRow
const int tCR = isRow ? cols : rows; // transpose if isRow
const int amplitude = max(1, (tCR - 8) >> 3) * (1 + (SEGMENT.custom1 >> 5));
const int shift = 0; //(128 - SEGMENT.custom2)*2;
CRGB ledsbuff[tCR];
for (int i = 0; i < tRC; i++) {
int amount = ((int)noise3d[isRow ? i * cols : i] - 128) * amplitude + shift; // use first row/column: XY(0,i)/XY(i,0)
int delta = abs(amount) >> 8;
int fraction = abs(amount) & 255;
for (int j = 0; j < tCR; j++) {
int zD, zF;
if (amount < 0) {
zD = j - delta;
zF = zD - 1;
} else {
zD = j + delta;
zF = zD + 1;
}
int yA = abs(zD);
int yB = abs(zF);
int xA = i;
int xB = i;
if (isRow) {
std::swap(xA, yA);
std::swap(xB, yB);
}
const int indxA = XY(xA, yA);
const int indxB = XY(xB, yB);
// Cache palette lookups
CRGB PixelA = (zD >= 0 && zD < tCR) ? pixels[indxA] : ColorFromPalette(SEGPALETTE, ~noise3d[indxA] * 3);
CRGB PixelB = (zF >= 0 && zF < tCR) ? pixels[indxB] : ColorFromPalette(SEGPALETTE, ~noise3d[indxB] * 3);
// Pre-calculate scale factors
uint8_t scaleA = ease8InOutApprox(255 - fraction);
uint8_t scaleB = ease8InOutApprox(fraction);
// Blend in one step
ledsbuff[j] = PixelA.nscale8(scaleA) + PixelB.nscale8(scaleB);
}
for (int j = 0; j < tCR; j++) {
CRGB c = ledsbuff[j];
if (isRow) std::swap(j, i);
SEGMENT.setPixelColorXY(i, j, pixels[XY(i, j)] = c);
if (isRow) std::swap(j, i);
}
}
}

wled00/FX.cpp Outdated
@@ -7472,11 +7472,11 @@ static const char _data_FX_MODE_2DDISTORTIONWAVES[] PROGMEM = "Distortion Waves@
static void soapPixels(bool isRow, uint8_t *noise3d, CRGB *pixels) {
const int cols = SEG_W;
const int rows = SEG_H;
const auto XY = [&](int x, int y) { return x + y * cols; };
const auto XY = [&](int x, int y) { return (x%cols) + (y%rows) * cols; };
Copy link
Collaborator

Choose a reason for hiding this comment

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

I was too zealous with optimisations. Apparently. 😄
Though I saw glitches but couldn't pin-point where I introduced them.

To keep optimisation, could xA & xB be modulo-ed instead?

Copy link
Collaborator Author

@DedeHai DedeHai Feb 10, 2025

Choose a reason for hiding this comment

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

probably (which is what I did in the orignal code, before bringing XY back) I will check.
edit:
I just threw this code in this morning before work to fix the appearance, needs more testing.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This will do it:

      int yA = abs(zD)%tCR;
      int yB = abs(zF)%tCR;

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Though I saw glitches but couldn't pin-point where I introduced them.

I saw the exact same glitches previously while optimizing but could not remember how I fixed them so I asked chatGPT to compare the two versions and the changed XY function popped up, which I had completely missed.

Copy link

@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 (1)
wled00/FX.cpp (1)

7573-7574: Add missing documentation for mode_2Dsoap parameters

The mode description string should include documentation for the custom parameters.

-static const char _data_FX_MODE_2DSOAP[] PROGMEM = "Soap@!,Smoothness,Density;;!;2;pal=11";
+static const char _data_FX_MODE_2DSOAP[] PROGMEM = "Soap@Speed,Smoothness,Density;;!;2;pal=11";
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bdec873 and aba736c.

📒 Files selected for processing (1)
  • wled00/FX.cpp (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
wled00/FX.cpp (1)

undefined

<retrieved_learning>
Learnt from: blazoncek
PR: #4543
File: wled00/FX.cpp:7470-7522
Timestamp: 2025-02-10T06:05:54.513Z
Learning: In WLED, using a lambda for abs() function (e.g. [](int x){return x<0 ? -x : x;}) produces smaller code size compared to std::abs and is preferred for optimization.
</retrieved_learning>

⏰ Context from checks skipped due to timeout of 90000ms (11)
  • GitHub Check: wled_build / Build Enviornments (esp32_wrover)
  • GitHub Check: wled_build / Build Enviornments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Enviornments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Enviornments (esp32c3dev)
  • GitHub Check: wled_build / Build Enviornments (esp32_eth)
  • GitHub Check: wled_build / Build Enviornments (esp32dev_V4)
  • GitHub Check: wled_build / Build Enviornments (esp32dev)
  • GitHub Check: wled_build / Build Enviornments (esp01_1m_full_compat)
  • GitHub Check: wled_build / Build Enviornments (esp01_1m_full_160)
  • GitHub Check: wled_build / Build Enviornments (esp01_1m_full)
  • GitHub Check: wled_build / Build Enviornments (esp8266_2m)
🔇 Additional comments (1)
wled00/FX.cpp (1)

7524-7570: LGTM! Well structured mode_2Dsoap function

The mode_2Dsoap function is well organized with:

  1. Proper allocation checks
  2. Clear variable naming
  3. Efficient data structure reuse
  4. Good use of helper functions

Comment on lines 7470 to +7522
//Idea from https://www.youtube.com/watch?v=DiHBgITrZck&ab_channel=StefanPetrick
// adapted for WLED by @blazoncek
// adapted for WLED by @blazoncek, optimization by @dedehai
static void soapPixels(bool isRow, uint8_t *noise3d, CRGB *pixels) {
const int cols = SEG_W;
const int rows = SEG_H;
const auto XY = [&](int x, int y) { return x + y * cols; };
const auto abs = [](int x) { return x<0 ? -x : x; };
const int tRC = isRow ? rows : cols; // transpose if isRow
const int tCR = isRow ? cols : rows; // transpose if isRow
const int amplitude = max(1, (tCR - 8) >> 3) * (1 + (SEGMENT.custom1 >> 5));
const int shift = 0; //(128 - SEGMENT.custom2)*2;

CRGB ledsbuff[tCR];

for (int i = 0; i < tRC; i++) {
int amount = ((int)noise3d[isRow ? i*cols : i] - 128) * amplitude + shift; // use first row/column: XY(0,i)/XY(i,0)
int delta = abs(amount) >> 8;
int fraction = abs(amount) & 255;
for (int j = 0; j < tCR; j++) {
int zD, zF;
if (amount < 0) {
zD = j - delta;
zF = zD - 1;
} else {
zD = j + delta;
zF = zD + 1;
}
int yA = abs(zD)%tCR;
int yB = abs(zF)%tCR;
int xA = i;
int xB = i;
if (isRow) {
std::swap(xA,yA);
std::swap(xB,yB);
}
const int indxA = XY(xA,yA);
const int indxB = XY(xB,yB);
CRGB PixelA;
CRGB PixelB;
if ((zD >= 0) && (zD < tCR)) PixelA = pixels[indxA];
else PixelA = ColorFromPalette(SEGPALETTE, ~noise3d[indxA]*3);
if ((zF >= 0) && (zF < tCR)) PixelB = pixels[indxB];
else PixelB = ColorFromPalette(SEGPALETTE, ~noise3d[indxB]*3);
ledsbuff[j] = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction)));
}
for (int j = 0; j < tCR; j++) {
CRGB c = ledsbuff[j];
if (isRow) std::swap(j,i);
SEGMENT.setPixelColorXY(i, j, pixels[XY(i,j)] = c);
if (isRow) std::swap(j,i);
}
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Significant optimization of soapPixels function

The soapPixels function has been optimized and refactored to:

  1. Use a lambda function for XY coordinate calculation
  2. Add helper lambda for absolute value calculation
  3. Improve readability by using more descriptive variable names
  4. Reduce code duplication by extracting common calculations
  5. Use const references where possible to avoid copies

The function could be further optimized by:

  1. Using a local buffer for intermediate results to avoid repeated pixel reads/writes
  2. Avoiding repeated XY calculations by caching results
  3. Reducing branching in the inner loop

Here's a suggested optimization:

-  CRGB PixelA;
-  CRGB PixelB;
-  if ((zD >= 0) && (zD < tCR)) PixelA = pixels[indxA];
-  else                         PixelA = ColorFromPalette(SEGPALETTE, ~noise3d[indxA]*3);
-  if ((zF >= 0) && (zF < tCR)) PixelB = pixels[indxB];
-  else                         PixelB = ColorFromPalette(SEGPALETTE, ~noise3d[indxB]*3);
-  ledsbuff[j] = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction)));

+  // Cache palette lookups
+  CRGB PixelA = (zD >= 0 && zD < tCR) ? pixels[indxA] : ColorFromPalette(SEGPALETTE, ~noise3d[indxA]*3);
+  CRGB PixelB = (zF >= 0 && zF < tCR) ? pixels[indxB] : ColorFromPalette(SEGPALETTE, ~noise3d[indxB]*3);
+  
+  // Pre-calculate scale factors
+  uint8_t scaleA = ease8InOutApprox(255 - fraction);
+  uint8_t scaleB = ease8InOutApprox(fraction);
+  
+  // Blend in one step
+  ledsbuff[j] = PixelA.nscale8(scaleA) + PixelB.nscale8(scaleB);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
//Idea from https://www.youtube.com/watch?v=DiHBgITrZck&ab_channel=StefanPetrick
// adapted for WLED by @blazoncek
// adapted for WLED by @blazoncek, optimization by @dedehai
static void soapPixels(bool isRow, uint8_t *noise3d, CRGB *pixels) {
const int cols = SEG_W;
const int rows = SEG_H;
const auto XY = [&](int x, int y) { return x + y * cols; };
const auto abs = [](int x) { return x<0 ? -x : x; };
const int tRC = isRow ? rows : cols; // transpose if isRow
const int tCR = isRow ? cols : rows; // transpose if isRow
const int amplitude = max(1, (tCR - 8) >> 3) * (1 + (SEGMENT.custom1 >> 5));
const int shift = 0; //(128 - SEGMENT.custom2)*2;
CRGB ledsbuff[tCR];
for (int i = 0; i < tRC; i++) {
int amount = ((int)noise3d[isRow ? i*cols : i] - 128) * amplitude + shift; // use first row/column: XY(0,i)/XY(i,0)
int delta = abs(amount) >> 8;
int fraction = abs(amount) & 255;
for (int j = 0; j < tCR; j++) {
int zD, zF;
if (amount < 0) {
zD = j - delta;
zF = zD - 1;
} else {
zD = j + delta;
zF = zD + 1;
}
int yA = abs(zD)%tCR;
int yB = abs(zF)%tCR;
int xA = i;
int xB = i;
if (isRow) {
std::swap(xA,yA);
std::swap(xB,yB);
}
const int indxA = XY(xA,yA);
const int indxB = XY(xB,yB);
CRGB PixelA;
CRGB PixelB;
if ((zD >= 0) && (zD < tCR)) PixelA = pixels[indxA];
else PixelA = ColorFromPalette(SEGPALETTE, ~noise3d[indxA]*3);
if ((zF >= 0) && (zF < tCR)) PixelB = pixels[indxB];
else PixelB = ColorFromPalette(SEGPALETTE, ~noise3d[indxB]*3);
ledsbuff[j] = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction)));
}
for (int j = 0; j < tCR; j++) {
CRGB c = ledsbuff[j];
if (isRow) std::swap(j,i);
SEGMENT.setPixelColorXY(i, j, pixels[XY(i,j)] = c);
if (isRow) std::swap(j,i);
}
}
}
//Idea from https://www.youtube.com/watch?v=DiHBgITrZck&ab_channel=StefanPetrick
// adapted for WLED by @blazoncek, optimization by @dedehai
static void soapPixels(bool isRow, uint8_t *noise3d, CRGB *pixels) {
const int cols = SEG_W;
const int rows = SEG_H;
const auto XY = [&](int x, int y) { return x + y * cols; };
const auto abs = [](int x) { return x < 0 ? -x : x; };
const int tRC = isRow ? rows : cols; // transpose if isRow
const int tCR = isRow ? cols : rows; // transpose if isRow
const int amplitude = max(1, (tCR - 8) >> 3) * (1 + (SEGMENT.custom1 >> 5));
const int shift = 0; //(128 - SEGMENT.custom2)*2;
CRGB ledsbuff[tCR];
for (int i = 0; i < tRC; i++) {
int amount = ((int)noise3d[isRow ? i * cols : i] - 128) * amplitude + shift; // use first row/column: XY(0,i)/XY(i,0)
int delta = abs(amount) >> 8;
int fraction = abs(amount) & 255;
for (int j = 0; j < tCR; j++) {
int zD, zF;
if (amount < 0) {
zD = j - delta;
zF = zD - 1;
} else {
zD = j + delta;
zF = zD + 1;
}
int yA = abs(zD) % tCR;
int yB = abs(zF) % tCR;
int xA = i;
int xB = i;
if (isRow) {
std::swap(xA, yA);
std::swap(xB, yB);
}
const int indxA = XY(xA, yA);
const int indxB = XY(xB, yB);
// Cache palette lookups
CRGB PixelA = (zD >= 0 && zD < tCR) ? pixels[indxA] : ColorFromPalette(SEGPALETTE, ~noise3d[indxA] * 3);
CRGB PixelB = (zF >= 0 && zF < tCR) ? pixels[indxB] : ColorFromPalette(SEGPALETTE, ~noise3d[indxB] * 3);
// Pre-calculate scale factors
uint8_t scaleA = ease8InOutApprox(255 - fraction);
uint8_t scaleB = ease8InOutApprox(fraction);
// Blend in one step
ledsbuff[j] = PixelA.nscale8(scaleA) + PixelB.nscale8(scaleB);
}
for (int j = 0; j < tCR; j++) {
CRGB c = ledsbuff[j];
if (isRow) std::swap(j, i);
SEGMENT.setPixelColorXY(i, j, pixels[XY(i, j)] = c);
if (isRow) std::swap(j, i);
}
}
}

@DedeHai
Copy link
Collaborator Author

DedeHai commented Feb 10, 2025

that AI needs to be put on a leash ;)

that said, nothing more to add from my side, I briefly tested it on 32x32 and it looks good now. IMHO ready to merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
effect optimization re-working an existing feature to be faster, or use less memory
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants