Files
2025-12-09 20:22:03 +08:00

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");
}
}
};