Reputation: 685
I have this situation with framebuffers which I don't understand at all. Have a look at the following:
class Shader {
constructor(cvs, dim) {
cvs.width = dim[0];
cvs.height = dim[1];
this.gl = twgl.getContext(cvs);
this.bfi = twgl.createBufferInfoFromArrays(this.gl, {
a_position: {
numComponents: 2,
data: [-1, -1, -1, 3, 3, -1]
}
});
this.pgi = {
write: twgl.createProgramInfo(this.gl, ["vs", "fs_write"]),
read: twgl.createProgramInfo(this.gl, ["vs", "fs_read"])
};
this.fbs = Array(2)
.fill()
.map(() => twgl.createFramebufferInfo(this.gl));
this.gl.useProgram(this.pgi.write.program);
twgl.setUniforms(this.pgi.write, {
u_resolution: dim
});
this.gl.useProgram(this.pgi.read.program);
twgl.setUniforms(this.pgi.read, {
u_framebuffer: this.fbs[0].attachments[0],
u_resolution: dim
});
}
ldImg(src, handler) {
this.gl.useProgram(this.pgi.write.program);
twgl.setUniforms(this.pgi.write, {
u_texture: twgl.createTexture(
this.gl,
{ src },
handler
)
});
}
frame() {
twgl.bindFramebufferInfo(this.gl, this.fbs[0]);
this.gl.useProgram(this.pgi.write.program);
twgl.setBuffersAndAttributes(this.gl, this.pgi.write, this.bfi);
twgl.drawBufferInfo(this.gl, this.bfi);
twgl.bindFramebufferInfo(this.gl, null);
this.gl.useProgram(this.pgi.read.program);
// twgl.setUniforms(this.pgi.read, {
// u_framebuffer: this.fbs[0].attachments[0]
// });
twgl.setBuffersAndAttributes(this.gl, this.pgi.read, this.bfi);
twgl.drawBufferInfo(this.gl, this.bfi);
// this.fbs = [this.fbs[1], this.fbs[0]];
}
}
(function main() {
const dim = [512, 512];
const shr = new Shader(document.querySelector("canvas"), dim);
shr.ldImg("https://assets.codepen.io/854924/ad.png", frame);
function frame(ms) {
shr.frame();
window.requestAnimationFrame(frame);
}
})();
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<script id="vs" type="x-shader/x-vertex">
attribute vec4 a_position;
void main() {
gl_Position = a_position;
}
</script>
<script id="fs_write" type="x-shader/x-fragment">
precision highp float;
uniform vec2 u_resolution;
uniform sampler2D u_texture;
void main() {
gl_FragColor = texture2D(u_texture, (gl_FragCoord.xy + 1.0) / u_resolution);
}
</script>
<script id="fs_read" type="x-shader/x-fragment">
precision highp float;
uniform sampler2D u_framebuffer;
uniform vec2 u_resolution;
void main() {
gl_FragColor = vec4(
1.0 - texture2D(u_framebuffer, gl_FragCoord.xy / u_resolution).rgb,
1.0
);
}
</script>
<canvas></canvas>
I tried to keep things as simple as possible. There are two programs, write
and read
, where write
reads from a texture, translates by one pixel and writes to the output. Then read
reads from a framebuffer, inverts the image and writes to the output. In the current situation, I let write
render to the framebuffer and read
render to the screen. Everything works as expected.
Now if I uncomment the first commented line in the JS, I get the error Feedback loop formed between Framebuffer and active Texture
, which is weird, as the uniform u_framebuffer
in read
is always set to the same framebuffer, I just rebind a uniform already bound. Also it should create no feedback loop, as write
doesn't have the framebuffer bound while read
doesn't attempt to render to the framebuffer.
If I uncomment both commented lines in the JS, so that the framebuffer get switched out every frame, I can see the texture 'walk' across the screen. So that is clearly a feedback loop (the one pixel offset getting accumulated), but I can't see where it is coming from. write
just reads from the texture, which should be absolutely static, but somehow it seems to consume its own output.
I'm quite lost here, either there is some logic in uniforms and framebuffers which I don't understand at all, or some bug I never encountered before?
Upvotes: 0
Views: 347
Reputation: 8123
Textures are not like uniforms, textures are bound to one of the globally available texture units which is a stateful operation, aka once you bind a texture to a unit it remains bound until you unbind it, just like framebuffers.
So when you call setUniforms
with a texture, it binds the texture to a unit and tells the sampler which of the global units to read from, omitting this results in the following shaders reading from whatever was bound previously, in your case the framebuffer texture. So what you want to do is rebind the read texture before you render with your read program:
this.gl.useProgram(this.pgi.write.program);
twgl.setUniforms(this.pgi.write, {
u_texture: this.readTexture //@TODO: store read texture in ldImg
});
twgl.setBuffersAndAttributes(this.gl, this.pgi.write, this.bfi);
twgl.drawBufferInfo(this.gl, this.bfi);
twgl.bindFramebufferInfo(this.gl, null);
this.gl.useProgram(this.pgi.read.program);
twgl.setUniforms(this.pgi.read, {
u_framebuffer: this.fbs[0].attachments[0]
});
Upvotes: 1