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:
import { init, effect, frame, sampler, surface, target } from "vgpu";
const gpu = await init();
const canvas = document.querySelector("canvas")!;
const canvasTarget = surface(gpu, canvas);
const pulseEffect = effect(gpu, `
struct Params { time: f32 }
@group(0) @binding(0) var<uniform> params: Params;
@fragment fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {
return vec4f(uv, sin(params.time) * 0.5 + 0.5, 1.0);
}
`, { set: { params: { time: 0 } } });
const postEffect = effect(gpu, `
@group(0) @binding(0) var src: texture_2d<f32>;
@group(0) @binding(1) var samp: sampler;
@fragment fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {
let base = textureSampleLevel(src, samp, uv, 0.0);
return vec4f(1.0 - base.rgb, 1.0);
}
`);
// ---cut---
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:
import { clock, init, effect, frameLoop, surface } from "vgpu";
const gpu = await init();
const canvas = document.querySelector("canvas")!;
const canvasTarget = surface(gpu, canvas);
const pulseEffect = effect(gpu, `
struct Params { time: f32 }
@group(0) @binding(0) var<uniform> params: Params;
@fragment fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {
return vec4f(uv, sin(params.time) * 0.5 + 0.5, 1.0);
}
`, { set: { params: { time: 0 } } });
// ---cut---
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:
import { init, effect, surface } from "vgpu";
const gpu = await init();
const canvas = document.querySelector("canvas")!;
const canvasTarget = surface(gpu, canvas);
const pulseEffect = effect(gpu, `
struct Params { time: f32 }
@group(0) @binding(0) var<uniform> params: Params;
@fragment fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {
return vec4f(uv, sin(params.time) * 0.5 + 0.5, 1.0);
}
`, { set: { params: { time: 0 } } });
// ---cut---
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.