Artem Gavrilov
Artem Gavrilov

Reputation: 3

What renders fast? fillRect() vs moveTo(), lineTo(), stroke()?

I have a legacy code where timeline for video is rendering using Canvas (as i understood, it's faster than DOM rendering). So, the author wrote a code with fillRect() method, but later he rewrote it to combination moveTo(), lineTo(), stroke().

The timeline looks like rectangle 8*1000px and can consist hundreds rectangles. I can't ask him why he did this changes and I have opinion that using fillRect() is better.

Is fillRect() renders as fast as lines (with fill inside)?

Upvotes: 0

Views: 1144

Answers (1)

Kaiido
Kaiido

Reputation: 136658

The second method will almost always be faster.

Until it actually has to paint something, all the context API does is simple arithmetic, all on the CPU and this is pretty fast.
What takes a long time is all the complex painting part + compositing.
Generally, to make this slow part faster, browsers will move that part to the GPU. But it still has to move all the data from the RAM to the GPU memory, and this takes some time.

So building a complex shape all on the CPU using the path manipulations methods and calling fill() or stroke() only once will definitively be faster than creating the same complex path while calling these fill() and stroke() several times like fillRect or strokeRect are bound to do.

const rects = Array.from({ length: 300 }, (_) => ({
  x: Math.random() * 1000,
  y: Math.random() * 1000,
  s: Math.random() * 200 + 50
}));
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

// Benchmark.js playground borrowed from 
// https://jsfiddle.net/533hc71h/

var test1_name = 'fillRect';
function test1() {
  rects.forEach(({ x, y, s }) => ctx.fillRect(x, y, s, s));
}
var test2_name = 'path + fill()';
function test2() {
  ctx.beginPath();
  rects.forEach(({ x, y, s }) => ctx.rect(x, y, s, s));
  ctx.fill();
}

function teardown() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
}

var cycleResults = document.getElementById('cycleResults');
var result = document.getElementById('result');
var btn = document.getElementById('btn');

// BENCHMARK ====================
btn.onclick = function runTests() {

  btn.setAttribute('disable', true);
  cycleResults.innerHTML = '';
  result.textContent = 'Tests running...';

  var suite = new Benchmark.Suite;

  // add tests
  suite
    .add(test1_name || 'test1', test1)
    .add(test2_name || 'test2', test2)
    // add listeners
    .on('cycle', function(event) {
      var result = document.createElement('li');
      result.textContent = String(event.target);

      document.getElementById('cycleResults')
        .appendChild(result);
    })
    .on('complete', function() {
      result.textContent = 'Fastest is ' + this.filter('fastest').pluck('name');
      btn.setAttribute('disable', false);
      teardown();
      test2();
    })
    .run({
      async: true
    });
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/benchmark/1.0.0/benchmark.min.js"></script>

<ul id='cycleResults'>

</ul>
<div id="result">

</div>
<br>
<button id="btn">
Run Tests
</button><br>

<canvas id="canvas" width="1000" height="1000"></canvas>

Upvotes: 2

Related Questions