General computation with offscreen rendering
Quick answer
This tutorial implements GPGPU with WebGL2 by borrowing the render pipeline, since compute shaders are unavailable. It creates two rgba32float textures with SAMPLE, RENDER and COPY_DST usage, pairs each with a Framebuffer, and draws a fullscreen triangle while ping-ponging the source and destination. It covers format fallback through isTextureFormatSupported, nearest sampling and readback into a Buffer. Readers can run particle or field simulations on the GPU.
Key points
- Alternate two textures and framebuffers each step to avoid a feedback loop.
- Draw a fullscreen triangle with clearColor false during the compute pass.
- Choose rgba32float only when isTextureFormatSupported confirms availability.
Example
function createField(width, height) {
return device.createTexture({
width: width,
height: height,
format: 'rgba32float',
usage: luma.Texture.SAMPLE | luma.Texture.RENDER | luma.Texture.COPY_DST,
sampler: { minFilter: 'nearest', magFilter: 'nearest', mipmapFilter: 'none' }
});
}
const SIZE = 512;
let src = createField(SIZE, SIZE);
let dst = createField(SIZE, SIZE);
const fboA = device.createFramebuffer({
width: SIZE,
height: SIZE,
colorAttachments: [src]
});
const fboB = device.createFramebuffer({
width: SIZE,
height: SIZE,
colorAttachments: [dst]
});
let writeFbo = fboB; // 本轮写入目标,初始写进 B
const sim = new luma.Model(device, {
vs: fullscreenVS,
fs: simFS,
attributes: { positions: quadBuffer },
bufferLayout: [
{ name: 'positions', stepMode: 'vertex', format: 'float32x2' }
],
bindings: { uState: src },
vertexCount: 3, // 全屏三角形
topology: 'triangle-list'
});function step() {
const pass = device.beginRenderPass({
framebuffer: writeFbo,
clearColor: false // 全屏覆盖,不需要清除
});
sim.setBindings({ uState: src });
sim.draw(pass);
pass.end();
const texTmp = src;
src = dst;
dst = texTmp;
writeFbo = (writeFbo === fboA) ? fboB : fboA;
}
const loop = new luma.AnimationLoop({
device: device,
onRender: function () { step(); }
});
Notes
- Binding the current color attachment as a sampler is undefined WebGL behaviour.
- Half-float accumulation shows blocky artifacts after hundreds of iterations.
English edition written for Atlaslog. Code is taken from the original Chinese page and runs unchanged.
Atlaslog (2026). General computation with offscreen rendering. https://www.jingweizhi.com/en/lumagl/tutorials/gpgpu-fbo.html (accessed 2026-11-03).