Why is this webgl code not drawing to the canvas?

61 Views Asked by At

I am writing what should be a simple wrapper around twgl so that I can work on shaders with a react UX. Why does this simple program not draw pixels on the Framebuffer?

Here is the wrapper class:

import * as twgl from 'twgl.js'

export class WGL {
  image: any;
  texture: any;
  gl: WebGLRenderingContext;

  constructor(_gl: WebGLRenderingContext) {
    this.gl = _gl;

    var ext = this.gl.getExtension('OES_texture_float');
    if (!ext) {
      alert('requires OES_texture_float');
    }
  }

  async loadImage(src: string) {
    const gl = this.gl;

    const _this = this;

    return new Promise((resolve, reject) => {
      console.log('ct', gl, this.gl);
      twgl.createTexture(
        gl,
        {
          src,
          min: gl.NEAREST,
          mag: gl.NEAREST,
          wrap: gl.CLAMP_TO_EDGE,
          crossOrigin: '',
        },
        (err: Error, texture: any, image: any) => {
          if (err) return reject(err);

          _this.image = image;
          _this.texture = texture;

          resolve({ image });
        }
      );
    });
  }

  async process() {
    const { gl, image, texture } = this;

    sampleTexture(gl, texture);

    const pi = await twgl.createProgramInfo(gl, [
      vertex_shader_src,
      fragment_shader_src,
    ]);

    const quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);

    gl.useProgram(pi.program);

    twgl.setBuffersAndAttributes(gl, pi, quadBufferInfo);

    // image to hold the result
    const targetFbi = twgl.createFramebufferInfo(
      gl,
      [
        {
          type: gl.FLOAT,
          min: gl.NEAREST,
          mag: gl.NEAREST,
          wrap: gl.CLAMP_TO_EDGE,
        },
      ],
      image.width,
      image.height
    );
    if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
      alert("can't render to floating point texture");
    }

    twgl.setUniforms(pi, {
      u_texture: texture,
    });

    twgl.bindFramebufferInfo(gl, targetFbi);

    twgl.drawBufferInfo(gl, gl.TRIANGLES, quadBufferInfo);

    sampleTexture(gl, targetFbi);
  }
}

Here is the react app:

export function App() {
  const outputRef = useRef();

  const [imageSrc, setImageSrc] = useState(DEFAULT_IMAGE);

  useEffect(() => {
    if (!outputRef.current || !imageSrc) return;

    var gl = outputRef.current.getContext('webgl');
    const wgl = new WGL(gl);

    wgl.loadImage(imageSrc).then(({ image }) => {
      wgl.process();
    });
  }, [outputRef, imageSrc]);

  return (
    <>
      <span>
        <canvas ref={outputRef} width="256" height="256"></canvas>
      </span>
    </>
  );
}

I have been using a sampleTexture() function to look at pixel values. The first call to sampleTexture() shows good pixel values from the given image. The second call should show modified versions of those pixels but shows zeroes. (If I bind to null and draw to the canvas, it's all black.)

export const sampleTexture = (
  gl: WebGLRenderingContext,
  texture: WebGLTexture,
  size = 10
) => {
  console.log('sampleTexture');
  var fb = gl.createFramebuffer();
  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
  gl.framebufferTexture2D(
    gl.FRAMEBUFFER,
    gl.COLOR_ATTACHMENT0,
    gl.TEXTURE_2D,
    texture,
    0
  );
  if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
    var pixels = new Uint8Array(size * size * 4);
    gl.readPixels(0, 0, size, size, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
    console.log(pixels); // Uint8Array
  }
};

I have verified that these shaders work elsewhere:

//vertex
attribute vec4 position;
attribute vec2 texcoord;

varying vec2 v_texCoord;

void main() {

   gl_Position = vec4(position.x, -position.y, 0, 1);

   v_texCoord = texcoord;
}

// fragment
#ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp float;
#else
  precision mediump float;
#endif

// our texture
uniform sampler2D u_texture;

// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;

void main() {

   vec4 pixel = texture2D(u_texture, v_texCoord);

   gl_FragColor = vec4(pixel.r, pixel.g, 0, 1);
}

UPDATE: The problem was the way I brought in twgl.js. I was importing twgl.js into the WGL source file and bundling with webpack. If I use an html script tag for twgl.js everything works as expected. How can I bundle twgl.js in a way that doesn't change how it works?

0

There are 0 best solutions below