Search code examples
webglopengl-es-2.0webgl2opengl-es-3.0

Can an layout(location=n) out skip an index for drawBuffers in WebGL?


I'm working on MRT in my graphics engine.

An interesting point i'm at (and aim to fix) has my generated fragment shader spitting out:

layout(location = 0) out vec4 thing1;
layout(location = 2) out vec4 thing2;

The drawBuffers call on the application side calls something like this:

gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.NONE, gl.COLOR_ATTACHMENT1]);

However, I'm getting an error:

WebGL: INVALID_OPERATION: drawBuffers: COLOR_ATTACHMENTi_EXT or NONE

So obviously, this would appear to not be allowed. From the documentation I've read from a wikipedia article discussing it:

https://www.khronos.org/opengl/wiki/Fragment_Shader

It states along the lines that the layout location specified refers to the array index specified from the drawBuffers call. So, in theory I would have thought this shader to configuration would be valid.

What am I missing from my understanding that makes this not work?

I ask for understanding mostly and not to fix my program, my generator will correct the indices when I'm done to be 'correct' with no location index skipping.

Update: As noted below, you CAN skip layout locations in the shader. My issue was the improper formatting of the drawBuffers call where I had COLOR_ATTACHMENT1 in the index where ONLY COLOR_ATTACHMENT2 is valid.


Solution

  • This is wrong

    gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.NONE, gl.COLOR_ATTACHMENT1]);
    

    the i-th attachment must be gl.NONE or gl.COLOR_ATTACHMENTi

    so it has to be this

    gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.NONE, gl.COLOR_ATTACHMENT2]);
    

    function main() {
      const gl = document.querySelector('canvas').getContext('webgl2');
    
      const vs = `#version 300 es
      void main() {
        gl_Position = vec4(0, 0, 0, 1);
        gl_PointSize = 100.0;
      }
      `;
    
      const fs = `#version 300 es
      precision highp float;
      layout(location = 0) out vec4 thing1;
      layout(location = 2) out vec4 thing2;
      void main () {
        thing1 = vec4(1, 0, 0, 1);
        thing2 = vec4(0, 0, 1, 1);
      }
      `;
    
      const prg = twgl.createProgram(gl, [vs, fs]);
    
      const fb = gl.createFramebuffer();
      gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
    
      createTextureAndAttach(gl, gl.COLOR_ATTACHMENT0);
      createTextureAndAttach(gl, gl.COLOR_ATTACHMENT2);
    
      gl.drawBuffers([
        gl.COLOR_ATTACHMENT0,
        gl.NONE,
        gl.COLOR_ATTACHMENT2,
      ]);
    
      const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
      if (status !== gl.FRAMEBUFFER_COMPLETE) {
        console.error("can't render to this framebuffer combo");
        return;
      }
    
      gl.useProgram(prg);
      gl.viewport(0, 0, 1, 1);
      gl.drawArrays(gl.POINTS, 0, 1);
      
      checkError();
      
      read(gl.COLOR_ATTACHMENT0);
      read(gl.COLOR_ATTACHMENT2);
    
      checkError();
    
      function checkError() {
        const err = gl.getError();
        if (err) {
          console.error(twgl.glEnumToString(gl, err));
        }
      }
    
      function read(attachmentPoint) {
        gl.readBuffer(attachmentPoint);
        const pixel = new Uint8Array(4);
        gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
        console.log(Array.from(pixel).join(','));
      }
    
      function createTextureAndAttach(gl, attachmentPoint) {
        const tex = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, tex);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
        gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D, tex, 0);
      }
    
    }
    
    main();
    <script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
    <canvas></canvas>

    note: Referencing the OpenGL docs for WebGL are often wrong and/or misleading for WebGL. You need to reference the OpenGL 3.0 ES spec for WebGL2