Skip to content

Instantly share code, notes, and snippets.

@subzey
Created July 17, 2019 13:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save subzey/9a3dafaa04c196173fdd120dc3440421 to your computer and use it in GitHub Desktop.
Save subzey/9a3dafaa04c196173fdd120dc3440421 to your computer and use it in GitHub Desktop.
Adding numbers with WebGL2 Transform Feedback
<!doctype html>
<html>
<head>
<title>Adding numbers with WebGL2 Transform Feedback</title>
</head>
<body>
<h1>Adding numbers with WebGL2 Transform Feedback.</h1>
<output></output>
<script src="webgl.js"></script>
<p>View <a href="webgl.js">the commented source</a>!</p>
</body>
</html>
// The typed arrays we will sum
const inputJSArray1 = Uint32Array.of(2, 2, 12345678);
const inputJSArray2 = Uint32Array.of(2, 40, 87654321);
const VERTEX_COUNT = Math.min(inputJSArray1.length, inputJSArray2.length);
// Create a <canvas> element. It should not even be attached to the #document, just exist
const a = document.createElement('canvas');
// Get a WebGL2 context. WebGL1 has no transfrom feedback
const gl = a.getContext('webgl2') || a.getContext('experimental-webgl2');
// Do not render anything - skip the fragment shader entirely
gl.enable(gl.RASTERIZER_DISCARD);
// Create a GLSL program. It's a set of:
// - vertex shader
// - fragment shader (it's turned off, but is still needed)
// - transfrom feedback
const program = gl.createProgram();
// Create a vertex shader
const vShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vShader, `#version 300 es
// Output parameter v_result (known as varying in WebGL1).
// Normally it would be passed to the fragment shader.
// flat means, there's no interpolation. There's no interpolation for integers anyway.
flat out uint v_result;
// Input paramenters
in uint a_input_1;
in uint a_input_2;
// This function is called for each vertex
void main() {
v_result = a_input_1 + a_input_2;
}
`);
gl.compileShader(vShader);
// Log shader errors
console.log('vShader', gl.getShaderInfoLog(vShader));
gl.attachShader(program, vShader);
// Create a vertex shader
const fShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fShader, `#version 300 es
// It won't be called anyway, write something valid here
void main() {}
`);
gl.compileShader(fShader);
console.log('fShader', gl.getShaderInfoLog(fShader));
gl.attachShader(program, fShader);
// Attach v_result varying should be intercepted
// and stored into the TRANSFROM_FEEDBACK buffer
gl.transformFeedbackVaryings(program, ['v_result'], gl.INTERLEAVED_ATTRIBS);
// Link program
gl.linkProgram(program);
console.log('program', gl.getProgramInfoLog(program));
// Use program.
// WebGL can use several programs for drawing, and we need to specify the active one.
gl.useProgram(program);
// Create and initialize buffers
// Input buffer 1
let inputBuffer1 = gl.createBuffer();
// We need to place the data on that buffer.
// You'd probably expect something like (buffer.setData), but no.
// We have to make this buffer an active ARRAY_BUFFER...
gl.bindBuffer(gl.ARRAY_BUFFER, inputBuffer1);
// ...then write data into the active ARRAY_BUFFER.
gl.bufferData(gl.ARRAY_BUFFER, inputJSArray1, gl.STATIC_DRAW);
// Input buffer 2
let inputBuffer2 = gl.createBuffer();
// Same again for the second buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, inputBuffer2);
gl.bufferData(gl.ARRAY_BUFFER, inputJSArray2, gl.STATIC_DRAW);
// Output buffer
let resultBuffer = gl.createBuffer();
// Create a TransformFeedback object
var transformFeedback = gl.createTransformFeedback();
// Make it the active TRANSFORM_FEEDBACK
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, transformFeedback);
// Make resultBuffer the active TRANSFORM_FEEDBACK_BUFFER #0
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, resultBuffer);
// Now we can set data to that buffer.
// We actually don;t care about the exact contents of this buffer,
// just make sure the length is 8 bytes (2 * Uint32)
gl.bufferData(gl.TRANSFORM_FEEDBACK_BUFFER, VERTEX_COUNT * Uint32Array.BYTES_PER_ELEMENT, gl.STATIC_DRAW);
// Now we have three buffers in the GPU memory:
// inputBuffer1: 8 bytes of attr1 data
// inputBuffer2: 8 bytes of attr2 data
// outputBuffer: 8 bytes of something we don't care, probably zeroes
// Attributes
// Attribute a_input_1
// Get the index that WegGL decided to assign to this attribute
const input1AttribLocation = gl.getAttribLocation(program, 'a_input_1');
// Debug print. Most probably, it will be 0
console.log(input1AttribLocation);
// We will specify the attribute as a pointer to a buffer
gl.enableVertexAttribArray(input1AttribLocation);
// But the buffer as an active one.
gl.bindBuffer(gl.ARRAY_BUFFER, inputBuffer1);
// On the buffer that is currently the active one..
gl.vertexAttribIPointer(
input1AttribLocation, // the attribute with the index input1AttribLocation ...
1, // is the single value (not a vector)...
gl.UNSIGNED_INT, // of type Uint32...
0, // starting at the offset 0
0 // with the distance between consecutive values 0 (it means, "auto")
);
// Same for a_input_2
const input2AttribLocation = gl.getAttribLocation(program, 'a_input_2');
console.log(input2AttribLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, inputBuffer2);
gl.enableVertexAttribArray(input2AttribLocation);
gl.vertexAttribIPointer(
input2AttribLocation, // index
1, // size
gl.UNSIGNED_INT, // type
0, // stride
0 // offset
);
// Ready to draw!
// Activate the transform feedback
gl.beginTransformFeedback(gl.POINTS);
// We don't need WebGL to use complex geometry to create triangles and stuff, just use points.
// Each point is a vertex. So we have to "draw" VERTEX_COUNT points.
gl.drawArrays(gl.POINTS, 0, VERTEX_COUNT);
// Deactivate the transform feedback, so we can read the buffer contents back.
gl.endTransformFeedback();
// Read back
const resultArray = new Uint32Array(VERTEX_COUNT);
gl.getBufferSubData(
gl.TRANSFORM_FEEDBACK_BUFFER, // target
0, // srcByteOffset
resultArray, // dstData
0, // srcOffset?
VERTEX_COUNT // length?
);
// TA-DA!!
document.querySelector('output').innerHTML = `
[${inputJSArray1.join(', ')}] <br>
+ <br>
[${inputJSArray2.join(', ')}] <br>
= <br>
[${resultArray.join(', ')}]
`;
// (hopefully)
@rjbudzynski
Copy link

This API is simply disgusting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment