simone
simone

Reputation: 5233

Putting data onto different textures in WebGL and getting it out

I am trying to output more than one buffer from a shader - the general goal is to use it for GPGPU purposes. I've looked at this answer and got closer to the goal with this:

document.addEventListener("DOMContentLoaded", function() {
    function main() {
        const gl = document.querySelector('canvas').getContext('webgl2');
        if (!gl) {
            return alert("need WebGL2");
        }
        gl.canvas.width  = 2;
        gl.canvas.height = 2;


        const vs = `
#version 300 es
in vec2 position;

void main(void) {
     gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
}
`;

        const fs = `
#version 300 es
precision mediump float;

  layout(location = 0) out vec4 outColor0;
  layout(location = 1) out vec4 outColor1;
  layout(location = 2) out vec4 outColor2;
  layout(location = 3) out vec4 outColor3;
  layout(location = 4) out vec4 outColor4;
  layout(location = 5) out vec4 outColor5;

  void main() {
    // simplified for question purposes
    outColor0 = vec4(1, 0, 0, 1);
    outColor1 = vec4(0, 1, 0, 1);
    outColor2 = vec4(0, 0, 1, 1);
    outColor3 = vec4(1, 1, 0, 1);
    outColor4 = vec4(1, 0, 1, 1);
    outColor5 = vec4(0, 1, 1, 1);
  } 
  `

        const program = twgl.createProgram(gl, [vs, fs]);

        const textures = [];

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

        for (let i = 0; i < 6; ++i) {
            const tex = gl.createTexture();
            textures.push(tex);
            gl.bindTexture(gl.TEXTURE_2D, tex);

            const width = 2;
            const height = 2;
            const level = 0;
            
            gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
            // attach texture to framebuffer
            gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, tex, level);
        }

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

        // tell it we want to draw to all 4 attachments

        gl.drawBuffers([
            gl.COLOR_ATTACHMENT0,
            gl.COLOR_ATTACHMENT1, 
            gl.COLOR_ATTACHMENT2,
            gl.COLOR_ATTACHMENT3,
            gl.COLOR_ATTACHMENT4,
            gl.COLOR_ATTACHMENT5,                   
        ]);

        // draw a single point
        gl.useProgram(program);
        {
            const offset = 0;
            const count = 1
            gl.drawArrays(gl.TRIANGLE, 0, 4);
        }

        for (var l = 0; l < 6; l++) { 
            var pixels  = new Uint8Array(gl.canvas.width * gl.canvas.height * 4);
            
            gl.readBuffer(gl.COLOR_ATTACHMENT0 + l);
            gl.readPixels(0, 0, gl.canvas.width, gl.canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);

            console.log(pixels.join(' '));
        }
    }
    main();
})

However, the result is that only one pixel in each buffer gets set, so the output is:

0 0 0 0 255 0 0 255 0 0 0 0 0 0 0 0
0 0 0 0 0 255 0 255 0 0 0 0 0 0 0 0
0 0 0 0 0 0 255 255 0 0 0 0 0 0 0 0
0 0 0 0 255 255 0 255 0 0 0 0 0 0 0 0
0 0 0 0 255 0 255 255 0 0 0 0 0 0 0 0
0 0 0 0 0 255 255 255 0 0 0 0 0 0 0 0

rather than what I was hoping/expecting:

255 0 0 255 255 0 0 255 255 0 0 255 255 0 0 255
etc.

I was expecting that

outColor0 = vec4(1, 0, 0, 1);

is the equivalent to

gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);

but clearly I am wrong.

So how do I get to the desired outcome - to be able to set each pixel on each of the buffers?

Upvotes: 0

Views: 93

Answers (1)

user128511
user128511

Reputation:

The code does not provide any vertex data even though it's asking it to draw 4 vertices. Further it's passing in gl.TRIANGLE which doesn't exist. It's gl.TRIANGLES with an S at the end. gl.TRIANGLE will be undefined which gets coerced into 0 which matches gl.POINTS

In the JavaScript console

> const gl = document.createElement('canvas').getContext('webgl2');
< undefined
> gl.TRIANGLE 
< undefined
> gl.TRIANGLES
< 4
> gl.POINTS
< 0

To put it another way all the gl.CONSTANTS are just integer values. Instead of

gl.drawArrays(gl.TRIANGLES, offset, count)

you can just do this

gl.drawArrays(4, offset, count) 

because gl.TRIANGLES = 4.

But you you didn't use gl.TRIANGLES you used gl.TRIANGLE (no S) so you effectively did this

gl.drawArrays(undefined, offset, count) 

that was interpreted as

gl.drawArrays(0, offset, count) 

0 = gl.POINTS so that's the same as

gl.drawArrays(gl.POINTS, offset, count) 

The code then draws a single 1 pixel point 4 times at the same location because you called it with a count of 4

gl.drawArrays(gl.POINTS, 0, 4) 

Nothing in your vertex shader changes each iteration so every iteration is going to do exactly the same thing. In this case it's going to draw a 1x1 pixel POINT at clip space position 0,0,0,1 which will end up being the bottom left pixel of the 2x2 pixels.

In any case you probably want to provide vertices but as a simple test if I add

  gl_PointSize = 2.0;

to the vertex shader and change the draw call to

gl.drawArrays(gl.POINTS, 0, 1); // draw 1 point

Then it produces the results you expect. It draws a single 2x2 pixel POINT at clip space position 0,0,0,1

