From 7fe6495a275ef405ae61358f0a4dd09cdee8fe66 Mon Sep 17 00:00:00 2001 From: cpsdqs Date: Sun, 29 Oct 2017 16:09:10 +0100 Subject: [PATCH] gl renderer: use a frame buffer --- js/term/screen_layout.js | 1 + js/term/webgl_renderer.js | 165 +++++++++++++++++++++++++++++++++----- 2 files changed, 145 insertions(+), 21 deletions(-) diff --git a/js/term/screen_layout.js b/js/term/screen_layout.js index 74300e7..677ed87 100644 --- a/js/term/screen_layout.js +++ b/js/term/screen_layout.js @@ -13,6 +13,7 @@ module.exports = class ScreenLayout extends EventEmitter { try { this.renderer = new WebGLRenderer(this.canvas) } catch (err) { + console.error(err) this.renderer = new CanvasRenderer(this.canvas) } diff --git a/js/term/webgl_renderer.js b/js/term/webgl_renderer.js index af29058..9fd8a02 100644 --- a/js/term/webgl_renderer.js +++ b/js/term/webgl_renderer.js @@ -56,19 +56,23 @@ module.exports = class WebGLRenderer extends EventEmitter { this.reverseVideo = false this.hasBlinkingCells = false this.statusScreen = null + this.backgroundImage = null this.blinkStyleOn = false this.blinkInterval = null this.cursorBlinkOn = false this.cursorBlinkInterval = null + this.redrawLoop = false this.resetDrawn(100, 100) + this.initTime = Date.now() - // start blink timers + this.init() + + // start loops and timers this.resetBlink() this.resetCursorBlink() - - this.init() + this.startDrawLoop() } render (reason, data) { @@ -82,7 +86,13 @@ module.exports = class WebGLRenderer extends EventEmitter { } resetDrawn (width, height) { - this.gl.clearColor(...this.getColor(this.defaultBG)) + if (this.backgroundImage) { + this.gl.clearColor(0, 0, 0, 0) + this.canvas.style.backgroundColor = getColor(this.defaultBG, this.palette) + } else { + this.gl.clearColor(...this.getColor(this.defaultBG)) + this.canvas.style.backgroundColor = null + } if (width && height) { this.gl.viewport(0, 0, width, height) } @@ -283,6 +293,43 @@ void main() { } `) + let fboShader = this.compileShader(` +precision mediump float; +attribute vec2 position; +uniform mat4 projection; +varying highp vec2 tex_coord; +void main() { + gl_Position = projection * vec4(position, 0.0, 1.0); + tex_coord = position; +} + `, ` +precision highp float; +uniform sampler2D texture; +uniform vec2 pixel_scale; +uniform float time; +varying highp vec2 tex_coord; +void main() { + gl_FragColor = texture2D(texture, tex_coord); + /* uncomment for CRT-ish effect + vec4 sum = vec4(0); + + for (int i = -4; i <= 4; i++) { + for (int j = -4; j <= 4; j++) { + sum += texture2D(texture, tex_coord + vec2(j, i) * pixel_scale) * 0.07; + } + } + float factor = 0.05 + 0.02 * sin(time * 5.0); + if (mod(tex_coord.y / pixel_scale.y, 10.0) < 5.0) { + factor += 0.1; + } + float beam_y = (mod(-time, 9.0) - 1.5) / 6.0; + if (abs(tex_coord.y - beam_y) < 0.05) { + factor += 0.2 * cos((tex_coord.y - beam_y) / 0.05 * 1.57); + } + gl_FragColor = sum * sum * factor + texture2D(texture, tex_coord); */ +} + `) + this.bgShader = { shader: bgShader, attributes: { @@ -314,6 +361,19 @@ void main() { } } + this.fboShader = { + shader: fboShader, + attributes: { + position: gl.getAttribLocation(fboShader, 'position') + }, + uniforms: { + projection: gl.getUniformLocation(fboShader, 'projection'), + pixelScale: gl.getUniformLocation(fboShader, 'pixel_scale'), + time: gl.getUniformLocation(fboShader, 'time'), + texture: gl.getUniformLocation(fboShader, 'texture') + } + } + let buffer = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, buffer) gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ @@ -333,14 +393,50 @@ void main() { this.drawSquare = () => { gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4) } + + // frame buffers + + let maxBuffers = gl.getParameter(gl.getExtension('WEBGL_draw_buffers').MAX_COLOR_ATTACHMENTS_WEBGL) + let createBuffer = i => { + let buffer = gl.createFramebuffer() + let texture = gl.createTexture() + + gl.bindFramebuffer(gl.FRAMEBUFFER, buffer) + gl.bindTexture(gl.TEXTURE_2D, texture) + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null) + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, texture, 0) + 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) + + return { buffer, texture } + } + + if (maxBuffers >= 2) { + this.buffers = { + drawing: createBuffer(0), + display: createBuffer(1) + } + } else { + let buffer = createBuffer(0) + this.buffers = { drawing: buffer, display: buffer } + } } draw (reason) { const { gl, width, height, padding, devicePixelRatio, statusScreen } = this let { screen, screenFG, screenBG, screenAttrs } = this + // ;[this.buffers.drawing, this.buffers.display] = [this.buffers.display, this.buffers.drawing] + + let drawingBuffer = this.buffers.drawing + gl.bindFramebuffer(gl.FRAMEBUFFER, drawingBuffer.buffer) + gl.activeTexture(gl.TEXTURE0) + gl.bindTexture(gl.TEXTURE_2D, drawingBuffer.texture) + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.drawingBufferWidth, gl.drawingBufferHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null) + if (statusScreen) { - this.startDrawLoop() + this.redrawLoop = true screen = new Array(width * height).fill(' ') screenFG = new Array(width * height).fill(this.defaultFG) @@ -361,7 +457,7 @@ void main() { screen[i] = '*' } } - } + } else this.redrawLoop = false if (this.debug && this._debug) this._debug.drawStart(reason) @@ -401,12 +497,12 @@ void main() { if (!(cell in screen)) continue if (statusScreen) isCursor = false - // let isDefaultBG = false + let isDefaultBG = false if (!(attrs & ATTR_FG)) fg = this.defaultFG if (!(attrs & ATTR_BG)) { bg = this.defaultBG - // isDefaultBG = true + isDefaultBG = true } if (attrs & ATTR_INVERSE) [fg, bg] = [bg, fg] // swap - reversed character colors @@ -423,20 +519,22 @@ void main() { bg = -2 } - gl.uniform2f(this.bgShader.uniforms.charPos, x, y) - gl.uniform4f(this.bgShader.uniforms.color, ...this.getColor(bg)) + if (!this.backgroundImage || !isDefaultBG) { + gl.uniform2f(this.bgShader.uniforms.charPos, x, y) + gl.uniform4f(this.bgShader.uniforms.color, ...this.getColor(bg)) - let extendX = 0 - let extendY = 0 + let extendX = 0 + let extendY = 0 - if (x === 0) extendX = -1 - if (x === width - 1) extendX = 1 - if (y === 0) extendY = -1 - if (y === height - 1) extendY = 1 + if (x === 0) extendX = -1 + if (x === width - 1) extendX = 1 + if (y === 0) extendY = -1 + if (y === height - 1) extendY = 1 - gl.uniform2f(this.bgShader.uniforms.extend, extendX, extendY) + gl.uniform2f(this.bgShader.uniforms.extend, extendX, extendY) - this.drawSquare() + this.drawSquare() + } if (text.trim() || isCursor || attrs) { let fontIndex = 0 @@ -452,13 +550,13 @@ void main() { } this.useShader(this.charShader, projection) - gl.activeTexture(gl.TEXTURE0) + gl.activeTexture(gl.TEXTURE1) for (let key in textCells) { let { font, text } = textCells[key][0] let texture = this.fontCache.getChar(font, text) gl.bindTexture(gl.TEXTURE_2D, texture) - gl.uniform1i(this.charShader.uniforms.texture, 0) + gl.uniform1i(this.charShader.uniforms.texture, 1) for (let cell of textCells[key]) { let { x, y, fg, bg, attrs, isCursor } = cell @@ -494,9 +592,31 @@ void main() { } } + gl.bindFramebuffer(gl.FRAMEBUFFER, null) + this.drawFrame() + if (this.debug && this._debug) this._debug.drawEnd() } + drawFrame () { + const { gl } = this + let drawingBuffer = this.buffers.drawing + + gl.clear(gl.COLOR_BUFFER_BIT) + gl.activeTexture(gl.TEXTURE0) + gl.bindTexture(gl.TEXTURE_2D, drawingBuffer.texture) + this.useShader(this.fboShader, [ + 2, 0, 0, 0, + 0, 2, 0, 0, + 0, 0, 1, 0, + -1, -1, 0, 1 + ]) + gl.uniform2f(this.fboShader.uniforms.pixelScale, 1 / gl.drawingBufferWidth, 1 / gl.drawingBufferHeight) + gl.uniform1i(this.fboShader.uniforms.texture, 0) + gl.uniform1f(this.fboShader.uniforms.time, ((Date.now() - this.initTime) / 1000) % 86400) + this.drawSquare() + } + startDrawLoop () { if (this._drawTimerThread) return let threadID = Math.random().toString(36) @@ -511,7 +631,10 @@ void main() { drawTimerLoop (threadID) { if (!threadID || threadID !== this._drawTimerThread) return window.requestAnimationFrame(() => this.drawTimerLoop(threadID)) - this.draw('draw-loop') + if (this.redrawLoop) this.draw('draw-loop') + // uncomment for an update every frame (GPU-intensive) + // (also, lots of errors. TODO: investigate) + // this.drawFrame() } /**