vgpu2 symbolsView source ↗

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

TypeScript
1
import { clock } from "vgpu";

Signature

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

ParamTypeRequiredDefaultNotes
gpuGpuThe context returned by init(). One clock per gpu: clock(gpu) === clock(gpu).
advance.dtSecondsnumberSeconds 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).
PropertyTypeDefaultNotes
timenumber0Seconds since the first frame. Advances once per frame: with the wall-clock delta, or with the value the last advance() was given.
deltaTimenumber0Seconds between the last two ticks. Exactly the argument of the last advance() when driving the clock manually.
frameCountnumber0Frames 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

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
});
TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
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, 120
TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
import { 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 unless advance() already ran since the last frame. Calling advance(dt) and then frame() in the same tick advances exactly once, by dt.
  • advance() moves time and deltaTime immediately, 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. Pass clock(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.