- Added benchmark comparing JPEG, PNG, WEBP, DDS DXT1, and CRN DXT1 formats.

- Pixi Canvas and WebGL render was added for quick benchmarking but it's going away soon, because WebGL is even slower than Canvas for big textures. The browsers nowadays use OpenGL in the backend for rendering Canvas.
- There is 3 different WebGL Renders but they need adjusting so they work with Non Power Of Two textures.
- For now the winner is JPEG+Canvas, however CRN+WebGL will be better almost for sure if server side supports it.
This commit is contained in:
Gunther Brunner
2014-02-28 16:41:49 +09:00
parent 999e5113bf
commit d381d90446
11 changed files with 393 additions and 39 deletions

View File

@@ -3,35 +3,7 @@
// See http://jsperf.com/canvas-drawimage-vs-webgl-drawarrays
function FastImageLoader(url) {
var that = this
this.loader = new Image()
if (url) {
this.load(url)
}
this.loader.onload = function () {
if (typeof(that.onLoad) === 'function') {
that.onLoad(this)
}
}
this.loader.onerror = function () {
if (typeof(that.onError) === 'function') {
that.onError(this)
}
}
}
/**
* Loads an URL
* @param {string} url
*/
FastImageLoader.prototype.load = function (url) {
this.loader.src = url
}
// -------------------------------------------------------------------------------------------------
function FastPixiRender(canvasElement, options) {
@@ -79,6 +51,7 @@ FastPixiRender.prototype.draw = function (image) {
this.renderer.render(this.stage)
}
// -------------------------------------------------------------------------------------------------
function CanvasRender(canvasElement, options) {
var checkForCanvasElement = function checkForCanvasElement() {
@@ -107,6 +80,215 @@ CanvasRender.prototype.clear = function () {
}
// -------------------------------------------------------------------------------------------------
// Based on http://www-cs-students.stanford.edu/~eparker/files/crunch/renderer.js
/**
* Constructs a renderer object.
* @param {WebGLRenderingContext} gl The GL context.
* @constructor
*/
var Renderer = function (gl) {
/**
* The GL context.
* @type {WebGLRenderingContext}
* @private
*/
this.gl_ = gl;
/**
* The WebGLProgram.
* @type {WebGLProgram}
* @private
*/
this.program_ = gl.createProgram();
/**
* @type {WebGLShader}
* @private
*/
this.vertexShader_ = this.compileShader_(
Renderer.vertexShaderSource_, gl.VERTEX_SHADER);
/**
* @type {WebGLShader}
* @private
*/
this.fragmentShader_ = this.compileShader_(
Renderer.fragmentShaderSource_, gl.FRAGMENT_SHADER);
/**
* Cached uniform locations.
* @type {Object.<string, WebGLUniformLocation>}
* @private
*/
this.uniformLocations_ = {};
/**
* Cached attribute locations.
* @type {Object.<string, WebGLActiveInfo>}
* @private
*/
this.attribLocations_ = {};
/**
* A vertex buffer containing a single quad with xy coordinates from [-1,-1]
* to [1,1] and uv coordinates from [0,0] to [1,1].
* @private
*/
this.quadVertexBuffer_ = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer_);
var vertices = new Float32Array(
[-1.0, -1.0, 0.0, 1.0,
+1.0, -1.0, 1.0, 1.0,
-1.0, +1.0, 0.0, 0.0,
1.0, +1.0, 1.0, 0.0]);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// Init shaders
gl.attachShader(this.program_, this.vertexShader_);
gl.attachShader(this.program_, this.fragmentShader_);
gl.bindAttribLocation(this.program_, 0, 'vert');
gl.linkProgram(this.program_);
gl.useProgram(this.program_);
gl.enableVertexAttribArray(0);
gl.enable(gl.DEPTH_TEST);
gl.disable(gl.CULL_FACE);
var count = gl.getProgramParameter(this.program_, gl.ACTIVE_UNIFORMS);
for (var i = 0; i < /** @type {number} */(count); i++) {
var infoU = gl.getActiveUniform(this.program_, i);
this.uniformLocations_[infoU.name] = gl.getUniformLocation(this.program_, infoU.name);
}
count = gl.getProgramParameter(this.program_, gl.ACTIVE_ATTRIBUTES);
for (var j = 0; j < /** @type {number} */(count); j++) {
var infoA = gl.getActiveAttrib(this.program_, j);
this.attribLocations_[infoA.name] = gl.getAttribLocation(this.program_, infoA.name);
}
};
Renderer.prototype.finishInit = function () {
this.draw();
};
Renderer.prototype.createDxtTexture = function (dxtData, width, height, format) {
var gl = this.gl_;
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.compressedTexImage2D(
gl.TEXTURE_2D,
0,
format,
width,
height,
0,
dxtData);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
//gl.generateMipmap(gl.TEXTURE_2D)
gl.bindTexture(gl.TEXTURE_2D, null);
return tex;
};
Renderer.prototype.createRgb565Texture = function (rgb565Data, width, height) {
var gl = this.gl_;
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGB,
width,
height,
0,
gl.RGB,
gl.UNSIGNED_SHORT_5_6_5,
rgb565Data);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
//gl.generateMipmap(gl.TEXTURE_2D)
gl.bindTexture(gl.TEXTURE_2D, null);
return tex;
};
Renderer.prototype.drawTexture = function (texture, width, height) {
var gl = this.gl_;
// draw scene
gl.clearColor(0, 0, 0, 1);
gl.clearDepth(1.0);
gl.viewport(0, 0, width, height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.uniform1i(this.uniformLocations_.texSampler, 0);
gl.enableVertexAttribArray(this.attribLocations_.vert);
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer_);
gl.vertexAttribPointer(this.attribLocations_.vert, 4, gl.FLOAT,
false, 0, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
};
/**
* Compiles a GLSL shader and returns a WebGLShader.
* @param {string} shaderSource The shader source code string.
* @param {number} type Either VERTEX_SHADER or FRAGMENT_SHADER.
* @return {WebGLShader} The new WebGLShader.
* @private
*/
Renderer.prototype.compileShader_ = function (shaderSource, type) {
var gl = this.gl_;
var shader = gl.createShader(type);
gl.shaderSource(shader, shaderSource);
gl.compileShader(shader);
return shader;
};
/**
* @type {string}
* @private
*/
Renderer.vertexShaderSource_ = [
'attribute vec4 vert;',
'varying vec2 v_texCoord;',
'void main() {',
' gl_Position = vec4(vert.xy, 0.0, 1.0);',
' v_texCoord = vert.zw;',
'}'
].join('\n');
/**
* @type {string}
* @private
*/
Renderer.fragmentShaderSource_ = [
'precision highp float;',
'uniform sampler2D texSampler;',
'varying vec2 v_texCoord;',
'void main() {',
' gl_FragColor = texture2D(texSampler, v_texCoord);',
'}'
].join('\n');
// -------------------------------------------------------------------------------------------------
function WebGLRender(canvasElement, options) {
var checkForCanvasElement = function checkForCanvasElement() {
if (!canvasElement) {
@@ -139,6 +321,13 @@ function WebGLRender(canvasElement, options) {
}
}
if (!this.ctx.getExtension('WEBKIT_WEBGL_compressed_texture_s3tc')) {
this.dxtSupported = false
}
this.renderer = new Renderer(this.ctx)
this.contextLost = false
// gl.useProgram(this.shaderManager.defaultShader.program)
@@ -146,7 +335,7 @@ function WebGLRender(canvasElement, options) {
//this.ctx.disable(this.ctx.DEPTH_TEST)
//this.ctx.disable(this.ctx.CULL_FACE)
this.setup()
//this.setup()
}
WebGLRender.prototype.setup = function () {
@@ -207,6 +396,13 @@ WebGLRender.prototype.setup = function () {
}
WebGLRender.prototype.draw = function (image) {
// this.renderer.drawTexture(image, image.width, image.height)
this.renderer.drawTexture(image, 643, 1149)
}
WebGLRender.prototype.drawOld = function (image) {
var tex = this.ctx.createTexture()
this.ctx.bindTexture(this.ctx.TEXTURE_2D, tex)
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_MIN_FILTER, this.ctx.NEAREST)
@@ -233,10 +429,29 @@ WebGLRender.prototype.clear = function () {
}
// -------------------------------------------------------------------------------------------------
function FastImageRender(canvasElement, options) {
var that = this
this.options = options || {}
this.canvasElement = canvasElement
if (true) {
this.loader = new Image()
this.loader.onload = function () {
if (typeof(that.onLoad) === 'function') {
that.onLoad(this)
}
}
this.loader.onerror = function () {
if (typeof(that.onError) === 'function') {
that.onError(this)
}
}
}
if (this.options.render === 'webgl') {
this.render = new WebGLRender(canvasElement, options)
} else if (this.options.render.indexOf('pixi') !== -1) {
@@ -244,6 +459,36 @@ function FastImageRender(canvasElement, options) {
} else {
this.render = new CanvasRender(canvasElement, options)
}
}
FastImageRender.prototype.load = function (url, type) {
var that = this
if (this.options.textureLoader) {
if (!this.textureLoader) {
this.textureLoader = new TextureUtil.TextureLoader(this.render.ctx)
}
var texture = null
if (type) {
texture = this.render.ctx.createTexture();
this.textureLoader.loadEx(url, texture, true, function (tex) {
if (typeof(that.onLoad) === 'function') {
that.onLoad(texture)
}
}, type)
} else {
this.textureLoader.load(url, function (texture) {
if (typeof(that.onLoad) === 'function') {
that.onLoad(texture)
}
})
}
} else {
this.loader.src = url
}
}
FastImageRender.prototype.draw = function (image) {
@@ -335,10 +580,12 @@ Object.defineProperty(FastImageRender.prototype, 'canvasStyleHeight', {
})
// -------------------------------------------------------------------------------------------------
// Check for Non CommonJS world
if (typeof module !== 'undefined') {
module.exports = {
FastImageRender: FastImageRender,
FastImageLoader: FastImageLoader
FastImageRender: FastImageRender
}
}

View File

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

View File

Before

Width:  |  Height:  |  Size: 577 KiB

After

Width:  |  Height:  |  Size: 577 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -21,7 +21,8 @@
<!--<canvas width="642px" height="1146px"></canvas>-->
<script src="http://cdnjs.cloudflare.com/ajax/libs/pixi.js/1.5.1/pixi.dev.js"></script>
<script src="../webgl-texture-utils/build/texture-util.js"></script>
<script src="../index.js"></script>
<script src="performance_test.js"></script>
</body>
</html>
</html>

View File

@@ -3,13 +3,11 @@ var frameNumberElement = document.querySelector('#frame-number')
var totalTimeElement = document.querySelector('#total-time')
var frame = {
total: 5000,
total: 50,
current: 0
}
var imageLoader = new FastImageLoader()
var imageRender = new FastImageRender(canvasElement, {render: 'canvas'})
var imageRender = new FastImageRender(canvasElement, {render: 'canvas', textureLoader: false})
function loadNext() {
console.time('load')
@@ -17,14 +15,15 @@ function loadNext() {
// var height = 300
// loader.src = 'http://placehold.it/' + width + 'x' + height + '?' + Date.now()
// loader.src = 'http://lorempixel.com/' + width + '/' + height + '/abstract/Frame-' + frames.current + '/?' + Date.now()
imageLoader.load('screen.jpg?' + Date.now())
imageRender.load('images/screen.webp?' + Date.now())
// imageRender.load('images/screen.jpg')
}
var startTime = new Date().getTime()
loadNext()
imageLoader.onLoad = function (image) {
imageRender.onLoad = function (image) {
console.timeEnd('load')
console.time('draw')
imageRender.draw(image)

View File

@@ -0,0 +1,103 @@
# Benchmark results (internal data)
-----
### Versions
- Canary 35
- Safari 7
- Firefox 27
### Time to load and draw 5000 frames
Hardware | Browser | Render | Time | ms
-------- | ------- | ------ | ---- | --
iMac | Canary | pixi-canvas | 54s |
iMac | Canary | pixi-webgl | 58s |
iMac | Canary | canvas | 54s | 5.8ms
iMac | Firefox | pixi-canvas | 75s | 8.3ms
iMac | Firefox | pixi-webgl | 82s | 11.3ms
iMac | Firefox | canvas | 76s | 8.1ms
MacBook | Canary | pixi-canvas | 68s
MacBook | Canary | pixi-webgl | 83s
MacBook | Canary | canvas | 76s
### Time to just draw 5000 frames
Hardware | Browser | Format | Render | Time | ms
-------- | ------- | ------ | ------ | ---- | --
iMac | Canary | DDS DXT1 | webgl | 55s |
### Time to render 1 frame
Hardware | Browser | Format | Size | Function | ms
-------- | ------- | ------ | ---- | -------- | --
iMac | Canary | DDS DXT1 | 2.1MB | load | 20-100ms
iMac | Canary | DDS DXT1 | 2.1MB | draw | **0.04ms**
iMac | Canary | DDS GZIP | 271KB | load |
iMac | Canary | DDS GZIP | 271KB | draw |
iMac | Canary | WEBP | 70KB | load | 9ms
iMac | Canary | WEBP | 70KB | draw | 24ms
iMac | Canary | DDS CRN | 70KB | load | 30ms
iMac | Canary | DDS CRN | 70KB | draw | **0.04ms**
iMac | Canary | JPEG | 94KB | load | 25ms
iMac | Canary | JPEG | 94KB | draw | 6ms
iMac | Canary | PNG | 590KB | load | 6ms
iMac | Canary | PNG | 590KB | draw | 30ms
# About ST3C DXT1 DDS CRN
### ST3C (S3 Texture Compression)
Group of related lossy texture compression algorithms, supported by most GPUs in Mac/PCs
### DXT1 (Block Compression 1)
Smallest ST3C algorithm, 1-bit Alpha, compression ratio 6:1
### DDS (DirectDraw Surface)
Container file format for storing ST3C textures
### CRN (DXTn Real-time Transcoding)
Container file format for transcoding crunched ST3C textures
# DDS vs JPEG
- Drawing a DXT1 texture is over 100 times faster than JPEG
- The DXT1 texture is transferred directly to the GPU, unlike JPEG
- The DXT1 uses 6 times less GPU memory
- File size is big, but when gzipped it's smaller than PNG
# CRN vs JPEG
- It transcodes to DXT1 so has all DXT1 benefits
- File size is very small, even smaller than JPEG (!)
- Requires to transcode on client side, so there is a CPU penalty
- However transcoding CRN->DXT1 is faster than decoding and tranfering JPEG->GPU
- JPEG->GPU texture uploading blocks the main thread and is slow
- Transcoding a CRN can be done async, and even offloaded to a WebWorker
- Currently the Crunch library is compiled to JS with Emscripten, so enabling asm.js would make the transcoding even faster
- Compressing CRN vs libjpeg-turbo benchmarks still need to be done
# Links
ECT1 texture format works on all Android devices
http://developer.android.com/tools/help/etc1tool.html
WebGL also
http://blog.tojicode.com/2011/12/compressed-textures-in-webgl.html
Crunch
https://code.google.com/p/crunch/
Fast compression
http://www.fastcompression.com/
DXT1 crunch WebGL
http://www-cs-students.stanford.edu/~eparker/files/crunch/decode_test.html
WebGL Texture Utils
https://github.com/gunta/webgl-texture-utils#webgl-texture-utils