function main() {
  const gl = document.querySelector('canvas').getContext('webgl2');
  if (!gl) {
    return alert("need WebGL2");
  }
  gl.canvas.width = 2;
  gl.canvas.height = 2;


  const vs = `
    #version 300 es
    in vec2 position;
    
    void main(void) {
         gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
         gl_PointSize = 2.0;
    }
    `;

  const fs = `
    #version 300 es
    precision mediump float;
    
      layout(location = 0) out vec4 outColor0;
      layout(location = 1) out vec4 outColor1;
      layout(location = 2) out vec4 outColor2;
      layout(location = 3) out vec4 outColor3;
      layout(location = 4) out vec4 outColor4;
      layout(location = 5) out vec4 outColor5;
    
      void main() {
        // simplified for question purposes
        outColor0 = vec4(1, 0, 0, 1);
        outColor1 = vec4(0, 1, 0, 1);
        outColor2 = vec4(0, 0, 1, 1);
        outColor3 = vec4(1, 1, 0, 1);
        outColor4 = vec4(1, 0, 1, 1);
        outColor5 = vec4(0, 1, 1, 1);
      } 
      `

  const program = twgl.createProgram(gl, [vs, fs]);

  const textures = [];

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

  for (let i = 0; i < 6; ++i) {
    const tex = gl.createTexture();
    textures.push(tex);
    gl.bindTexture(gl.TEXTURE_2D, tex);

    const width = 2;
    const height = 2;
    const level = 0;

    gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
    // attach texture to framebuffer
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, tex, level);
  }

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

  // tell it we want to draw to all 4 attachments

  gl.drawBuffers([
    gl.COLOR_ATTACHMENT0,
    gl.COLOR_ATTACHMENT1,
    gl.COLOR_ATTACHMENT2,
    gl.COLOR_ATTACHMENT3,
    gl.COLOR_ATTACHMENT4,
    gl.COLOR_ATTACHMENT5,
  ]);

  // draw a single point
  gl.useProgram(program); {
    const offset = 0;
    const count = 1
    gl.drawArrays(gl.POINTS, 0, 1);
  }

  for (var l = 0; l < 6; l++) {
    var pixels = new Uint8Array(gl.canvas.width * gl.canvas.height * 4);

    gl.readBuffer(gl.COLOR_ATTACHMENT0 + l);
    gl.readPixels(0, 0, gl.canvas.width, gl.canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);

    console.log(pixels.join(' '));
  }
}
main();
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

You can try using webgl-lint which if I run with your original code will at least complain

Uncaught Error: https://greggman.github.io/webgl-lint/webgl-lint.js:2942: error in drawArrays(/UNKNOWN WebGL ENUM/ undefined, 0, 4): argument 0 is undefined with WebGLProgram("unnamed") as current program with the default vertex array bound

function main() {
  const gl = document.querySelector('canvas').getContext('webgl2');
  if (!gl) {
    return alert("need WebGL2");
  }
  gl.canvas.width = 2;
  gl.canvas.height = 2;


  const vs = `
    #version 300 es
    in vec2 position;
    
    void main(void) {
         gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
    }
    `;

  const fs = `
    #version 300 es
    precision mediump float;
    
      layout(location = 0) out vec4 outColor0;
      layout(location = 1) out vec4 outColor1;
      layout(location = 2) out vec4 outColor2;
      layout(location = 3) out vec4 outColor3;
      layout(location = 4) out vec4 outColor4;
      layout(location = 5) out vec4 outColor5;
    
      void main() {
        // simplified for question purposes
        outColor0 = vec4(1, 0, 0, 1);
        outColor1 = vec4(0, 1, 0, 1);
        outColor2 = vec4(0, 0, 1, 1);
        outColor3 = vec4(1, 1, 0, 1);
        outColor4 = vec4(1, 0, 1, 1);
        outColor5 = vec4(0, 1, 1, 1);
      } 
      `

  const program = twgl.createProgram(gl, [vs, fs]);

  const textures = [];

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

  for (let i = 0; i < 6; ++i) {
    const tex = gl.createTexture();
    textures.push(tex);
    gl.bindTexture(gl.TEXTURE_2D, tex);

    const width = 2;
    const height = 2;
    const level = 0;

    gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
    // attach texture to framebuffer
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, tex, level);
  }

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

  // tell it we want to draw to all 4 attachments

  gl.drawBuffers([
    gl.COLOR_ATTACHMENT0,
    gl.COLOR_ATTACHMENT1,
    gl.COLOR_ATTACHMENT2,
    gl.COLOR_ATTACHMENT3,
    gl.COLOR_ATTACHMENT4,
    gl.COLOR_ATTACHMENT5,
  ]);

  // draw a single point
  gl.useProgram(program); {
    const offset = 0;
    const count = 1
    gl.drawArrays(gl.TRIANGLE, 0, 4);
  }

  for (var l = 0; l < 6; l++) {
    var pixels = new Uint8Array(gl.canvas.width * gl.canvas.height * 4);

    gl.readBuffer(gl.COLOR_ATTACHMENT0 + l);
    gl.readPixels(0, 0, gl.canvas.width, gl.canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);

    console.log(pixels.join(' '));
  }
}
main();
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
<script src="https://greggman.github.io/webgl-lint/webgl-lint.js" crossorigin="anonymous"></script>

Upvotes: 1

Related Questions