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?