Question

I have serious performance problems using canvas clip() with chrome.

I have made a test case to illustrate.

Even in a simple case like this the red rectangle blinks as if it takes too much time to redraw, and a CPU profiling shows the clip() method takes about 10% of the CPU.

In my real program, it get to 16% and keep increasing each frame until the canvas almost freezes the browser..

Is there something wrong in my use of clip ?

Thank you for any suggestions,

Regards.

Was it helpful?

Solution

Cause

Insert a beginPath() as rect() adds to the path unlike fillRect()/strokeRect(). What happens here is that the rectangles are accumulating eventually slowing the clipping down over time.

This in combination with using setInterval, which is not able to synchronize with monitor/screen refreshes, worsens the problem.

To elaborate on the latter:

Using setInterval()/setTimeout() can cause tearing which happens when the draw is in the "middle" of its operation not fully completed and a screen refresh occur.

setInterval can only take integer values and you would need for 16.67 ms to synchronize frame-wise (@60Hz). Even if setInterval could take floats it would not synchronize the timing with the monitor timing as the timer mechanism isn't bound to monitor at all.

To solve this always use requestAnimationFrame to synchronize drawings with screen updates. This is directly linked to monitor refreshes and is a more low-level and efficient implementation than the other, and is made for this purpose, hence the name.

Solution embedding both fixes above

See modified bin here.

The code for future visitors:

function draw() {

  context.fillStyle = '#000';
  context.fillRect(0, 0, width, height);
  
  context.save();
  
  context.beginPath();            /// add a beginPath here
  context.rect(0, 0, 100, 100);
  context.clip();
  
  context.fillStyle = '#ff0000';
  context.fillRect(0, 0, 200, 200);
  
  context.restore();

  requestAnimationFrame(draw);    /// use rAF here
}

canvas.width = width;
canvas.height = height;
canvas.style.width = width+'px';
canvas.style.height = height+'px';

requestAnimationFrame(draw);      /// start loop

PS: If you need to stop the loop inject a condition to run rAF all inside the loop, ie:

if (isPlaying) requestAnimationFrame(draw);

There is BTW no need for closePath() as this will be done implicit for you as by the specs. You can add it if you want, but calling clip() (or fill() etc.) will do this for you (specs is addressing browser implementers):

Open subpaths must be implicitly closed when computing the clipping region

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top