Canvas API calls vs. matrix computing

JavaScript performance comparison

Revision 4 of this test case created by

Preparation code

<canvas width="640" height="480" id="canvas1"></canvas>
<canvas width="640" height="480" id="canvas2"></canvas>
<canvas width="640" height="480" id="canvas3"></canvas>
      
<script>
Benchmark.prototype.setup = function() {
  var transform2d = function(foreign) {
    var matrix = new Float32Array(256 + 6),
      currentPos = 6;
  
    function identity() {
      matrix[0] = 1;
      matrix[2] = 0;
      matrix[4] = 0;
      matrix[1] = 0;
      matrix[3] = 1;
      matrix[5] = 0;
    }
  
    function translate(x, y) {
      matrix[4] += matrix[0] * x + matrix[2] * y;
      matrix[5] += matrix[1] * x + matrix[3] * y;
    }
  
    function scale(sx, sy) {
      matrix[0] *= sx;
      matrix[1] *= sx;
      matrix[2] *= sy;
      matrix[3] *= sy;
    }
  
    function rotate(a) {
      var cos = Math.cos(a),
        sin = Math.sin(a),
        mAA = matrix[0] * cos + matrix[2] * sin,
        mAB = matrix[1] * cos + matrix[3] * sin,
        mBA = -matrix[0] * sin + matrix[2] * cos,
        mBB = -matrix[1] * sin + matrix[3] * cos;
  
      matrix[0] = mAA;
      matrix[1] = mAB;
      matrix[2] = mBA;
      matrix[3] = mBB;
    }
  
    function push() {
      matrix[currentPos++] = matrix[0];
      matrix[currentPos++] = matrix[1];
      matrix[currentPos++] = matrix[2];
      matrix[currentPos++] = matrix[3];
      matrix[currentPos++] = matrix[4];
      matrix[currentPos++] = matrix[5];
    }
  
    function pop() {
      matrix[5] = matrix[--currentPos];
      matrix[4] = matrix[--currentPos];
      matrix[3] = matrix[--currentPos];
      matrix[2] = matrix[--currentPos];
      matrix[1] = matrix[--currentPos];
      matrix[0] = matrix[--currentPos];
    }
  
    function transformContext() {
      foreign.context.setTransform(matrix[0], matrix[1], matrix[2],
        matrix[3], matrix[4], matrix[5]);
    }
  
    return {
      transformContext: transformContext,
      identity: identity,
      translate: translate,
      scale: scale,
      rotate: rotate,
      push: push,
      pop: pop
    };
  };
  
  var transform2dNew = function(foreign) {
    var maxMatrices = 32,
      matrices = [],
      currentMatrix,
      currentPos = 6;
  
    function createMatrices() {
      var i;
  
      for (i = 0; i < maxMatrices; i += 1) {
        matrices.push({
          aa: 1,
          ba: 0,
          ca: 0,
          ab: 0,
          bb: 1,
          cb: 0,
          next: null,
          prev: i > 0 ? matrices[i - 1] : null
        });
      }
  
      for (i = 0; i < maxMatrices - 1; i += 1) {
        matrices[i].next = matrices[i + 1];
      }
      currentMatrix = matrices[0];
    }
  
    function identity() {
      currentMatrix.aa = 1;
      currentMatrix.ba = 0;
      currentMatrix.ca = 0;
      currentMatrix.ab = 0;
      currentMatrix.bb = 1;
      currentMatrix.cb = 0;
    }
  
    function translate(x, y) {
      currentMatrix.ca += currentMatrix.aa * x + currentMatrix.ba * y;
      currentMatrix.cb += currentMatrix.ab * x + currentMatrix.bb * y;
    }
  
    function scale(sx, sy) {
      currentMatrix.aa *= sx;
      currentMatrix.ab *= sx;
      currentMatrix.ba *= sy;
      currentMatrix.bb *= sy;
    }
  
    function rotate(a) {
      var cos = Math.cos(a),
        sin = Math.sin(a),
        mAA = currentMatrix.aa * cos + currentMatrix.ba * sin,
        mAB = currentMatrix.ab * cos + currentMatrix.bb * sin,
        mBA = -currentMatrix.aa * sin + currentMatrix.ba * cos,
        mBB = -currentMatrix.ab * sin + currentMatrix.bb * cos;
  
      currentMatrix.aa = mAA;
      currentMatrix.ab = mAB;
      currentMatrix.ba = mBA;
      currentMatrix.bb = mBB;
    }
  
    function push() {
      var oldMatrix = currentMatrix;
      currentMatrix = currentMatrix.next;
  
      currentMatrix.aa = oldMatrix.aa;
      currentMatrix.ba = oldMatrix.ba;
      currentMatrix.ca = oldMatrix.ca;
      currentMatrix.ab = oldMatrix.ab;
      currentMatrix.bb = oldMatrix.bb;
      currentMatrix.cb = oldMatrix.cb;
    }
  
    function pop() {
      var oldMatrix = currentMatrix;
      currentMatrix = currentMatrix.prev;
    }
  
    function transformContext() {
      foreign.context.setTransform(currentMatrix.aa, currentMatrix.ab, currentMatrix.ba,
        currentMatrix.bb, currentMatrix.ca, currentMatrix.cb);
    }
  
    createMatrices();
  
    return {
      transformContext: transformContext,
      identity: identity,
      translate: translate,
      scale: scale,
      rotate: rotate,
      push: push,
      pop: pop
    };
  };
  
  var canvas1 = document.querySelector('#canvas1'),
    ctx1 = canvas1.getContext('2d'),
    mat1 = transform2dNew({
      context: ctx1
    }),
    canvas2 = document.querySelector('#canvas2'),
    ctx2 = canvas2.getContext('2d'),
    mat2 = transform2d({
      context: ctx2
    }),
    canvas3 = document.querySelector('#canvas3'),
    ctx3 = canvas3.getContext('2d');
  
  ctx1.fillStyle = 'black';
  ctx2.fillStyle = 'black';
  ctx3.fillStyle = 'black';
  
  mat1.identity();
  mat2.identity();

};
</script>

