Skip to content
New issue

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

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

Already on GitHub? # to your account

Soap FX optimization #4543

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 65 additions & 73 deletions wled00/FX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7468,42 +7468,86 @@ static const char _data_FX_MODE_2DDISTORTIONWAVES[] PROGMEM = "Distortion Waves@
//Soap
//@Stepko
//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;
blazoncek marked this conversation as resolved.
Show resolved Hide resolved

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);
blazoncek marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}
}
Comment on lines 7470 to +7522
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);
}
}
}


uint16_t mode_2Dsoap() {
if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up

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 XY = [&](int x, int y) { return x + y * cols; };

const size_t dataSize = SEGMENT.width() * SEGMENT.height() * sizeof(uint8_t); // prevent reallocation if mirrored or grouped
const size_t segSize = SEGMENT.width() * SEGMENT.height(); // prevent reallocation if mirrored or grouped
const size_t dataSize = segSize * (sizeof(uint8_t) + sizeof(CRGB)); // pixels and noise
if (!SEGENV.allocateData(dataSize + sizeof(uint32_t)*3)) return mode_static(); //allocation failed

uint8_t *noise3d = reinterpret_cast<uint8_t*>(SEGENV.data);
uint32_t *noise32_x = reinterpret_cast<uint32_t*>(SEGENV.data + dataSize);
uint32_t *noise32_y = reinterpret_cast<uint32_t*>(SEGENV.data + dataSize + sizeof(uint32_t));
uint32_t *noise32_z = reinterpret_cast<uint32_t*>(SEGENV.data + dataSize + sizeof(uint32_t)*2);
uint8_t *noise3d = reinterpret_cast<uint8_t*>(SEGENV.data);
CRGB *pixels = reinterpret_cast<CRGB*>(SEGENV.data + segSize * sizeof(uint8_t));
blazoncek marked this conversation as resolved.
Show resolved Hide resolved
uint32_t *noisecoord = reinterpret_cast<uint32_t*>(SEGENV.data + dataSize); // x, y, z coordinates
const uint32_t scale32_x = 160000U/cols;
const uint32_t scale32_y = 160000U/rows;
const uint32_t mov = MIN(cols,rows)*(SEGMENT.speed+2)/2;
const uint8_t smoothness = MIN(250,SEGMENT.intensity); // limit as >250 produces very little changes

// init
if (SEGENV.call == 0) {
*noise32_x = hw_random();
*noise32_y = hw_random();
*noise32_z = hw_random();
} else {
*noise32_x += mov;
*noise32_y += mov;
*noise32_z += mov;
}
if (SEGENV.call == 0) for (int i = 0; i < 3; i++) noisecoord[i] = hw_random(); // init
else for (int i = 0; i < 3; i++) noisecoord[i] += mov;

for (int i = 0; i < cols; i++) {
int32_t ioffset = scale32_x * (i - cols / 2);
for (int j = 0; j < rows; j++) {
int32_t joffset = scale32_y * (j - rows / 2);
uint8_t data = inoise16(*noise32_x + ioffset, *noise32_y + joffset, *noise32_z) >> 8;
uint8_t data = inoise16(noisecoord[0] + ioffset, noisecoord[1] + joffset, noisecoord[2]) >> 8;
noise3d[XY(i,j)] = scale8(noise3d[XY(i,j)], smoothness) + scale8(data, 255 - smoothness);
}
}
Expand All @@ -7518,64 +7562,12 @@ uint16_t mode_2Dsoap() {
}
}

int zD;
int zF;
int amplitude;
int shiftX = 0; //(SEGMENT.custom1 - 128) / 4;
int shiftY = 0; //(SEGMENT.custom2 - 128) / 4;
CRGB ledsbuff[MAX(cols,rows)];

amplitude = (cols >= 16) ? (cols-8)/8 : 1;
for (int y = 0; y < rows; y++) {
int amount = ((int)noise3d[XY(0,y)] - 128) * 2 * amplitude + 256*shiftX;
int delta = abs(amount) >> 8;
int fraction = abs(amount) & 255;
for (int x = 0; x < cols; x++) {
if (amount < 0) {
zD = x - delta;
zF = zD - 1;
} else {
zD = x + delta;
zF = zD + 1;
}
CRGB PixelA = CRGB::Black;
if ((zD >= 0) && (zD < cols)) PixelA = SEGMENT.getPixelColorXY(zD, y);
else PixelA = ColorFromPalette(SEGPALETTE, ~noise3d[XY(abs(zD),y)]*3);
CRGB PixelB = CRGB::Black;
if ((zF >= 0) && (zF < cols)) PixelB = SEGMENT.getPixelColorXY(zF, y);
else PixelB = ColorFromPalette(SEGPALETTE, ~noise3d[XY(abs(zF),y)]*3);
ledsbuff[x] = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction)));
}
for (int x = 0; x < cols; x++) SEGMENT.setPixelColorXY(x, y, ledsbuff[x]);
}

amplitude = (rows >= 16) ? (rows-8)/8 : 1;
for (int x = 0; x < cols; x++) {
int amount = ((int)noise3d[XY(x,0)] - 128) * 2 * amplitude + 256*shiftY;
int delta = abs(amount) >> 8;
int fraction = abs(amount) & 255;
for (int y = 0; y < rows; y++) {
if (amount < 0) {
zD = y - delta;
zF = zD - 1;
} else {
zD = y + delta;
zF = zD + 1;
}
CRGB PixelA = CRGB::Black;
if ((zD >= 0) && (zD < rows)) PixelA = SEGMENT.getPixelColorXY(x, zD);
else PixelA = ColorFromPalette(SEGPALETTE, ~noise3d[XY(x,abs(zD))]*3);
CRGB PixelB = CRGB::Black;
if ((zF >= 0) && (zF < rows)) PixelB = SEGMENT.getPixelColorXY(x, zF);
else PixelB = ColorFromPalette(SEGPALETTE, ~noise3d[XY(x,abs(zF))]*3);
ledsbuff[y] = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction)));
}
for (int y = 0; y < rows; y++) SEGMENT.setPixelColorXY(x, y, ledsbuff[y]);
}
soapPixels(true, noise3d, pixels); // rows
soapPixels(false, noise3d, pixels); // cols

return FRAMETIME;
}
static const char _data_FX_MODE_2DSOAP[] PROGMEM = "Soap@!,Smoothness;;!;2;pal=11";
static const char _data_FX_MODE_2DSOAP[] PROGMEM = "Soap@!,Smoothness,Density;;!;2;pal=11";


//Idea from https://www.youtube.com/watch?v=HsA-6KIbgto&ab_channel=GreatScott%21
Expand Down
Loading