Symbols in this topic
clock
The frame clock of a gpu: elapsed time, last delta, frame count, and the manual advance() that lets an external ticker own the clock. Use it whenever a shader, a camera or a simulation needs time; use advance() when something else already owns the timeline (GSAP/Motion, an XR frame callback, a fixed-timestep loop, a deterministic replay).
Import
import { clock } from "vgpu";Signature
import type { Gpu } from "vgpu";
interface Clock {
readonly time: number;
readonly deltaTime: number;
readonly frameCount: number;
advance(dtSeconds: number): void;
}
declare function clock(gpu: Gpu): Clock;Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| gpu | Gpu | ✔ | — | The context returned by init(). One clock per gpu: clock(gpu) === clock(gpu). |
| advance.dtSeconds | number | ✔ | — | Seconds to move the clock forward, finite and >= 0. Scale it for a timescale (dt * 0.5), or pass a constant for a fixed timestep (1 / 60). |
| Property | Type | Default | Notes |
|---|---|---|---|
| time | number | 0 | Seconds since the first frame. Advances once per frame: with the wall-clock delta, or with the value the last advance() was given. |
| deltaTime | number | 0 | Seconds between the last two ticks. Exactly the argument of the last advance() when driving the clock manually. |
| frameCount | number | 0 | Frames opened by frame(gpu) / frameLoop(gpu). advance() never counts a frame. |
Returns: Clock — a live view of this gpu's frame clock. It reads through to the clock, so a single instance can be captured outside the loop and read inside it.
Throws: VGPU-GPU-DISPOSED when clock(gpu) runs after gpu.dispose() — read the clock before disposing, or init() a new gpu; VGPU-GPU-FOREIGN when the argument was not created by init(); VGPU-CLOCK-DELTA-INVALID when advance() receives a value that is not a finite, non-negative number — pass elapsed seconds, e.g. advance(1 / 60).
Examples
import { init, clock, effect, frameLoop, surface } from "vgpu";
declare const canvas: HTMLCanvasElement;
const gpu = await init();
const canvasSurface = surface(gpu, canvas);
const wave = 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 } } });
// Automatic: every frame advances the clock with the wall-clock delta.
const time = clock(gpu);
frameLoop(gpu, (frame) => {
wave.set({ params: { time: time.time } });
frame.pass(canvasSurface, wave);
});import { init, clock, effect, frame, target } from "vgpu/mock";
const gpu = await init();
const scene = target(gpu, { size: [64, 64] });
const shader = effect(gpu, `@fragment fn fs_main() -> @location(0) vec4f { return vec4f(1); }`);
// Manual: a fixed timestep makes the render deterministic, run after run.
const time = clock(gpu);
for (let step = 0; step < 120; step++) {
time.advance(1 / 60);
frame(gpu, (currentFrame) => currentFrame.pass(scene, shader));
}
console.log(time.time, time.frameCount); // 2, 120import { init, clock, frameLoop } from "vgpu/mock";
const gpu = await init();
const time = clock(gpu);
// Timescale: the same wall clock, played at half speed. Slow motion is one multiplication.
let previousMs = performance.now();
const timescale = 0.5;
frameLoop(gpu, () => {
const nowMs = performance.now();
time.advance(((nowMs - previousMs) / 1000) * timescale);
previousMs = nowMs;
});Notes
- One tick per frame, manual first:
frame()advances the clock with wall-clock time unlessadvance()already ran since the last frame. Callingadvance(dt)and thenframe()in the same tick advances exactly once, bydt. advance()movestimeanddeltaTimeimmediately, before any frame opens — so code that reads the clock outside a frame (input smoothing, physics substeps) sees the value it is about to render with.- Mixing is allowed and useful: drive the clock manually while an external ticker runs, then drop back to plain
frame(gpu)calls and the wall clock takes over again, measured from the last tick. - The clock is not a global. It belongs to the gpu, and it is created lazily: a program that never opens a frame and never calls
clock(gpu)never allocates it. - There is no clock on
Frame. Passclock(gpu)(or the numbers you read from it) into render helpers instead of reaching for frame state. - See also:
frame,frameLoop,Gpu, and the guide Driving vgpu with an external ticker.