flipping for fun and profit

JavaScript performance comparison

Test case created by Greggman

Preparation code

<canvas id="webgl" width="1000" height="1000"></canvas>
<canvas id="specialcanvasjustforflipping"></canvas>
<style>
canvas {
  width: 100px;
  padding: 5px;
}
      
<script>
Benchmark.prototype.setup = function() {
  var gl = document.querySelector("#webgl").getContext("webgl");
  var flipCtx = document.querySelector("#specialcanvasjustforflipping").getContext("2d");
  
  // fill webgl canvas with red on top and blue on bottom
  gl.enable(gl.SCISSOR_TEST);
  for (var y = 0; y < 15; ++y) {
    var v = y / 14;
    gl.scissor(0, v * gl.canvas.height, gl.canvas.width, 1 / 15 * gl.canvas.height);
    gl.clearColor(v, 0, 1 - v, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);
  }
  
  
  var pixels = new Uint8Array(
     gl.drawingBufferWidth * gl.drawingBufferHeight * 4);
  gl.readPixels(
     0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight, 
     gl.RGBA, gl.UNSIGNED_BYTE, pixels);

};

Benchmark.prototype.teardown = function() {
  // verify
  // NOTE: There's a bug in JSPERF, this never get's run
  
  var v = document.createElement("canvas").getContext("2d");
  v.canvas.width = gl.drawingBufferWidth;
  v.canvas.height = gl.drawingBufferHeight;
  
  var vi = new ImageData(new Uint8ClampedArray(pixels.buffer), gl.drawingBufferWidth, gl.drawingBufferHeight);
  v.putImageData(vi, 0, 0);
  gl.canvas.parentNode.appendChild(v.canvas);
  

};
</script>

Preparation code output

<canvas id="webgl" width="1000" height="1000"></canvas> <canvas id="specialcanvasjustforflipping"></canvas> <style> canvas { width: 100px; padding: 5px; }

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
flipData
var halfHeight = gl.drawingBufferHeight / 2 | 0;
var bytesPerRow = gl.drawingBufferWidth * 4;
var temp = new Uint8Array(gl.drawingBufferWidth * 4 * (gl.drawingBufferHeight + 1));

temp.set(pixels);

var size = gl.drawingBufferWidth * 4 * gl.drawingBufferHeight;

for (var y = 0; y < halfHeight; ++y) {
  var topOffset = y * bytesPerRow;
  var bottomOffset = (gl.drawingBufferHeight - y - 1) * bytesPerRow;

  // make copy of a row on the top half
  temp.copyWithin(size, topOffset, topOffset + bytesPerRow);

  // copy a row from the bottom half to the top
  temp.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow);

  // copy the copy of the top half row to the bottom half 
  temp.copyWithin(bottomOffset, size, size + bytesPerRow);
}

pixels.set(temp.subarray(0, size));
pending…
flipWith2DCanvas
var imageData = new ImageData(new Uint8ClampedArray(pixels.buffer), gl.drawingBufferWidth, gl.drawingBufferHeight)
var ctx = document.createElement("canvas").getContext("2d");
ctx.canvas.width = gl.drawingBufferWidth;
ctx.canvas.height = gl.drawingBufferHeight;

  // first put the imageData
  ctx.putImageData(imageData, 0,0);
  // because we've got transparency
  ctx.globalCompositeOperation = 'copy';
  ctx.scale(1,-1); // Y flip
  ctx.translate(0, -imageData.height); // so we can draw at 0,0
  ctx.drawImage(ctx.canvas, 0,0);
  // now we can restore the context to defaults if needed
  ctx.setTransform(1,0,0,1,0,0);

// now you need to actually provide the copy of readPixels data he wanted flipped
var pixels = ctx.getImageData(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight).data;
pending…
flipWith2DCanvasExistingHelperCanvas
var imageData = new ImageData(new Uint8ClampedArray(pixels.buffer), gl.drawingBufferWidth, gl.drawingBufferHeight)
var ctx = flipCtx;
ctx.canvas.width = gl.drawingBufferWidth;
ctx.canvas.height = gl.drawingBufferHeight;

  // first put the imageData
  ctx.putImageData(imageData, 0,0);
  // because we've got transparency
  ctx.globalCompositeOperation = 'copy';
  ctx.scale(1,-1); // Y flip
  ctx.translate(0, -imageData.height); // so we can draw at 0,0
  ctx.drawImage(ctx.canvas, 0,0);
  // now we can restore the context to defaults if needed
  ctx.setTransform(1,0,0,1,0,0);

// now you need to actually provide the copy of readPixels data he wanted flipped
var pixels = ctx.getImageData(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight).data;

// you need to zero out the canvas here otherwise it's sitting around
// wasting memory the user can never recover
ctx.canvas.width = 0;
ctx.canvas.height = 0;
pending…
Assume only need output in canvas
var imageData = new ImageData(new Uint8ClampedArray(pixels.buffer), gl.drawingBufferWidth, gl.drawingBufferHeight)
var ctx = flipCtx;
ctx.canvas.width = gl.drawingBufferWidth;
ctx.canvas.height = gl.drawingBufferHeight;

  // first put the imageData
  ctx.putImageData(imageData, 0,0);
  // because we've got transparency
  ctx.globalCompositeOperation = 'copy';
  ctx.scale(1,-1); // Y flip
  ctx.translate(0, -imageData.height); // so we can draw at 0,0
  ctx.drawImage(ctx.canvas, 0,0);
  // now we can restore the context to defaults if needed
  ctx.setTransform(1,0,0,1,0,0);
pending…
Using set per line (the case that should run fast but doesn't because set is slow)
var halfHeight = gl.drawingBufferWidth / 2 | 0;  // the | 0 keeps the result an int
var bytesPerRow = gl.drawingBufferWidth * 4;

// make a temp buffer to hold one row
var temp = new Uint8Array(gl.drawingBufferWidth * 4);
for (var y = 0; y < halfHeight; ++y) {
  var topOffset = y * bytesPerRow;
  var bottomOffset = (gl.drawingBufferHeight - y - 1) * bytesPerRow;

  // make copy of a row on the top half
  temp.set(pixels.subarray(topOffset, topOffset + bytesPerRow));

  // copy a row from the bottom half to the top
  pixels.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow);

  // copy the copy of the top half row to the bottom half 
  pixels.set(temp, bottomOffset);
}
pending…
Assume set was optimized as it should be
var halfHeight = gl.drawingBufferHeight / 2 | 0;
var bytesPerRow = gl.drawingBufferWidth * 4;
var temp = new Uint8Array(gl.drawingBufferWidth * 4);

var size = gl.drawingBufferWidth * 4 * gl.drawingBufferHeight;

// This doesn't actually work. It's just a test of timing. set should
// be as fast as copyWithin but it's not at the moment so this
// simulates what if it was.
for (var y = 0; y < halfHeight; ++y) {
  var topOffset = y * bytesPerRow;
  var bottomOffset = (gl.drawingBufferHeight - y - 1) * bytesPerRow;

  // make copy of a row on the top half
  pixels.copyWithin(bottomOffset, topOffset, topOffset + bytesPerRow);

  // copy a row from the bottom half to the top
  pixels.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow);

  // copy the copy of the top half row to the bottom half 
  pixels.copyWithin(bottomOffset, topOffset, topOffset + bytesPerRow);
}
pending…

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

0 Comments