Search code examples
texturesframebuffertexture2drender-to-texturewebgl2

read and write to different textures within same framebuffer


Is it possible to read from texture A and write to texture B that both use the same FBO? I don't believe this has been covered here already. Someone asked the question about OpenGL, but not about WebGL. It seems like the answer is no, based on the answer to this post, but if someone could tell me definitively whether reading/writing to textures in the same fbo is possible it'd really help me. In my code I get the following error:

WebGL warning: drawElementsInstanced: Texture level 0 would be read by TEXTURE_2D unit 0, but written by framebuffer attachment COLOR_ATTACHMENT1, which would be illegal feedback.

the following snippet generates an 'illegal feedback' warning. I know the code may not look/be useful but the point is accomplishing the read/write. To sumarize what I expect to happen is textureA (or call it texture 1) should be used to render to texture B ( aka texture 2 ). I should then see something on the screen; mostly likely 4 greed squares:

/* YOU CAN PROBABLY IGNORE THIS AS IT'S INITIALIZATION CODE */

const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl2');
var fb;
var textures = [];

var tex_datei1 = new Array( 4*64*64 ).fill(9);

var vbo = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER , vbo );

var vertices = new Array( 4*64*64 ).fill( 0 );

gl.bindBuffer( gl.ARRAY_BUFFER , vbo );
gl.bufferData( gl.ARRAY_BUFFER , new Float32Array( vertices ) , gl.STATIC_DRAW );

var ibo = gl.createBuffer();
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER , ibo );

var indices = [ ...Array( vertices.length/4 ).keys() ];
indices[0] = 0;
indices[1] = 1;
indices[2] = 2;
indices[3] = 2;
indices[4] = 3;
indices[5] = 1;
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( indices ), gl.STATIC_DRAW);

var vc = `#version 300 es
 precision mediump float;

 uniform sampler2D u_image0;
 uniform sampler2D u_image1;

 in vec3 coordinates;

void main(void) {

  float x = coordinates.x;
  float y = coordinates.y;

  vec4 sample_A = texture( u_image0 , vec2( x , y ) );
  sample_A.x *= 64.0 * 4.0;
  sample_A.y *= 64.0 * 4.0;

  gl_Position = vec4( sample_A.x/10.0 , sample_A.y/10.0 , 0.0 , 1.0 );

  gl_PointSize = 30.0;

}
`;

var vs = gl.createShader( gl.VERTEX_SHADER );
gl.shaderSource( vs , vc );
gl.compileShader(vs);
if( !gl.getShaderParameter( vs , gl.COMPILE_STATUS ) ){
  console.log( gl.COMPILE_STATUS );
  console.log( gl.getShaderInfoLog(vs) );
}

var fc = `#version 300 es
precision highp float;
out vec4 color;
void main(void) {
  color = vec4( 0.5 , 0.5 , 0.0 , 1.0 );
}
`;

var fs = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fs, fc);
gl.compileShader(fs);
if( !gl.getShaderParameter( fs , gl.COMPILE_STATUS ) ){
  console.log( gl.COMPILE_STATUS );
  console.log( gl.getShaderInfoLog(fs) );
}

var program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
gl.useProgram( program);

var coordinates_u_loc = gl.getAttribLocation(program, "coordinates");
var u_image0_loc = gl.getUniformLocation( program , "u_image0");
var die_berechnung_u_image1Location = gl.getUniformLocation( program , "u_image1");

gl.bindBuffer( gl.ARRAY_BUFFER , vbo );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER , ibo );
gl.enableVertexAttribArray( coordinates_u_loc );
gl.vertexAttribPointer( coordinates_u_loc , 4 , gl.FLOAT , false , 0 , 0 );
 
 
 
 
 
 
 
 
/* HERE IS WHERE I GET INTO FRAMEBUFFER SETUP */

fb = gl.createFramebuffer();
gl.bindFramebuffer( gl.FRAMEBUFFER , fb );


gl.uniform1i( u_image0_loc , 0);
//gl.uniform1i( u_image1_loc , 1);


var internal_formats = [ gl.RGBA , gl.RGBA ];
var formats = [ gl.RGBA , gl.RGBA ];
var types = [ gl.UNSIGNED_BYTE , gl.UNSIGNED_BYTE ];

var datas = [ new Uint8Array( tex_datei1.slice() ) , new Uint8Array( tex_datei1.slice() ) ];

