LED animation rendering: framerate drops over time significantly - what causes this?

StefanPetrick

Well-known member
Hi,

I'm rewriting my Processing simulation code for Arduino + FastLED. The animation runs fine and looks as expected - but is way slower than I'd expect on a small 16x16 matrix. I guess I made a silly mistake and I#m too blind to see it.

My guess was: Is it possible, that sinf() gets really slow with (very) large angles? I tryed to avoid it by using fmodf() before, but no change.

The (in my eyes) critical part of the code is here, it gets called for every single pixel:

Code:
// calculate distance and angle of the point relative to
// the origin described by center_x & center_y

void get_polar_values() {

  // calculate the cartesian distances (deltas) from polar origin point 
  // aka visual rotation center
  float dx = x - center_x;
  float dy = y - center_y;
  
  // calculate distance between current point & polar origin
  // it's hypotf(), length of the origin vector, pytharorean theroem
  dist = hypotf(dx, dy);   
  
  // calculate the angle 
  // (where around the polar origin is the current point?)
  angle = atan2f(dy, dx);           
  
}


// convert polar coordinates back to cartesian 
// & render noise value there

uint8_t render_pixel() {

  // convert polar coordinates back to cartesian ones
  float newx = (offset_x + center_x - (cosf(newangle) * newdist)) * scale_x;
  float newy = (offset_y + center_y - (sinf(newangle) * newdist)) * scale_y;
  
  // render noisevalue at this new cartesian point
  uint16_t noise_field_value = inoise16(newx, newy, z);
  
  // A) enhance histogram (improve contrast) by setting the black and white point
  // B) scale the result to a 0-255 range 
  // it's the contrast boosting & the "colormapping" (technically brightness mapping) 

  if (noise_field_value<30000) noise_field_value = 30000;
  if (noise_field_value>50000) noise_field_value = 50000;
  uint8_t value = map(noise_field_value, 30000, 50000, 0, 255);
  return value;
  
}

It runs on a Teensy 3.6 with 256 LEDs at the beginning @240fps and drops within minutes down to 45fps with only 3 animation layers. Both numbers feel just not right. I suspect I do something stupid I'm not aware of. I use no lookup table for the polar data, because later I want to move the polar origin during runtime, too.


Here the complete code for context what I'm doing:

Code:
// (c) by Stefan Petrick 2023

#include <FastLED.h>

#define WIDTH 16
#define HEIGHT 16
#define NUM_LEDS ((WIDTH) * (HEIGHT))

CRGB leds[WIDTH*HEIGHT];

float c, d, e, f, time;                 // some timedependant counters
float newdist, newangle;           // parameters for reconstruction
float z;                                    // 3d dimension for the 3d noise function 
float offset_x, offset_y;            // wanna shift the cartesians during runtime?
float scale_x, scale_y;              // cartesian scaling in 2 dimensions                          
float dist, angle;                      // the actual polar coordinates

int x, y;                                            // the cartesian coordiantes
int num_x = WIDTH;                         // horizontal pixel count
int num_y = HEIGHT;                         // vertical pixel count
  

float center_x = (num_x / 2) - 0.5;   // the reference point for polar coordinates
float center_y = (num_y / 2) - 0.5;   // (can also be outside of the actual xy matrix)

void setup() {
 
  Serial.begin(115200);
  FastLED.addLeds<APA102, 33, 32, BGR, DATA_RATE_MHZ(12)>(leds, 256);

}

