From dba6c9423697e7b8093daa18c538c2a7d04c5932 Mon Sep 17 00:00:00 2001 From: Zellyn Hunter Date: Tue, 8 May 2018 22:50:29 -0400 Subject: [PATCH] webgl: working on renderImage() --- index.html | 3 +- screenEmu.js | 225 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 219 insertions(+), 9 deletions(-) diff --git a/index.html b/index.html index d9dc33f..5de3037 100644 --- a/index.html +++ b/index.html @@ -224,8 +224,7 @@ void main() { async function tryScreenView() { let canvas = document.getElementById("d"); - let gl = canvas.getContext("webgl"); - let sv = new screenEmu.ScreenView(gl); + let sv = new screenEmu.ScreenView(canvas); await sv.initOpenGL(); let imageInfo = new screenEmu.ImageInfo(0, 0, 0, 0, 0, 0, new ImageData(10, 10)); diff --git a/screenEmu.js b/screenEmu.js index 2432f36..1f6da58 100644 --- a/screenEmu.js +++ b/screenEmu.js @@ -244,6 +244,62 @@ void main(void) }; } + const Point = class { + constructor(x, y) { + this.x = x; + this.y = y; + } + } + + const Size = class { + constructor(width, height) { + this.width = width; + this.height = height; + } + + copy() { + return new Size(this.width, this.height); + } + } + + const Rect = class { + constructor(x, y, width, height) { + this.origin = new Point(x, y); + this.size = new Size(width, height); + } + + get x() { + return this.origin.x; + } + + get y() { + return this.origin.y; + } + + get width() { + return this.size.width; + } + + get height() { + return this.size.height; + } + + get l() { + return this.origin.x; + } + + get r() { + return this.origin.x + this.size.width; + } + + get t() { + return this.origin.y; + } + + get b() { + return this.origin.y + this.size.height; + } + } const Vector = class { constructor(n) { @@ -432,6 +488,11 @@ void main(void) "IMAGE_PERSISTENCE", ]; + const BUFFER_NAMES = [ + "POSITION", + "TEXCOORD", + ]; + const SHADER_NAMES = [ "COMPOSITE", "DISPLAY", @@ -490,11 +551,15 @@ void main(void) this.height = height; this.glTexture = glTexture; } + + get size() { + return new Size(this.width, this.height); + } }; const DisplayConfiguration = class { constructor() { - this.videoDecoder = "CANVAS_RGB"; + this.videoDecoder = "CANVAS_YUV"; this.videoBrightness = 0; this.videoContrast = 1; this.videoSaturation = 1; @@ -547,26 +612,36 @@ void main(void) get height() { return this.data.height; } + + get size() { + return new Size(this.data.width, this.data.height); + } }; const ScreenView = class { - constructor(gl) { + constructor(canvas) { + const gl = canvas.getContext("webgl"); const float_texture_ext = gl.getExtension('OES_texture_float'); if (float_texture_ext == null) { throw new Error("WebGL extension 'OES_texture_float' unavailable"); } + this.canvas = canvas; this.gl = gl; this.textures = {}; this.shaders = {}; + this.buffers = {}; this.image = null; this.display = null; - this.configurationChanged = true; - this.imageChanged = true; this.imageSampleRate = null; this.imageBlackLevel = null; this.imageWhiteLevel = null; this.imageSubcarrier = null; + this.viewportSize = new Size(0, 0); + + this.configurationChanged = true; + this.imageChanged = true; + this.shaderEnabled = true; } get image() { @@ -595,6 +670,10 @@ void main(void) this.textures[name] = new TextureInfo(0, 0, gl.createTexture()); } + for (let name of BUFFER_NAMES) { + this.buffers[name] = gl.createBuffer(); + } + await this.loadTextures(); gl.pixelStorei(gl.PACK_ALIGNMENT, 1); @@ -610,6 +689,10 @@ void main(void) gl.deleteTexture(this.textures[name].glTexture); } + for (let name of BUFFER_NAMES) { + gl.deleteBuffer(this.buffers[name]); + } + this.deleteShaders(); } @@ -663,8 +746,18 @@ void main(void) } vsync() { + const gl = this.gl; + resizeCanvas(this.canvas); + const canvasWidth = this.canvas.width; + const canvasHeight = this.canvas.height; + // if viewport size has changed: - // glViewPort(0, 0, new_width, new_height); + if ((this.viewportSize.width != canvasWidth) + || (this.viewportSize.height != this.canvasHeight)) { + this.viewportSize = new Size(canvasWidth, canvasHeight); + gl.viewport(0, 0, canvasWidth, canvasHeight); + this.configurationChanged = true; + } if (this.imageChanged) { this.uploadImage(); @@ -752,7 +845,7 @@ void main(void) if (!renderShader || !displayShader) return; - const isCompositeDecoder = (renderShaderName == "RGB"); + const isCompositeDecoder = (renderShaderName == "COMPOSITE"); // Render shader gl.useProgram(renderShader); @@ -967,8 +1060,126 @@ void main(void) this.display.displayLuminanceGain); } - // TODO(zellyn): implement renderImage() { + const gl = this.gl; + const [renderShader, renderShaderName] = this.getRenderShader(); + if (!renderShader || !this.shaderEnabled) + return; + + const isCompositeDecoder = (renderShaderName == "COMPOSITE"); + + gl.useProgram(renderShader); + + const texSize = this.textures["IMAGE_IN"].size; + this.resizeTexture("IMAGE_DECODED", texSize.width, texSize.height); + + gl.uniform1i(gl.getUniformLocation(renderShader, "texture"), 0); + gl.uniform2f(gl.getUniformLocation(renderShader, "textureSize"), + texSize.width, texSize.height); + + if (isCompositeDecoder) + { + gl.uniform1i(gl.getUniformLocation(renderShader, "phaseInfo"), 1); + + gl.activeTexture(gl.TEXTURE1); + + gl.bindTexture(gl.TEXTURE_2D, this.textures["IMAGE_PHASEINFO"].glTexture); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + + gl.activeTexture(gl.TEXTURE0); + } + + // Render to the back buffer, to avoid using FBOs + // (support for vanilla OpenGL 2.0 cards) + + // I think webgl is rendering to the back buffer anyway, until + // we stop executing javascript statements and give control + // back, at which point it flips. So we might not need + // this. Although truly, I'm not certain what it's doing. If we + // *do* end up needing it, we'll have to go full webgl2. + + // glReadBuffer(GL_BACK); + + const imageSize = this.image.size; + + for (let y = 0; y < this.image.height; y += this.viewportSize.height) { + for (let x = 0; x < this.image.width; x += this.viewportSize.width) { + // Calculate rects + const clipSize = this.viewportSize.copy(); + + if ((x + clipSize.width) > imageSize.width) + clipSize.width = imageSize.width - x; + if ((y + clipSize.height) > imageSize.height) + clipSize.height = imageSize.height - y; + const textureRect = new Rect(x / texSize.width, + y / texSize.height, + clipSize.width / texSize.width, + clipSize.height / texSize.height); + const canvasRect = new Rect(-1, + -1, + 2 * clipSize.width / this.viewportSize.width, + 2 * clipSize.height / this.viewportSize.height); + + // Render + gl.bindTexture(gl.TEXTURE_2D, this.textures["IMAGE_IN"].glTexture); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + + // What is this, some ancient fixed pipeline nonsense? No way. + // glLoadIdentity(); + + const positionLocation = gl.getAttribLocation(renderShader, "a_position"); + const texcoordLocation = gl.getAttribLocation(renderShader, "a_texCoord"); + const positionBuffer = this.buffers["POSITION"]; + gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); + const p_x1 = canvasRect.l; + const p_x2 = canvasRect.r; + const p_y1 = canvasRect.t; + const p_y2 = canvasRect.b; + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ + p_x1, p_y1, + p_x2, p_y1, + p_x1, p_y2, + p_x1, p_y2, + p_x2, p_y1, + p_x2, p_y2, + ]), gl.STATIC_DRAW); + + const texcoordBuffer = this.buffers["TEXCOORD"]; + gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer); + const t_x1 = textureRect.l; + const t_x2 = textureRect.r; + const t_y1 = textureRect.t; + const t_y2 = textureRect.b; + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ + t_x1, t_y1, + t_x2, t_y1, + t_x1, t_y2, + t_x1, t_y2, + t_x2, t_y1, + t_x2, t_y2, + ]), gl.STATIC_DRAW); + + gl.clearColor(0, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.enableVertexAttribArray(positionLocation); + gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); + gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); + + gl.enableVertexAttribArray(texcoordLocation); + gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer); + gl.vertexAttribPointer(texcoordLocation, 2, gl.FLOAT, false, 0, 0); + + gl.drawArrays(gl.TRIANGLES, 0, 6); + } + } + + + // TODO(zellyn): implement } // TODO(zellyn): implement