Preparation code output

<canvas width="640" height="480" id="canvas1"></canvas> <canvas width="640" height="480" id="canvas2"></canvas> <canvas width="640" height="480" id="canvas3"></canvas>

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in CCBot 2.0.0 / Other 0.0.0
Test Ops/sec
New matrix with push/pop
mat1.push();
mat1.translate(canvas1.width / 2, canvas1.height / 2);
mat1.push();
mat1.rotate(Math.PI / 4);
mat1.translate(-10, -10);
mat1.transformContext();
ctx1.fillRect(0, 0, 20, 20);
mat1.pop();

mat1.push();
mat1.rotate(-Math.PI / 3);
mat1.scale(4, 4);
mat1.translate(-10, -10);
mat1.transformContext();
ctx1.fillRect(0, 0, 20, 20);
mat1.pop();
mat1.pop();
pending…
Old matrix with push/pop
mat2.push();
mat2.translate(canvas2.width / 2, canvas2.height / 2);
mat2.push();
mat2.rotate(Math.PI / 4);
mat2.translate(-10, -10);
mat2.transformContext();
ctx2.fillRect(0, 0, 20, 20);
mat2.pop();

mat2.push();
mat2.rotate(-Math.PI / 3);
mat2.scale(4, 4);
mat2.translate(-10, -10);
mat2.transformContext();
ctx2.fillRect(0, 0, 20, 20);
mat2.pop();
mat2.pop();
pending…
Canvas API with save/restore
ctx3.save();
ctx3.translate(canvas3.width / 2, canvas3.height / 2);
ctx3.save();
ctx3.rotate(Math.PI / 4);
ctx3.translate(-10, -10);

ctx3.fillRect(0, 0, 20, 20);
ctx3.restore();

ctx3.save();
ctx3.rotate(-Math.PI / 3);
ctx3.scale(4, 4);
ctx3.translate(-10, -10);

ctx3.fillRect(0, 0, 20, 20);
ctx3.restore();
ctx3.restore();
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL.

0 Comments