Canvas API calls vs. matrix computing

JavaScript performance comparison

Revision 4 of this test case created

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

Test runner

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

Java applet disabled.

Testing in unknown unknown
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. Here’s a list of current revisions for this page:

0 comments

Add a comment