Skip to content

Performance · CPU · core

Frame Budget

every system shares one slice of time per frame

At 60 Hz a frame has 16.67 ms for input, gameplay, physics, AI and rendering combined. Measure each system and keep the total under budget with headroom.

View .md
Interactive visual

The bottom bar is one frame: each coloured slice is a system (orange physics, purple AI, yellow gameplay, cyan rendering, grey other) and the white line is the budget, with any overrun in red. The bars above are recent frames (green on time, red late). Raise the load or the target rate to see frames miss.

What it is

The frame budget is the time one frame may take before the display moves on without you: 1000 / targetHz milliseconds, so 16.67 ms at 60 Hz, 8.33 ms at 120 Hz and 33.3 ms at 30 Hz. Input, gameplay scripts, physics, AI, animation, audio mixing and the CPU side of rendering all have to fit inside it, every frame.

Think in milliseconds, not FPS. FPS is the reciprocal of frame time, so it hides how costs add up: dropping from 60 to 50 fps costs 3.3 ms, while 30 to 20 fps costs 16.7 ms. A feature that costs 2 ms is 12% of a 60 Hz frame no matter what the current FPS is.

With vsync, missing the budget by even 0.1 ms usually means waiting for the next vertical blank, so the frame shows for 33.3 ms and the game stutters. Plan for headroom (spend about 80–90% of the budget) because spikes from garbage collection, asset loads and busy combat scenes all land on top of the average.

Split heavy work you can't shrink across frames with time slicing, and cut per-object CPU cost with spatial partitioning and fewer draw calls.

Key formulas

Budget per frame

60 Hz → 16.67 ms, 120 Hz → 8.33 ms, 144 Hz → 6.94 ms.

Frame time adds up
Shown time with vsync

17 ms of work at 60 Hz is shown for 33.3 ms: effectively 30 fps.

Rules of thumb

  • Target about 13–14 ms of work at 60 Hz (80–85%) so spikes don't cross 16.67 ms.
  • Track the 99th-percentile frame time, not just the average. Players feel spikes, not means.
  • Give each system a written budget, e.g. gameplay 3 ms, physics 2.5 ms, AI 2 ms, animation 1.5 ms, render submission 4 ms.
  • Time with performance.now() (sub-millisecond, monotonic). Browsers coarsen it, so average over many frames.
  • On the web, the GPU runs in parallel: CPU time and GPU time each need to fit, and the slower one sets the frame rate.

Implementation

Per-system timings with a rolling average and a budget check:
const BUDGET_MS = 1000 / 60
const timings = new Map<string, number>() // smoothed ms per system

function timed(name: string, fn: () => void) {
  const t0 = performance.now()
  fn()
  const ms = performance.now() - t0
  const prev = timings.get(name) ?? ms
  timings.set(name, prev + (ms - prev) * 0.1) // exponential moving average
}

function frame(dt: number) {
  timed("input", () => input.poll())
  timed("gameplay", () => world.update(dt))
  timed("physics", () => physics.step(dt))
  timed("ai", () => ai.update(dt))
  timed("render", () => renderer.render(scene, camera))

  let total = 0
  for (const ms of timings.values()) total += ms
  if (total > BUDGET_MS * 0.9) hud.warn(`frame ${total.toFixed(1)} ms`)
}

Common mistakes

  • Optimising by FPS deltas instead of milliseconds, which misjudges how much a feature costs.
  • Spending the whole 16.67 ms on average, so every GC pause or loading hitch drops a frame.
  • Profiling a debug build or with DevTools open and treating the numbers as shipping performance.
  • Measuring only the CPU when the GPU is the bottleneck (or vice versa). Check both before optimising.
  • Doing one-off heavy work (pathfinding for everyone, level loads, JSON parsing) inside a single frame instead of spreading it out.

Prompt

Build a frame-budget profiler for my TypeScript + Three.js game. Provide `beginSystem(name)` / `endSystem(name)` using performance.now(), store an exponential moving average and a rolling 120-frame window per system, and compute total, average and 99th-percentile frame time. Accept a target refresh rate (default 60 Hz → 16.67 ms) and flag frames above 90% of budget. Draw a small overlay with one coloured bar per system scaled to the budget and the overrun in red. No allocations per frame (preallocate ring buffers). Write unit tests for the percentile function and for the vsync rounding rule ceil(frame/budget)·budget.

Further reading

Updated 2026-11-01