jsPerf.app is an online JavaScript performance benchmark test runner & jsperf.com mirror. It is a complete rewrite in homage to the once excellent jsperf.com now with hopefully a more modern & maintainable codebase.
jsperf.com URLs are mirrored at the same path, e.g:
https://jsperf.com/negative-modulo/2
Can be accessed at:
https://jsperf.app/negative-modulo/2
Compare speed of calling drawImage with fully opaque, fully transparent, fully translucent image (with 50% transparency) and putImageData.
<b>Background</b>: Anyone wishing to create a graphics engine will need to test the speed of various operations involving transparency. Full transparency (as opposed to translucency) is a common feature for rendering layers, so this benchmark tests against the different cases for that reason.
An additional benchmark of a custom transparency test prior to putImageData is included to benchmark the speed of a custom rendering engine.
<b>Results</b> show that drawImage with translucent and transparent pixels is a great deal faster than performing a custom blit with a transparency check. Also the test shows that manipulating packed RGBA values is much more efficient than accessing an interleaved array where each array index holds a single colour component.
Since Array's are faster than Typed-Arrays in Chrome, and of the typed arrays Int32 arrays are faster than Int8 arrays (* http://goo.gl/Yf2Gv ), it stands to reason that custom manipulation is best handled with packed RGBA arrays.
<canvas id="output" width="160" height="144">
</canvas>const RED_INDEX = 0;
const GREEN_INDEX = 1;
const BLUE_INDEX = 2;
const ALPHA_INDEX = 3;
var outputCanvas = document.getElementById('output');
var outputContext = outputCanvas.getContext('2d');
var width = outputCanvas.width;
var height = outputCanvas.height;
var outputImageData = outputContext.createImageData(width, height);
var bufferOpaque = CreateBuffer(width, height);
var bufferTransparency = CreateBuffer(width, height);
var bufferTranslucency = CreateBuffer(width, height);
var packedTransparencyData = new Array(width * height);
// Create random white noise (with 0xaa luminosity).
{
for (var i = 0; i < outputImageData.data.length; i += 4) {
var value = Math.round(Math.random() * 0xaa);
for (var j = 0; j < 3; ++j) {
outputImageData.data[i + j] = value;
}
outputImageData.data[i + 3] = 255;
}
outputContext.putImageData(outputImageData, 0, 0);
}
// Create fully opaque GREEN bufferContext and bufferData
{
var context = bufferOpaque.context;
var data = bufferOpaque.imageData.data;
for (var i = 0; i < data.length; i += 4) {
for (var j = 0; j < 3; ++j) {
data[i + j] = 0;
}
data[i + GREEN_INDEX] = 255;
data[i + ALPHA_INDEX] = 255;
}
context.putImageData(bufferOpaque.imageData, 0, 0);
}
// Cover with fully transparent RED (random pixels are fully transparent).
{
var context = bufferTransparency.context;
var data = bufferTransparency.imageData.data;
for (var i = 0; i < data.length; i += 4) {
for (var j = 0; j < 3; ++j) {
data[i + j] = 0;
}
data[i + RED_INDEX] = 255;
var alpha = 0;
if ((i % 8) == 0) {
alpha = 255;
}
data[i + ALPHA_INDEX] = alpha;
// Store packed data.
packedTransparencyData[i / 4] = 0;
for (var j = 0; j < 4; ++j) {
packedTransparencyData[i / 4] = (packedTransparencyData[i / 4] << 8) | data[i + j];
}
}
context.putImageData(bufferTransparency.imageData, 0, 0);
}
// Cover with translucency.
{
var context = bufferTranslucency.context;
var data = bufferTranslucency.imageData.data;
for (var i = 0; i < data.length; i += 4) {
for (var j = 0; j < 3; ++j) {
data[i + j] = 0;
}
data[i + BLUE_INDEX] = 255;
var alpha = 0;
if ((i % 8) == 0) {
alpha = 128;
}
data[i + ALPHA_INDEX] = alpha;
}
context.putImageData(bufferTranslucency.imageData, 0, 0);
}
function CreateBuffer(width, height) {
var buffer = new Object();
buffer.canvas = document.createElement("canvas");
buffer.canvas.width = width;
buffer.canvas.height = height;
buffer.context = buffer.canvas.getContext("2d");
buffer.imageData = buffer.context.createImageData(width, height);
return buffer;
}
// Will copy inputData to outputData
function ManualBlit(inputImageData, outputImageData, transparencyOn) {
var inputData = inputImageData.data;
var outputData = outputImageData.data;
var length = inputData.length / 4;
var indexPtr = 0;
if (transparencyOn) {
for (var i = 0; i < length; ++i) {
if (inputData[indexPtr + ALPHA_INDEX] >= 128) {
outputData[indexPtr] = inputData[indexPtr++];
outputData[indexPtr] = inputData[indexPtr++];
outputData[indexPtr] = inputData[indexPtr++];
outputData[indexPtr++] = 255; // ALPHA_INDEX
} else {
indexPtr += 4;
}
}
} else {
// No alpha check
for (var i = 0; i < length; ++i) {
outputData[indexPtr] = inputData[indexPtr++];
outputData[indexPtr] = inputData[indexPtr++];
outputData[indexPtr] = inputData[indexPtr++];
outputData[indexPtr] = 255; // ALPHA_INDEX
}
}
}
function PackedBlit(packedImageData, outputImageData) {
var packedPtr = 0;
var outputPtr = 0;
var data = outputImageData.data;
for (var i = 0; i < packedImageData.length; ++i) {
if ((packedValue & 0xff) > 128) {
var packedValue = packedImageData[packedPtr];
data[outputPtr++] = packedValue >> 24;
data[outputPtr++] = (packedValue >> 16) & 0xff;
data[outputPtr++] = (packedValue >> 8) & 0xff;
data[outputPtr++] = 0xff;
} else {
outputPtr += 4;
}
++packedPtr;
}
}Ready to run.
| Test | Ops/sec | |
|---|---|---|
| Fully opaque, drawImage | | ready |
| Full opaque, putImageData | | ready |
| Full transparency, drawImage | | ready |
| Full transparency, custom operation, putImageData | | ready |
| Full transparency, custom operation, packed pixels, putPixel | | ready |
| Translucency, drawImage | | ready |
You can edit these tests or add more tests to this page by appending /edit to the URL.