blendMode framerate performance issues

I have a large project that uses blendMode(SCREEN). I call the blendMode() function once in setup(). I am using the FX2D renderer, but the issue also presents itself in P2D mode as well.

On my Windows 10 pc, the project runs fine at 60fps. The exact same project runs around 1-5 fps when compiled and run on my Mac at home. If I remove the blendMode directive, the project runs at 60fps on the Mac.

I have also noticed similar projects slow down to crawl when I attempt to use blendMode(). These are all giant projects so I canā€™t really post the code. I have yet to build a simple example that exhibits the same problem.

Can someone help me understand what might be happening? Thanks!

My experience with blendMode is that it often slows down my sketches. This isnā€™t only with Processing thoughā€“ the same happens when Iā€™m blending layers in Photoshop. The size of the stage, the amount of details, and gradients are some examples that greatly influence the performance.

Itā€™s just a guess, but I think that youā€™re more likely to win speed by optimizing your code. Or perhaps there is way to write a function that simulates the blend mode, but is somewhat a bit more efficient.

Curious to see the response of others.

Here is an example I wrote to demonstrate the problem I am having.

My AMD Threadripper and 1080 graphics I see
80,000 ellipses @60fps blendMode(NORMAL)
2,800 ellipses @60fps blendMode(SCREEN)

Perhaps this is expected behavior. I would like to screen all these objects without loss of frameRate. Can I eliminate alpha or draw objects differently to help blend faster?

// BlendMode Test
//     Thousands of semi-transparent colored ellipses randomly move around the screen. Simulation continues to add ellipses and monitor frameRate while user can toggle blendModes for effect.
//   Jared S Tarbell
//   November 12, 2019
//   Levitated Toy Factory
//   Albuquerque, New Mexico USA
//
//   Processing 3.5.3

ArrayList<Ball> balls = new ArrayList<Ball>();
IntList history = new IntList();

long[] times;
int averageOverFrames = 5;
long baseTime;

int max = 100000;
long lastMillis = 0;

boolean doBlend = false;

void setup() {
  size(1000,1000,FX2D);
  background(0);
  
  times = new long[averageOverFrames];
  baseTime = millis();
  for (int i=0;i<averageOverFrames-1;i++) times[i] = baseTime;
    
}

void mousePressed() {
  // toggle blendmode
  doBlend = !doBlend;
  if (!doBlend) blendMode(NORMAL);
}

void draw() {
  // clear the background and set the blend mode or not
  background(0);
  if (doBlend) blendMode(SCREEN);
  
  // draw all the balls
  for (Ball b:balls) b.render();
  
  // make new balls to reach maximum number
  for (int k=0;k<2;k++) {
    if (balls.size()<max) {
      Ball neo = new Ball();
      balls.add(neo);
    }
  }
  
  // render framerate history
  pushMatrix();
  stroke(0,255,0,222);
  noFill();
  int starti = 0;
  if (history.size()>width) {
    //scale((1.0*width)/history.size());
    translate(width-history.size(),0);
    starti = history.size()-width;
  }
  beginShape();
  for (int i=starti;i<history.size();i++) vertex(i,height-history.get(i));
  endShape();
  popMatrix();
  
  // draw 60fps benchline
  stroke(255,128);
  line(0,height-60,width,height-60);

  // calculate framerate and build history 
  float f = calcFrameRate();
  history.append(round(f));

  // calculate elapsed time
  float secs = (millis()-baseTime)/1000.0;

  // report framerate and other metrics
  fill(255);
  noStroke();
  textSize(20);
  textAlign(LEFT);
  String rpt = balls.size()+" balls @"+round(f)+"fps    "+nf(secs,0,1)+"s   ";
  if (doBlend) rpt+="SCREEN";
  else rpt+="NORMAL";
  text(rpt,10,height-20);
  textAlign(RIGHT);
  text("mousePress to toggle blendMode",width-10,height-20);

}

float calcFrameRate() {
  // calculate the average frame rate over the last 5 frames
  for (int i=0;i<averageOverFrames-1;i++) times[i] = times[i+1];
  times[averageOverFrames-1] = millis();
  long totalMillis = times[averageOverFrames-1] - times[0];
  if (totalMillis<=0) return 0;  // something is weird
  return (averageOverFrames-1)*1000.0/totalMillis; 
}


class Ball {
  float x, y;
  float vx, vy;
  float w;
  color myc;
  
  Ball() {
    x = random(width);
    y = random(height);
    vx = random(-3,3);
    vy = random(-3,3);
    w = random(2,20);
    myc = color(random(255),random(255),random(255));
  }
  
  void render() {
    fill(myc);
    noStroke();
    ellipse(x,y,w,w);
    x+=vx;
    y+=vy;
    
    // bound check
    if (x<0) x = width;
    if (w>width) x = 0;
    if (y<0) y = height;
    if (y>height) y = 0;
  }
  
}
1 Like

-a-
nice tool, but if i want test / compare anything i not see the differences ( from your code/display ).

i just wanted to try how
noSmooth();
would effect the bench result.

-b-
as the blendMode() is not even in the reference a link might be nice
https://processing.org/reference/blendMode_.html

-c- you compare 2 Modes,


blendMode(NORMAL);
// .v.s.
blendMode(SCREEN);

the NORMAL is not even documented
what does / should it mean? OFF / BLEND ?

BLEND - linear interpolation of colors: C = A*factor + B. This is the default.

Thanks so much for sharing this! Very helpful.

This is correct ā€“ by accident. NORMAL is defined in PConstants as ā€œ1ā€. The default blendMode, BLEND, is also defined as ā€œ1ā€. So this should be written ā€œblendMode(BLEND)ā€ ā€¦but it does the same thing.

The scale of the difference is a bit surprising, although it is not surprising that SCREEN is slower. In primitive operations, often addition and subtraction are faster than multiplication is faster than division. SCREEN is division based, often the slowest operation. Still, usually not 40x slower. In addition to benchmarking, it might be worth looking at the underlying implementation.

2 Likes