void loop() {

  // set timers

  time = millis();         // save the current running time
  time = time * 0.5 ;        // global anaimation speed (higher value = faster)
  c = time / 400;        // speeds of the subtimers (smaller value = faster)
  d = time / 800; 
  e = time / 600; 
 
  // calculate whats constant in the current frame

  center_x = (num_x / 2) - 0.5;   // the reference point for polar coordinates
  center_y = (num_y / 2) - 0.5;

  
  for (x = 0; x < num_x; x++) {
    for (y = 0; y < num_y; y++) {


      // layer No 1: RED------------------------------------------------------------- 
      
      // calculate distance and angle of the point relative to
      // the origin described by center_x & center_y
      
      get_polar_values();

      // set all parameters for the first layer (red)

      scale_x = 5000;
      scale_y = 5000;
      newangle = angle * 3 + c - dist/4;
      newdist = dist;
      z = c * 10000;
      offset_x = 0;
      offset_y = c*5;

      // convert polar coordinates back to cartesian 
      // & render noise value there
      
      byte show1 = render_pixel();

      // layer 2: Green

      scale_x = 6000;
      scale_y = 6000;
      newangle = angle * 3 + c*1.1 - dist/4;
      newdist = dist;
      z = c * 12000;
      offset_x = 0;
      offset_y = d*5;

      // convert polar coordinates back to cartesian 
      // & render noise value there
      
      byte show2 = render_pixel();

      // layer 3: blue

      scale_x = 7000;
      scale_y = 7000;
      newangle = angle * 3 + c*1.2 - dist/4;
      newdist = dist;
      z = c * 13000;
      offset_x = 0;
      offset_y = e*5;

      // convert polar coordinates back to cartesian 
      // & render noise value there
      
      byte show3 = render_pixel();
      

      CRGB color = CRGB(show1,show2,show3);
      leds[XY(x, y)] = color;

    }
  }

  adjust_gamma();
  FastLED.show();
  
  EVERY_N_MILLIS(100) Serial.println(FastLED.getFPS());
}




void get_polar_values() {

  // calculate the cartesian distances (deltas) from polar origin point 
  // aka visual rotation center
  float dx = x - center_x;
  float dy = y - center_y;
  
  // calculate distance between current point & polar origin
  // it's hypotf(), length of the origin vector, pytharorean theroem
  //dist = sqrt((dx*dx)+(dy*dy));
  dist = hypotf(dx, dy);   
  
  // calculate the angle 
  // (where around the polar origin is the current point?)
  angle = atan2f(dy, dx);           
  
  // done, that's all we need
}



uint8_t render_pixel() {

  // convert polar coordinates back to cartesian ones
  
  fmodf(newangle, 2 * PI);  // no solution to the fps decline, but slower to begin with


  float newx = (offset_x + center_x - (cosf(newangle) * newdist)) * scale_x;
  float newy = (offset_y + center_y - (sinf(newangle) * newdist)) * scale_y;
 
  // render noisevalue at this new cartesian point
  uint16_t noise_field_value = inoise16(newx, newy, z);
  
  // A) enhance histogram (improve contrast) by setting the black and white point
  // B) scale the result to a 0-255 range 
  // it's the contrast boosting & the "colormapping" (technically brightness mapping) 

  // #################### not sure if the following 3 lines are unperformant?!
  // #################### do the datatypes make sense?

  if (noise_field_value<30000) noise_field_value = 30000;
  if (noise_field_value>50000) noise_field_value = 50000;
  uint8_t value = map(noise_field_value, 30000, 50000, 0, 255);
  return value;
  
  // done, we've just rendered one color value for one single pixel
}


uint16_t XY(uint8_t x, uint8_t y) {
  //if (x >= WIDTH) return NUM_LEDS;
  //if (y >= HEIGHT) return NUM_LEDS;
  if (y & 1)
    return (y + 1) * WIDTH - 1 - x;
  else
    return y * WIDTH + x;
}

void adjust_gamma()
{
  for (uint16_t i = 0; i < NUM_LEDS; i++)
  {
    leds[i].r = dim8_video(leds[i].r);
    leds[i].g = dim8_video(leds[i].g);
    leds[i].b = dim8_video(leds[i].b);

  }
}

Any hint, comment or roast is appreciated! I'm a bit lost.
 
Last edited:
Frames per second over time

Bildschirmfoto 2023-03-11 um 17.40.34.jpg

I posted a video of the animation here on Reddit.
 
Last edited:
Found the problem, solved. FPS in the stable 400s now. Good.

Will explain the solution and final result when it's done.
 
Last edited:
Just a short video showing the current state. I reconsidered what is really required to calculate for each single pixel and what can be done just once per frame. Also fixed the collapsing framerate - sinf and cosf become really slow when fed with ever increasing angles.

Btw, behind the paper diffusor are only 16x16 LEDs, I'm really looking forward to seeing this stuff on high resolution setups with 2k or more LEDs! And please forgive my underwelming webcam, in reality the dynamic range is better.


 
Last edited:
Back
Top