Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is gl.readPixels giving all 0 from Firefox?

I’m using WebGL to run some image processing (similar to Brad Larson’s GPUImage or the tutorial WebGL Fundamentals), followed by a CPU step that consumes the pixels. Unfortunately, when I call gl.readPixels, the buffer is all black in Firefox. Why?

Here’s a standalone example: http://jsfiddle.net/yonran/8C3n5/ In Chrome, the result is, “Within the framebuffer, there are 924 black pixels and 100 blue pixels,” while in Firefox, the result is, “Within the framebuffer, there are 1024 black pixels and 0 blue pixels.”

This is how I set up the framebuffer:

function createTextureAndFramebuffer(gl, width, height) {
  var outputTexture = createAndSetupTexture(gl);
  // Make the texture the same size as the image.
  gl.bindTexture(gl.TEXTURE_2D, outputTexture);
  gl.texImage2D(
      gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0,
      gl.RGBA, gl.UNSIGNED_BYTE, null);
  gl.bindTexture(gl.TEXTURE_2D, null);

  // create a framebuffer
  var fbo = gl.createFramebuffer();
  gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
  // attach a texture to it.
  gl.framebufferTexture2D(
      gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, outputTexture, 0);
  return {texture: outputTexture, framebuffer: fbo};
}
function createAndSetupTexture(gl) {
  var texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);

  // Set up texture so we can render any size image and so we are
  // working with pixels.
    // This is necessary for non-power-of-two textures
  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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

  return texture;
}

This is how I draw to the framebuffer:

ImageFilter.prototype = {
  ...,
  draw: function() {
    var gl = this.gl;
    // Use the texture
    gl.bindTexture(gl.TEXTURE_2D, this.texture);

    // make this the framebuffer we are rendering to.
    gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);

    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    // Tell webgl the viewport setting needed for framebuffer.
    gl.viewport(0, 0, this.outputWidth, this.outputHeight);

    gl.useProgram(this.program);
    this.applyUniforms();

    // Draw the rectangle.
    gl.drawArrays(gl.TRIANGLES, 0, 6);
    gl.flush();
  },
  vertexShaderString:
      "attribute vec4 position;\n" +
      "attribute vec4 inputTextureCoordinate;\n" +
      "\n" +
      "varying vec2 textureCoordinate;\n" +
      "\n" +
      "void main()\n" +
      "{\n" +
      "     gl_Position = position;\n" +
      "     textureCoordinate = inputTextureCoordinate.xy;\n" +
      "}\n",
  fragmentShaderString:
      "varying highp vec2 textureCoordinate;\n" +
      "\n" +
      "uniform sampler2D inputImageTexture;\n" +
      "\n" +
      "void main()\n" +
      "{\n" +
      "  gl_FragColor = texture2D(inputImageTexture, textureCoordinate);\n" +
      "}\n"
};

And then I read from the framebuffer like this:

gl.bindFramebuffer(gl.FRAMEBUFFER, imageFilter.framebuffer);
var width = imageFilter.outputWidth, height = imageFilter.outputHeight;
var array = new Uint8ClampedArray(width * height * 4);
gl.finish();
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, array);
var blackPixels = 0, bluePixels = 0;
var firstBlueX, firstBlueY;
for (var y = 0; y < height; y++) {
  for (var x = 0; x < width; x++) {
    var idx = 4*(x + y*width);
    var r = array[idx], g = array[idx+1], b = array[idx+2], a = array[idx+3];
    if (r === 0 && g === 0 && b === 0)
      blackPixels++;
    else if (b === 255) {
      bluePixels++;
      if (firstBlueX == null) {
        firstBlueX = x;
        firstBlueY = y;
      }
    }
  }
}
var el = document.createElement("div");
document.body.appendChild(el);
el.textContent = "The first canvas is drawn to a framebuffer and then drawn again on the second canvas. " +
  "Within the framebuffer, there are " + blackPixels + " black pixels and " +
  bluePixels + " blue pixels The first blue pixel is at (" + firstBlueX+ "," + firstBlueY + ") " +
  "(origin is at bottom left).";

i thought this might be a synchronization problem, but adding gl.finish() or a setTimeout before reading doesn’t help. When searching, I found that multisampling can cause the framebuffer to be 0, but setting antialias: false within the canvas.getContext call doesn’t help.

Edit: I just tried setting a pixel before readPixels, and Firefox doesn’t unset the pixel. Perhaps readPixels is not implemented yet and is a no-op as of 14.0.1?

like image 622
yonran Avatar asked Nov 03 '22 19:11

yonran


1 Answers

The offending line is here:

var array = new Uint8ClampedArray(width * height * 4);
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, array);

When you specify the type as GL_UNSIGNED_BYTE you must pass in the correctly typed array or FF fails. The correct type here is Uint8Array, not Uint8ClampedArray.

If you check the FF console log you will see this is the error message, resolved by swapping these array types:

Error: WebGL: readPixels: Mismatched type/pixels types
like image 195
fuzic Avatar answered Nov 15 '22 13:11

fuzic