1
0
Fork 0
mirror of https://github.com/openstf/stf synced 2025-10-04 18:29:17 +02:00
OpenSTF/res/app/components/stf/screen/fast-image-render/index.js
Gunther Brunner d381d90446 - 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.
2014-02-28 16:41:49 +09:00

591 lines
16 KiB
JavaScript

// See http://jsperf.com/fastest-canvas-drawing/2
// See http://jsperf.com/canvas-drawimage-vs-putimagedata/3
// See http://jsperf.com/canvas-drawimage-vs-webgl-drawarrays
// -------------------------------------------------------------------------------------------------
function FastPixiRender(canvasElement, options) {
this.options = options
this.stage = new PIXI.Stage(0x000000)
this.canvasWidth = 643
this.canvasHeight = 1149
switch (this.options.render) {
case 'pixi':
this.renderer = new PIXI.autoDetectRenderer(this.canvasWidth, this.canvasHeight, canvasElement)
break;
case 'pixi-canvas':
this.renderer = new PIXI.CanvasRenderer(this.canvasWidth, this.canvasHeight, canvasElement)
break;
case 'pixi-webgl':
this.renderer = new PIXI.WebGLRenderer(this.canvasWidth, this.canvasHeight, canvasElement)
break;
}
}
FastPixiRender.prototype.draw = function (image) {
// if (this.baseTexture) {
// this.baseTexture.destroy()
// }
this.baseTexture = new PIXI.BaseTexture(image)
if (this.texture) {
this.texture.destroy(true)
}
this.texture = new PIXI.Texture(this.baseTexture)
if (this.sprite) {
this.sprite.setTexture(this.texture)
//this.stage.removeChild(this.sprite)
} else {
this.sprite = new PIXI.Sprite(this.texture)
this.stage.addChild(this.sprite)
}
this.renderer.render(this.stage)
}
// -------------------------------------------------------------------------------------------------
function CanvasRender(canvasElement, options) {
var checkForCanvasElement = function checkForCanvasElement() {
if (!canvasElement) {
throw new Error('Needs a canvas element')
}
this.displayWidth = canvasElement.offsetWidth
this.displayHeight = canvasElement.offsetHeight
if (!this.displayWidth || !this.displayHeight) {
throw new Error('Unable to get display size canvas must have dimensions')
}
}
this.options = options
this.context = canvasElement.getContext('2d')
}
CanvasRender.prototype.draw = function (image) {
this.context.drawImage(image, 0, 0)
}
CanvasRender.prototype.clear = function () {
this.context.clearRect(0, 0, this.displayWidth, this.displayHeight)
}
// -------------------------------------------------------------------------------------------------
// 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) {
throw new Error('Needs a canvas element')
}
this.displayWidth = canvasElement.offsetWidth
this.displayHeight = canvasElement.offsetHeight
if (!this.displayWidth || !this.displayHeight) {
throw new Error('Unable to get display size canvas must have dimensions')
}
}
this.options = {
// alpha: this.transparent,
// antialias: !!antialias,
// premultipliedAlpha: !!transparent,
// stencil: true
}
try {
this.ctx = canvasElement.getContext('experimental-webgl', this.options)
} catch (e) {
try {
this.ctx = canvasElement.getContext('webgl', this.options)
} catch (e2) {
// fail, not able to get a context
throw new Error('This browser does not support webGL. Try using the canvas renderer' + this)
}
}
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)
//this.ctx.disable(this.ctx.DEPTH_TEST)
//this.ctx.disable(this.ctx.CULL_FACE)
//this.setup()
}
WebGLRender.prototype.setup = function () {
// create shaders
var vertexShaderSrc =
'attribute vec2 aVertex;' +
'attribute vec2 aUV;' +
'varying vec2 vTex;' +
'void main(void) {' +
' gl_Position = vec4(aVertex, 0.0, 1.0);' +
' vTex = aUV;' +
'}';
var fragmentShaderSrc =
'precision highp float;' +
'varying vec2 vTex;' +
'uniform sampler2D sampler0;' +
'void main(void){' +
' gl_FragColor = texture2D(sampler0, vTex);' +
'}';
var vertShaderObj = this.ctx.createShader(this.ctx.VERTEX_SHADER)
var fragShaderObj = this.ctx.createShader(this.ctx.FRAGMENT_SHADER)
this.ctx.shaderSource(vertShaderObj, vertexShaderSrc)
this.ctx.shaderSource(fragShaderObj, fragmentShaderSrc)
this.ctx.compileShader(vertShaderObj)
this.ctx.compileShader(fragShaderObj)
var progObj = this.ctx.createProgram()
this.ctx.attachShader(progObj, vertShaderObj)
this.ctx.attachShader(progObj, fragShaderObj)
this.ctx.linkProgram(progObj)
this.ctx.useProgram(progObj)
var width = this.displayWidth
var height = this.displayHeight
this.ctx.viewport(0, 0, width, height)
this.vertexBuff = this.ctx.createBuffer()
this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.vertexBuff)
this.ctx.bufferData(
this.ctx.ARRAY_BUFFER,
new Float32Array([-1 / 8, 1 / 6, -1 / 8, -1 / 6, 1 / 8, -1 / 6, 1 / 8, 1 / 6]),
this.ctx.STATIC_DRAW)
this.texBuff = this.ctx.createBuffer()
this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.texBuff)
this.ctx.bufferData(
this.ctx.ARRAY_BUFFER,
new Float32Array([0, 1, 0, 0, 1, 0, 1, 1]),
this.ctx.STATIC_DRAW)
this.vloc = this.ctx.getAttribLocation(progObj, 'aVertex')
this.tloc = this.ctx.getAttribLocation(progObj, 'aUV')
}
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)
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_MAG_FILTER, this.ctx.NEAREST)
// this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_MIN_FILTER, this.ctx.LINEAR);
// this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_WRAP_S, this.ctx.CLAMP_TO_EDGE);
// this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_WRAP_T, this.ctx.CLAMP_TO_EDGE);
this.ctx.generateMipmap(this.ctx.TEXTURE_2D)
this.ctx.texImage2D(this.ctx.TEXTURE_2D, 0, this.ctx.RGBA, this.ctx.RGBA, this.ctx.UNSIGNED_BYTE, image)
this.ctx.enableVertexAttribArray(this.vloc)
this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.vertexBuff)
this.ctx.vertexAttribPointer(this.vloc, 2, this.ctx.FLOAT, false, 0, 0)
this.ctx.enableVertexAttribArray(this.tloc)
this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.texBuff)
this.ctx.bindTexture(this.ctx.TEXTURE_2D, tex)
this.ctx.vertexAttribPointer(this.tloc, 2, this.ctx.FLOAT, false, 0, 0)
}
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) {
this.render = new FastPixiRender(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) {
this.render.draw(image)
}
FastImageRender.prototype.clear = function () {
this.render.clear()
}
Object.defineProperty(FastImageRender.prototype, 'canvasWidth', {
get: function () {
return this.canvasElement.width
},
set: function (width) {
if (width) {
if (width !== this.canvasElement.width) {
this.canvasElement.width = width;
}
}
}
})
Object.defineProperty(FastImageRender.prototype, 'canvasHeight', {
get: function () {
return this.canvasElement.height
},
set: function (height) {
if (height) {
if (height !== this.canvasElement.height) {
this.canvasElement.height = height;
}
}
}
})
Object.defineProperty(FastImageRender.prototype, 'displayWidth', {
get: function () {
return this.canvasElement.width
},
set: function (width) {
if (width) {
if (width !== this.canvasElement.width) {
this.canvasElement.width = width;
}
}
}
})
Object.defineProperty(FastImageRender.prototype, 'displayHeight', {
get: function () {
return this.canvasElement.height
},
set: function (height) {
if (height) {
if (height !== this.canvasElement.height) {
this.canvasElement.height = height;
}
}
}
})
Object.defineProperty(FastImageRender.prototype, 'canvasStyleWidth', {
get: function () {
return parseInt(this.canvasElement.style.width, 10)
},
set: function (width) {
if (width) {
var styleWidth = width + 'px'
if (styleWidth !== this.canvasElement.style.width) {
this.canvasElement.style.width = styleWidth;
}
}
}
})
Object.defineProperty(FastImageRender.prototype, 'canvasStyleHeight', {
get: function () {
return parseInt(this.canvasElement.style.height, 10)
},
set: function (height) {
if (height) {
var styleHeight = height + 'px'
if (styleHeight !== this.canvasElement.style.height) {
this.canvasElement.style.height = height;
}
}
}
})
// -------------------------------------------------------------------------------------------------
// Check for Non CommonJS world
if (typeof module !== 'undefined') {
module.exports = {
FastImageRender: FastImageRender
}
}