diff --git a/demo/index.html b/demo/index.html index b233c37..7db493f 100644 --- a/demo/index.html +++ b/demo/index.html @@ -23,18 +23,19 @@ - - -
diff --git a/src/repa-shader.js b/src/repa-shader.js index 32c2438..6835203 100644 --- a/src/repa-shader.js +++ b/src/repa-shader.js @@ -15,14 +15,16 @@ const CHUNKS = { #define m mouse #define t time #define f frame +#define b backbuffer #define o outColor precision highp float; uniform vec2 resolution; uniform vec2 mouse; uniform float time; uniform float frame; +uniform sampler2D backbuffer; out vec4 outColor; -`, // TODO sampler2D, MRT +`, // TODO MRT geekestStart: ` void main() { `, @@ -74,6 +76,17 @@ class RepaShader extends HTMLElement { // TODO resize + // postprocessing + this._postProgram = this._gl.createProgram(); + const pvs = this._createShader(this._postProgram, this.postVS, true); + const pfs = this._createShader(this._postProgram, this.postFS); + this._gl.linkProgram(this._postProgram); + this._gl.deleteShader(pvs); + this._gl.deleteShader(pfs); + this._postUniLocation = {}; + this._postUniLocation.texture = this._gl.getUniformLocation(this._postProgram, 'drawTexture'); + this._postAttLocation = this._gl.getAttribLocation(this._postProgram, 'position'); + this._gl.bindBuffer(this._gl.ARRAY_BUFFER, this._gl.createBuffer()); this._gl.bufferData(this._gl.ARRAY_BUFFER, new Float32Array([-1,1,0,-1,-1,0,1,1,0,1,-1,0]), this._gl.STATIC_DRAW); this._gl.disable(this._gl.DEPTH_TEST); @@ -154,8 +167,74 @@ class RepaShader extends HTMLElement { this._mousePosition = [x / this._target.width, 1 - y / this._target.height]; } + _resetBuffer(buff) { + if (!this._gl || !buff) { + return; + } + + // framebuffer + if (buff.framebuffer && this._gl.isFramebuffer(buff.framebuffer)) { + this._gl.bindFramebuffer(this._gl.FRAMEBUFFER, null); + this._gl.deleteFramebuffer(buff.framebuffer); + buff.framebuffer = null; + } + + // renderbuffer + if (buff.renderbuffer && this._gl.isRenderbuffer(buff.renderbuffer)) { + this._gl.bindRenderbuffer(this._gl.RENDERBUFFER, null); + this._gl.deleteRenderbuffer(buff.renderbuffer); + buff.renderbuffer = null; + } + + // texture + if (buff.texture && this._gl.isTexture(buff.texture)) { + this._gl.bindTexture(this._gl.TEXTURE_2D, null); + this._gl.deleteTexture(buff.texture); + buff.texture = null; + } + } + + _createBuffer(w, h) { + const buff = {}; + + // framebuffer + buff.framebuffer = this._gl.createFramebuffer(); + this._gl.bindFramebuffer(this._gl.FRAMEBUFFER, buff.framebuffer); + + // renderbuffer + buff.renderbuffer = this._gl.createRenderbuffer(); + this._gl.bindRenderbuffer(this._gl.RENDERBUFFER, buff.renderbuffer); + this._gl.renderbufferStorage(this._gl.RENDERBUFFER, this._gl.DEPTH_COMPONENT16, w, h); + this._gl.framebufferRenderbuffer(this._gl.FRAMEBUFFER, this._gl.DEPTH_ATTACHMENT, this._gl.RENDERBUFFER, buff.renderbuffer); + + // texture + buff.texture = this._gl.createTexture(); + this._gl.bindTexture(this._gl.TEXTURE_2D, buff.texture); + this._gl.texImage2D(this._gl.TEXTURE_2D, 0, this._gl.RGBA, w, h, 0, this._gl.RGBA, this._gl.UNSIGNED_BYTE, null); + this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR); + this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR); + this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE); + this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE); + this._gl.framebufferTexture2D(this._gl.FRAMEBUFFER, this._gl.COLOR_ATTACHMENT0, this._gl.TEXTURE_2D, buff.texture, 0); + + // unbind + this._gl.bindTexture(this._gl.TEXTURE_2D, null); + this._gl.bindRenderbuffer(this._gl.RENDERBUFFER, null); + this._gl.bindFramebuffer(this._gl.FRAMEBUFFER, null); + + return buff; + } + + _resetBuffers() { + this._resetBuffer(this.backbuffer); + this._resetBuffer(this.frontbuffer); + } + async reset(time) { this._resizeTarget(); + this._resetBuffers(); + this.backbuffer = this._createBuffer(this._target.width, this._target.height); + this.frontbuffer = this._createBuffer(this._target.width, this._target.height); if (this.hasAttribute('mouse')) { this._target.addEventListener('pointermove', this._onMouseMove.bind(this)); @@ -186,11 +265,7 @@ class RepaShader extends HTMLElement { return; } - const resolution = 'resolution'; - const mouse = 'mouse'; - const nowTime = 'time'; - const frame = 'frame'; - // TODO sound? backbuffer? mrt? + // TODO sound? mrt? textures? if (this._program) { this._gl.deleteProgram(this._program); @@ -198,10 +273,11 @@ class RepaShader extends HTMLElement { this.program = program; this._gl.useProgram(this.program); this._uniLocation = {}; - this._uniLocation.resolution = this._gl.getUniformLocation(this.program, resolution); - this._uniLocation.mouse = this._gl.getUniformLocation(this.program, mouse); - this._uniLocation.time = this._gl.getUniformLocation(this.program, nowTime); - this._uniLocation.frame = this._gl.getUniformLocation(this.program, frame); + this._uniLocation.resolution = this._gl.getUniformLocation(this.program, 'resolution'); + this._uniLocation.mouse = this._gl.getUniformLocation(this.program, 'mouse'); + this._uniLocation.time = this._gl.getUniformLocation(this.program, 'time'); + this._uniLocation.frame = this._gl.getUniformLocation(this.program, 'frame'); + this._uniLocation.backbuffer = this._gl.getUniformLocation(this.program, 'backbuffer'); this._attLocation = this._gl.getAttribLocation(this.program, 'position'); this._mousePosition= [0, 0]; @@ -226,6 +302,13 @@ class RepaShader extends HTMLElement { this._gl.useProgram(this.program); + this._gl.bindFramebuffer(this._gl.FRAMEBUFFER, this.frontbuffer.framebuffer); + + // backbuffer + this._gl.activeTexture(this._gl.TEXTURE0); + this._gl.bindTexture(this._gl.TEXTURE_2D, this.backbuffer.texture); + this._gl.uniform1i(this._uniLocation.backbuffer, 0); + this._gl.enableVertexAttribArray(this._attLocation); this._gl.vertexAttribPointer(this._attLocation, 3, this._gl.FLOAT, false, 0, 0); this._gl.clear(this._gl.COLOR_BUFFER_BIT); @@ -236,7 +319,22 @@ class RepaShader extends HTMLElement { this._gl.drawArrays(this._gl.TRIANGLE_STRIP, 0, 4); + // fill buffer + this._gl.useProgram(this._postProgram); + this._gl.bindFramebuffer(this._gl.FRAMEBUFFER, null); + this._gl.activeTexture(this._gl.TEXTURE0); + this._gl.bindTexture(this._gl.TEXTURE_2D, this.frontbuffer.texture); + this._gl.enableVertexAttribArray(this._postAttLocation); + this._gl.vertexAttribPointer(this._postAttLocation, 3, this._gl.FLOAT, false, 0, 0); + this._gl.clear(this._gl.COLOR_BUFFER_BIT); + this._gl.uniform1i(this._postUniLocation.texture, 0); + this._gl.drawArrays(this._gl.TRIANGLE_STRIP, 0, 4); + this._gl.flush(); + + // swap buffers + [this.frontbuffer, this.backbuffer] = [this.backbuffer, this.frontbuffer]; + // TODO draw callback } @@ -319,12 +417,33 @@ class RepaShader extends HTMLElement { in vec3 position; void main(){ gl_Position=vec4(position, 1.); -} -`; +}`; + } + + get postVS() { + return `#version 300 es +in vec3 position; +out vec2 vTexCoord; +void main() { + vTexCoord = (position.xy + 1.0) * 0.5; + gl_Position = vec4(position, 1.); +}`; + } + + get postFS() { + return `#version 300 es +precision mediump float; +uniform sampler2D drawTexture; +in vec2 vTexCoord; +layout (location = 0) out vec4 outColor; +void main() { + outColor = texture(drawTexture, vTexCoord); +}`; } async getFS() { // auto guessing mode + // - starts with #version -> raw // - contains `precision` -> twigl classic300es // - no `precision`, but has `main()` -> twigl geeker300es // - no `precision`, no `main()` -> twigl geekest300es @@ -350,6 +469,8 @@ void main(){ let end = ''; let snippets = ''; switch (mode) { + case 'raw': + return this._fsSource; case 'classic': start = CHUNKS.es300; break;