mirror of
https://github.com/DeviceFarmer/stf.git
synced 2026-04-23 22:35:25 +02:00
- 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:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 577 KiB After Width: | Height: | Size: 577 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
Binary file not shown.
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
103
res/app/components/stf/screen/fast-image-render/test/results.md
Normal file
103
res/app/components/stf/screen/fast-image-render/test/results.md
Normal 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
|
||||
|
||||
Reference in New Issue
Block a user