191 lines
5.9 KiB
JavaScript
191 lines
5.9 KiB
JavaScript
class Texture {
|
|
constructor(gl) {
|
|
this.gl = gl;
|
|
this.texture = gl.createTexture();
|
|
gl.bindTexture(gl.TEXTURE_2D, this.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);
|
|
}
|
|
|
|
bind(n, program, name) {
|
|
const gl = this.gl;
|
|
gl.activeTexture(gl.TEXTURE0 + n);
|
|
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
gl.uniform1i(gl.getUniformLocation(program, name), n);
|
|
}
|
|
|
|
fill(width, height, data, format) {
|
|
const gl = this.gl;
|
|
format = format || gl.LUMINANCE;
|
|
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format,
|
|
gl.UNSIGNED_BYTE, data);
|
|
}
|
|
};
|
|
|
|
class WebGLPlayer {
|
|
constructor(canvas) {
|
|
this.canvas = canvas;
|
|
this.gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
|
this.#init();
|
|
}
|
|
|
|
#init() {
|
|
if (!this.gl) {
|
|
console.log("[ERROR] WebGL not supported");
|
|
return;
|
|
}
|
|
|
|
const gl = this.gl;
|
|
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
|
|
|
const program = gl.createProgram();
|
|
|
|
const vertexShaderSource = [
|
|
"attribute highp vec3 aPos;",
|
|
"attribute vec2 aTexCoord;",
|
|
"varying highp vec2 vTexCoord;",
|
|
"void main(void) {",
|
|
" gl_Position = vec4(aPos, 1.0);",
|
|
" vTexCoord = aTexCoord;",
|
|
"}",
|
|
].join("\n");
|
|
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
|
|
gl.shaderSource(vertexShader, vertexShaderSource);
|
|
gl.compileShader(vertexShader);
|
|
{
|
|
const msg = gl.getShaderInfoLog(vertexShader);
|
|
if (msg) {
|
|
console.log("[ERROR] Vertex shader compile failed");
|
|
console.log(msg);
|
|
}
|
|
}
|
|
|
|
const fragmentShaderSource = [
|
|
"precision highp float;",
|
|
"varying lowp vec2 vTexCoord;",
|
|
"uniform sampler2D yTex;",
|
|
"uniform sampler2D uTex;",
|
|
"uniform sampler2D vTex;",
|
|
"const mat4 YUV2RGB = mat4(",
|
|
" 1.1643828125, 0, 1.59602734375, -.87078515625,",
|
|
" 1.1643828125, -.39176171875, -.81296875, .52959375,",
|
|
" 1.1643828125, 2.017234375, 0, -1.081390625,",
|
|
" 0, 0, 0, 1",
|
|
");",
|
|
"void main(void) {",
|
|
" // gl_FragColor = vec4(vTexCoord.x, vTexCoord.y, 0., 1.0);",
|
|
" gl_FragColor = vec4(",
|
|
" texture2D(yTex, vTexCoord).x,",
|
|
" texture2D(uTex, vTexCoord).x,",
|
|
" texture2D(vTex, vTexCoord).x,",
|
|
" 1",
|
|
" ) * YUV2RGB;",
|
|
"}",
|
|
].join("\n");
|
|
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
|
|
gl.shaderSource(fragmentShader, fragmentShaderSource);
|
|
gl.compileShader(fragmentShader);
|
|
{
|
|
const msg = gl.getShaderInfoLog(fragmentShader);
|
|
if (msg) {
|
|
console.log("[ERROR] Fragment shader compile failed");
|
|
console.log(msg);
|
|
}
|
|
}
|
|
|
|
gl.attachShader(program, vertexShader);
|
|
gl.attachShader(program, fragmentShader);
|
|
gl.linkProgram(program);
|
|
gl.useProgram(program);
|
|
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
console.log("[ERROR] Shader link failed");
|
|
}
|
|
|
|
const vertices = new Float32Array([
|
|
// positions // texture coords
|
|
-1.0, -1.0, 0.0, 0.0, 1.0, // bottom left
|
|
1.0, -1.0, 0.0, 1.0, 1.0, // bottom right
|
|
-1.0, 1.0, 0.0, 0.0, 0.0, // top left
|
|
1.0, 1.0, 0.0, 1.0, 0.0, // top right
|
|
])
|
|
const verticesBuffer = gl.createBuffer();
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, verticesBuffer);
|
|
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
|
|
|
|
const vertexPositionAttribute = gl.getAttribLocation(program, "aPos");
|
|
gl.enableVertexAttribArray(vertexPositionAttribute);
|
|
gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 20, 0);
|
|
|
|
const textureCoordAttribute = gl.getAttribLocation(program, "aTexCoord");
|
|
gl.enableVertexAttribArray(textureCoordAttribute);
|
|
gl.vertexAttribPointer(textureCoordAttribute, 2, gl.FLOAT, false, 20, 12);
|
|
|
|
gl.y = new Texture(gl);
|
|
gl.u = new Texture(gl);
|
|
gl.v = new Texture(gl);
|
|
gl.y.bind(0, program, "yTex");
|
|
gl.u.bind(1, program, "uTex");
|
|
gl.v.bind(2, program, "vTex");
|
|
}
|
|
|
|
render(frame) {
|
|
if (!this.gl) {
|
|
console.log("[ERROR] Render failed due to WebGL not supported");
|
|
return;
|
|
}
|
|
|
|
const gl = this.gl;
|
|
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
|
gl.clearColor(0.0, 0.0, 0.0, 0.0);
|
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
|
|
{
|
|
const width = frame.width;
|
|
const height = frame.height;
|
|
const bytes = frame.bytes;
|
|
|
|
const len_y = width * height;
|
|
const len_u = len_y >> 2;
|
|
const len_uv = len_y >> 1;
|
|
|
|
gl.y.fill(width, height, bytes.subarray(0, len_y));
|
|
gl.u.fill(width >> 1, height >> 1, bytes.subarray(len_y, len_y+len_u));
|
|
gl.v.fill(width >> 1, height >> 1, bytes.subarray(len_y+len_u, len_y+len_uv));
|
|
}
|
|
|
|
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
}
|
|
|
|
fullscreen() {
|
|
const canvas = this.canvas;
|
|
if (canvas.RequestFullScreen) {
|
|
canvas.RequestFullScreen();
|
|
} else if (canvas.webkitRequestFullScreen) {
|
|
canvas.webkitRequestFullScreen();
|
|
} else if (canvas.mozRequestFullScreen) {
|
|
canvas.mozRequestFullScreen();
|
|
} else if (canvas.msRequestFullscreen) {
|
|
canvas.msRequestFullscreen();
|
|
} else {
|
|
alert("This browser doesn't support fullscreen");
|
|
}
|
|
}
|
|
|
|
exitFullscreen() {
|
|
if (document.exitFullscreen) {
|
|
document.exitFullscreen();
|
|
} else if (document.webkitExitFullscreen) {
|
|
document.webkitExitFullscreen();
|
|
} else if (document.mozCancelFullScreen) {
|
|
document.mozCancelFullScreen();
|
|
} else if (document.msExitFullscreen) {
|
|
document.msExitFullscreen();
|
|
} else {
|
|
alert("Exit fullscreen doesn't work");
|
|
}
|
|
}
|
|
};
|