Frames
A frame is one unit of GPU work. Inside it you open passes, each drawing into a target you choose, and draw the effects you created earlier. When the callback returns, vgpu encodes everything into one command encoder and submits it once.
Render a single frame
frame(gpu) runs synchronously and renders immediately — every pass inside is encoded into one command encoder and submitted once. That single submit is what the frame is for:
const sceneTarget = target(gpu, { size: [canvasTarget.size[0], canvasTarget.size[1]] });
postEffect.set({
src: sceneTarget,
samp: sampler(gpu, { minFilter: 'linear', magFilter: 'linear' }),
});
frame(gpu, (currentFrame) => {
currentFrame.pass(sceneTarget, pulseEffect);
currentFrame.pass(canvasTarget, postEffect);
}); // two passes, one encoder, one submitOne-shot draws like pulseEffect.draw(canvasTarget) are the simple default for a single pass. Multi-pass hot paths should use frame(gpu) to batch passes into one command encoder and one submit. One-shot draws never join a surrounding frame; inside frame(gpu), always go through frame.pass().
Warning: one-shot
draw()calls do not join a surrounding frame — inside a frame callback they submit on their own immediately. Insideframe(gpu), always draw throughframe.pass().
Warning: Do not call
frame(gpu)from inside another frame callback or from a surface resize callback. vgpu throwsVGPU-FRAME-REENTRANTso command encoders stay ordered and predictable.
Render loops
For animation, use frameLoop(gpu) — it runs your frame every tick:
const time = clock(gpu);
const handle = frameLoop(gpu, (frame) => {
pulseEffect.set({ params: { time: time.time } }); // update uniforms every tick
frame.pass(canvasTarget, pulseEffect);
}, { fps: 30 });
handle.stop(); // call it when your component unmountsThe loop advances the frame clock — clock(gpu).time, deltaTime and frameCount — and runs surface auto-resize before each tick. The optional fps throttles it.
This is what the same loop looks like by hand with requestAnimationFrame:
function tick() {
pulseEffect.set({ params: { time: performance.now() / 1000 } }); // you own the clock now
pulseEffect.draw(canvasTarget);
requestAnimationFrame(tick); // and the scheduling
}
requestAnimationFrame(tick);Both work. frameLoop(gpu) is the same loop with the clock, throttling, and resize handling done for you.
See it live: the fluid example runs a compute-driven simulation with exactly this frame loop shape.