GPGPU
You may need to read the article about framebuffers before this one.
General-purpose GPU (GPGPU) refers to doing work with the GPU other than rendering scenes. This can be useful when you need to perform lots of small, independent tasks, which GPUs are designed to excel at. GPGPU work can be performed both via textures and via buffers.
GPGPU With Textures
Conceptually, GPGPU is very similar to a map
function, which takes a list and a lambda expression as inputs and creates an output list by applying the lambda expression to each entry in the input list correspondingly. In this example, the rendered fragments compose the input list, the shader program is the lambda function, and a texture or buffer is used to store the output list.
Previous articles have already covered writing to textures and reading from textures via framebuffers. From that point, the only new thing that needs to be done to perform GPGPU work is to read data from a texture on the CPU. This can be achieved with readPixels
, which reads pixel data from the read buffer of the currently-bound read framebuffer and stores it in either a typed array or the currently-bound pixel pack buffer.
const out = new Uint8Array(3 * 2 * 4); // 4 channels per fragment for `RGBA`.
gl.readPixels(0, 0, 3, 2, gl.RGBA, gl.UNSIGNED_BYTE, out);
Similar to how the unpack alignment needs to be set to properly load image data into a texture, the pack alignment needs to be set to read data out of a texture. If this isn't done before calling readPixels
, rows that aren't divisible by the pack alignment will be padded to the next multiple of the pack alignment with zeros.
gl.pixelStorei(gl.PACK_ALIGNMENT, 1);
Note that if you don't actually care how the result looks, you can use an OffscreenCanvas
for this type of task. Alternatively, you can enable rasterizer discarding, which causes fragments to be discarded right before the rasterization step.
gl.enable(gl.RASTERIZER_DISCARD);
GPGPU With Buffers
In cases where you need one-dimensional output, it can be more intuitive to use transform feedback. Transform feedback is the process of copying varying values into buffers.
To read the values of varyings, those varyings need to be marked as transform feedback varyings with transformFeedbackVaryings
prior to linking the shader program on which transform feedback will be performed.
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.transformFeedbackVaryings(program, ["v_out"], gl.SEPARATE_ATTRIBS);
gl.linkProgram(program);
Transform feedback can use one of two modes. In separate attributes mode, each varying will have its values written to a separate buffer. In interleaved attributes mode, all of the values will be written to the same buffer, interleaved in the order that the varying names were passed to transformFeedbackVaryings
.
Using a transform feedback object for output is an analogue of using a vertex array object for input. A transform feedback object can be created with createTransformFeedback
and bound with bindTransformFeedback
. Output buffers can be bound to varyings with bindBufferBase
or bindBufferRange
, which takes as its second argument the index of the corresponding varying in the array of varying names passed to transformFeedbackVaryings
.
const tf = gl.createTransformFeedback();
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, outBuffer);
To actually perform the transform feedback, activate the transform feedback object with beginTransformFeedback
after using its shader program and before calling a rendering function and deactivate it with endTransformFeedback
after calling the rendering function.
gl.useProgram(program);
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf);
gl.beginTransformFeedback(gl.POINTS);
gl.bindVertexArray(vao);
gl.drawElements(gl.POINTS, count, type, offset);
gl.endTransformFeedback();
Buffers that will be used to store transform feedback output can't be bound to any other binding point. Specifically, buffers that are bound to a transform feedback object can't also be bound to another binding point when any rendering function or function that uses that binding point is called while that transform feedback object is bound. To read the data from the buffers, use getBufferSubData
, ensuring that you first unbind the transform feedback object.
const out = new Uint8Array(size);
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null); // Unbind the TFO.
gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER, outBuffer);
gl.getBufferSubData(gl.TRANSFORM_FEEDBACK_BUFFER, offset, out);
The next article is about picking.