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:

TypeScript
1
2
3
4
5
6
7
8
9
10
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 submit

One-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. Inside frame(gpu), always draw through frame.pass().

Warning: Do not call frame(gpu) from inside another frame callback or from a surface resize callback. vgpu throws VGPU-FRAME-REENTRANT so command encoders stay ordered and predictable.

Render loops

For animation, use frameLoop(gpu) — it runs your frame every tick:

TypeScript
1
2
3
4
5
6
7
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 unmounts

The 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:

TypeScript
1
2
3
4
5
6
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.

API in this chapter