309 lines
8.0 KiB
JavaScript
309 lines
8.0 KiB
JavaScript
JSMpeg.Renderer.WebGL = (function(){ "use strict";
|
|
|
|
var WebGLRenderer = function(options) {
|
|
if (options.canvas) {
|
|
this.canvas = options.canvas;
|
|
this.ownsCanvasElement = false;
|
|
}
|
|
else {
|
|
this.canvas = document.createElement('canvas');
|
|
this.ownsCanvasElement = true;
|
|
}
|
|
this.width = this.canvas.width;
|
|
this.height = this.canvas.height;
|
|
this.enabled = true;
|
|
|
|
this.hasTextureData = {};
|
|
|
|
var contextCreateOptions = {
|
|
preserveDrawingBuffer: !!options.preserveDrawingBuffer,
|
|
alpha: false,
|
|
depth: false,
|
|
stencil: false,
|
|
antialias: false,
|
|
premultipliedAlpha: false
|
|
};
|
|
|
|
this.gl =
|
|
this.canvas.getContext('webgl', contextCreateOptions) ||
|
|
this.canvas.getContext('experimental-webgl', contextCreateOptions);
|
|
|
|
if (!this.gl) {
|
|
throw new Error('Failed to get WebGL Context');
|
|
}
|
|
|
|
this.handleContextLostBound = this.handleContextLost.bind(this);
|
|
this.handleContextRestoredBound = this.handleContextRestored.bind(this);
|
|
|
|
this.canvas.addEventListener('webglcontextlost', this.handleContextLostBound, false);
|
|
this.canvas.addEventListener('webglcontextrestored', this.handleContextRestoredBound, false);
|
|
|
|
this.initGL();
|
|
};
|
|
|
|
WebGLRenderer.prototype.initGL = function() {
|
|
this.hasTextureData = {};
|
|
|
|
var gl = this.gl;
|
|
var vertexAttr = null;
|
|
|
|
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
|
|
|
|
// Init buffers
|
|
this.vertexBuffer = gl.createBuffer();
|
|
var vertexCoords = new Float32Array([0, 0, 0, 1, 1, 0, 1, 1]);
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
|
|
gl.bufferData(gl.ARRAY_BUFFER, vertexCoords, gl.STATIC_DRAW);
|
|
|
|
// Setup the main YCrCbToRGBA shader
|
|
this.program = this.createProgram(
|
|
WebGLRenderer.SHADER.VERTEX_IDENTITY,
|
|
WebGLRenderer.SHADER.FRAGMENT_YCRCB_TO_RGBA
|
|
);
|
|
vertexAttr = gl.getAttribLocation(this.program, 'vertex');
|
|
gl.enableVertexAttribArray(vertexAttr);
|
|
gl.vertexAttribPointer(vertexAttr, 2, gl.FLOAT, false, 0, 0);
|
|
|
|
this.textureY = this.createTexture(0, 'textureY');
|
|
this.textureCb = this.createTexture(1, 'textureCb');
|
|
this.textureCr = this.createTexture(2, 'textureCr');
|
|
|
|
|
|
// Setup the loading animation shader
|
|
this.loadingProgram = this.createProgram(
|
|
WebGLRenderer.SHADER.VERTEX_IDENTITY,
|
|
WebGLRenderer.SHADER.FRAGMENT_LOADING
|
|
);
|
|
vertexAttr = gl.getAttribLocation(this.loadingProgram, 'vertex');
|
|
gl.enableVertexAttribArray(vertexAttr);
|
|
gl.vertexAttribPointer(vertexAttr, 2, gl.FLOAT, false, 0, 0);
|
|
|
|
this.shouldCreateUnclampedViews = !this.allowsClampedTextureData();
|
|
};
|
|
|
|
WebGLRenderer.prototype.handleContextLost = function(ev) {
|
|
ev.preventDefault();
|
|
};
|
|
|
|
WebGLRenderer.prototype.handleContextRestored = function(ev) {
|
|
this.initGL();
|
|
};
|
|
|
|
WebGLRenderer.prototype.destroy = function() {
|
|
var gl = this.gl;
|
|
|
|
this.deleteTexture(gl.TEXTURE0, this.textureY);
|
|
this.deleteTexture(gl.TEXTURE1, this.textureCb);
|
|
this.deleteTexture(gl.TEXTURE2, this.textureCr);
|
|
|
|
gl.useProgram(null);
|
|
gl.deleteProgram(this.program);
|
|
gl.deleteProgram(this.loadingProgram);
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
|
gl.deleteBuffer(this.vertexBuffer);
|
|
|
|
this.canvas.removeEventListener('webglcontextlost', this.handleContextLostBound, false);
|
|
this.canvas.removeEventListener('webglcontextrestored', this.handleContextRestoredBound, false);
|
|
|
|
if (this.ownsCanvasElement) {
|
|
this.canvas.remove();
|
|
}
|
|
};
|
|
|
|
WebGLRenderer.prototype.resize = function(width, height) {
|
|
this.width = width|0;
|
|
this.height = height|0;
|
|
|
|
this.canvas.width = this.width;
|
|
this.canvas.height = this.height;
|
|
|
|
this.gl.useProgram(this.program);
|
|
|
|
var codedWidth = ((this.width + 15) >> 4) << 4;
|
|
this.gl.viewport(0, 0, codedWidth, this.height);
|
|
};
|
|
|
|
WebGLRenderer.prototype.createTexture = function(index, name) {
|
|
var gl = this.gl;
|
|
var texture = gl.createTexture();
|
|
|
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
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.uniform1i(gl.getUniformLocation(this.program, name), index);
|
|
|
|
return texture;
|
|
};
|
|
|
|
WebGLRenderer.prototype.createProgram = function(vsh, fsh) {
|
|
var gl = this.gl;
|
|
var program = gl.createProgram();
|
|
|
|
gl.attachShader(program, this.compileShader(gl.VERTEX_SHADER, vsh));
|
|
gl.attachShader(program, this.compileShader(gl.FRAGMENT_SHADER, fsh));
|
|
gl.linkProgram(program);
|
|
gl.useProgram(program);
|
|
|
|
return program;
|
|
};
|
|
|
|
WebGLRenderer.prototype.compileShader = function(type, source) {
|
|
var gl = this.gl;
|
|
var shader = gl.createShader(type);
|
|
gl.shaderSource(shader, source);
|
|
gl.compileShader(shader);
|
|
|
|
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
throw new Error(gl.getShaderInfoLog(shader));
|
|
}
|
|
|
|
return shader;
|
|
};
|
|
|
|
WebGLRenderer.prototype.allowsClampedTextureData = function() {
|
|
var gl = this.gl;
|
|
var texture = gl.createTexture();
|
|
|
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
gl.texImage2D(
|
|
gl.TEXTURE_2D, 0, gl.LUMINANCE, 1, 1, 0,
|
|
gl.LUMINANCE, gl.UNSIGNED_BYTE, new Uint8ClampedArray([0])
|
|
);
|
|
return (gl.getError() === 0);
|
|
};
|
|
|
|
WebGLRenderer.prototype.renderProgress = function(progress) {
|
|
var gl = this.gl;
|
|
|
|
gl.useProgram(this.loadingProgram);
|
|
|
|
var loc = gl.getUniformLocation(this.loadingProgram, 'progress');
|
|
gl.uniform1f(loc, progress);
|
|
|
|
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
};
|
|
|
|
WebGLRenderer.prototype.render = function(y, cb, cr, isClampedArray) {
|
|
if (!this.enabled) {
|
|
return;
|
|
}
|
|
|
|
var gl = this.gl;
|
|
var w = ((this.width + 15) >> 4) << 4,
|
|
h = this.height,
|
|
w2 = w >> 1,
|
|
h2 = h >> 1;
|
|
|
|
// In some browsers WebGL doesn't like Uint8ClampedArrays (this is a bug
|
|
// and should be fixed soon-ish), so we have to create a Uint8Array view
|
|
// for each plane.
|
|
if (isClampedArray && this.shouldCreateUnclampedViews) {
|
|
y = new Uint8Array(y.buffer),
|
|
cb = new Uint8Array(cb.buffer),
|
|
cr = new Uint8Array(cr.buffer);
|
|
}
|
|
|
|
gl.useProgram(this.program);
|
|
|
|
this.updateTexture(gl.TEXTURE0, this.textureY, w, h, y);
|
|
this.updateTexture(gl.TEXTURE1, this.textureCb, w2, h2, cb);
|
|
this.updateTexture(gl.TEXTURE2, this.textureCr, w2, h2, cr);
|
|
|
|
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
};
|
|
|
|
WebGLRenderer.prototype.updateTexture = function(unit, texture, w, h, data) {
|
|
var gl = this.gl;
|
|
gl.activeTexture(unit);
|
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
|
|
if (this.hasTextureData[unit]) {
|
|
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, w, h, gl.LUMINANCE, gl.UNSIGNED_BYTE, data);
|
|
}
|
|
else {
|
|
this.hasTextureData[unit] = true;
|
|
gl.texImage2D(
|
|
gl.TEXTURE_2D, 0, gl.LUMINANCE, w, h, 0,
|
|
gl.LUMINANCE, gl.UNSIGNED_BYTE, data
|
|
);
|
|
}
|
|
};
|
|
|
|
WebGLRenderer.prototype.deleteTexture = function(unit, texture) {
|
|
var gl = this.gl;
|
|
gl.activeTexture(unit);
|
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
gl.deleteTexture(texture);
|
|
};
|
|
|
|
WebGLRenderer.IsSupported = function() {
|
|
try {
|
|
if (!window.WebGLRenderingContext) {
|
|
return false;
|
|
}
|
|
|
|
var canvas = document.createElement('canvas');
|
|
return !!(
|
|
canvas.getContext('webgl') ||
|
|
canvas.getContext('experimental-webgl')
|
|
);
|
|
}
|
|
catch (err) {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
WebGLRenderer.SHADER = {
|
|
FRAGMENT_YCRCB_TO_RGBA: [
|
|
'precision mediump float;',
|
|
'uniform sampler2D textureY;',
|
|
'uniform sampler2D textureCb;',
|
|
'uniform sampler2D textureCr;',
|
|
'varying vec2 texCoord;',
|
|
|
|
'mat4 rec601 = mat4(',
|
|
'1.16438, 0.00000, 1.59603, -0.87079,',
|
|
'1.16438, -0.39176, -0.81297, 0.52959,',
|
|
'1.16438, 2.01723, 0.00000, -1.08139,',
|
|
'0, 0, 0, 1',
|
|
');',
|
|
|
|
'void main() {',
|
|
'float y = texture2D(textureY, texCoord).r;',
|
|
'float cb = texture2D(textureCb, texCoord).r;',
|
|
'float cr = texture2D(textureCr, texCoord).r;',
|
|
|
|
'gl_FragColor = vec4(y, cr, cb, 1.0) * rec601;',
|
|
'}'
|
|
].join('\n'),
|
|
|
|
FRAGMENT_LOADING: [
|
|
'precision mediump float;',
|
|
'uniform float progress;',
|
|
'varying vec2 texCoord;',
|
|
|
|
'void main() {',
|
|
'float c = ceil(progress-(1.0-texCoord.y));',
|
|
'gl_FragColor = vec4(c,c,c,1);',
|
|
'}'
|
|
].join('\n'),
|
|
|
|
VERTEX_IDENTITY: [
|
|
'attribute vec2 vertex;',
|
|
'varying vec2 texCoord;',
|
|
|
|
'void main() {',
|
|
'texCoord = vertex;',
|
|
'gl_Position = vec4((vertex * 2.0 - 1.0) * vec2(1, -1), 0.0, 1.0);',
|
|
'}'
|
|
].join('\n')
|
|
};
|
|
|
|
return WebGLRenderer;
|
|
|
|
})();
|
|
|