Created
July 17, 2019 13:36
-
-
Save subzey/9a3dafaa04c196173fdd120dc3440421 to your computer and use it in GitHub Desktop.
Adding numbers with WebGL2 Transform Feedback
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This API is simply disgusting.