for( var i = 0 ; i < datas.length ; i++ ){

  var texture = gl.createTexture();
  gl.bindTexture( gl.TEXTURE_2D , texture );

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

  gl.texImage2D( gl.TEXTURE_2D ,
    0 ,
    internal_formats[i] ,
    64 ,
    64 ,
    0 ,
    formats[ i ] ,
    types[ i ] ,
    datas[ i ]
  );

  textures.push( texture );

 }

/* HERE MAY LIE MY ISSUE AS I HAVE NO IDEA HOW TO SPECIFY THAT THE SECOND TEXTURE SHOULD BE WRITTEN TO. I'VE TRIED MESSING AROUND WITH THE LAYERS PARAMETER BUT THAT JUST CREATES MIPS ISSUES. */

gl.bindTexture( gl.TEXTURE_2D , textures[ 0 ] );
var attachmentPoint = gl.COLOR_ATTACHMENT0;
gl.framebufferTexture2D( gl.FRAMEBUFFER , attachmentPoint , gl.TEXTURE_2D , textures[ 0 ] , 0 );

gl.bindTexture( gl.TEXTURE_2D , textures[ 1 ] );
var attachmentPoint = gl.COLOR_ATTACHMENT1;
gl.framebufferTexture2D( gl.FRAMEBUFFER , attachmentPoint , gl.TEXTURE_2D , textures[ 1 ] , 0 );

var er = gl.checkFramebufferStatus( gl.FRAMEBUFFER );
if( er !== 36053 ){
  console.log( er );
}








 /* DRAW */

 gl.clearColor( 0.0 , 0.0 , 0.0 , 0.0 );
 gl.enable( gl.DEPTH_TEST );
 gl.clear( gl.COLOR_BUFFER_BIT );

 gl.viewport( 0,0, 64 , 64 );

 gl.activeTexture(gl.TEXTURE1);
 gl.bindTexture(gl.TEXTURE_2D, textures[1] );

 gl.drawElements( gl.POINTS , indices.length , gl.UNSIGNED_SHORT , 0 );
										
<!DOCTYPE html>
<html>
    <head>
        
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <body>
      <canvas width="64" heigh="64" style="width:64px; height:64px; border:thick solid #0000FF; "></canvas>

    </body>
    
</html>


Solution

  • Is it possible to read from texture A and write to texture B that both use the same FBO

    No, it is not possible. Why would you ever want to do that?

    What are you trying to accomplish? Sometimes writing to A and sometimes writing to B? If so make 2 fbos. fboA and fboB, attach texture A to fboA and B to fboB. When you want to write to A bind fboA. When you want to write to B bind fboB. If you sometimes want to write to both make fboAB and attach both A and B.

    As it is though the code is not setup to ever write to B when you have both textures attached. See: WebGL 2.0: Multiple output textures from the same program

    update

    The code is attaching both texture[0] and texture[1] to a framebuffer. So at this point both texture[0] and texture[1] are setup for being written to. Also at that point texture[1] is bound to texture unit 0 for reading.

    The shader only references one texture, even though it declares both u_image0 and u_image1 you never use u_image1.

    The code then assigns texture[1] to texture unit 1 but it never sets u_image0 so u_image0 is using texture unit 0 since uniforms default to 0.

    So then the code calls gl.drawArrays. Because the framebuffer is using both texture[0] and texture[1] for writing and because the shader it using texture[1] on texture unit 0 for reading that's a feedback loop and so the code gets an error.

    Here's a script you can paste at the top of the snippet HTML

    <script src="https://greggman.github.io/webgl-helpers/webgl-check-framebuffer-feedback.js"></script>
    

    from https://greggman.github.io/webgl-helpers/

    When I do that and check the JavaScript console I see this error

    Uncaught Error: WebGL feedback loop: texture on uniform: u_image0 bound to texture unit 0 is also attached to current framebuffer on attachment: COLOR_ATTACHMENT1

    If you want use texture A to generate texture B then at init time you attach B and only B to a framebuffer object

    // at init time
    const fb = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
                            gl.TEXTURE_2D, textureB, 0);
    

    Then at render time you bind that framebuffer

    // make gl.drawXXX write to textureB
    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
    

    Bind texture A to some texture unit.

    const unit = 7;
    gl.activeTexture(gl.TEXTURE0 + unit);
    gl.bindTexture(gl.TEXTURE, textureA); 
    

    Tell the shader sampler what unit you put the texture on

    gl.uniform1i(u_image0Location, unit);
    

    More info here