Rendering

Frame Scheduler

Managing per-frame logic, task ordering, and invalidation with the useFrame API.


The frame scheduler manages all per-frame logic in Motion GPU. Every useFrame callback is registered as a task with ordering constraints and an invalidation policy. This page covers the useFrame API in depth.

For render modes, stages, and profiling, see Render Modes and Scheduling.

Basic usage

<script lang="ts"> import { useFrame } from '@motion-core/motion-gpu/svelte'; useFrame((state) => { state.setUniform('uTime', state.time); }); </script>

useFrame must be called inside a <FragCanvas> component tree. Calling it outside throws.

Overloads

Signature Description
useFrame(callback, options?) Auto-generated task key
useFrame(key, callback, options?) Explicit stable key for ordering and debugging
// Auto key
useFrame((state) => { ... });

// Explicit key
useFrame('camera-update', (state) => { ... });

// With options
useFrame('particles', (state) => { ... }, {
  stage: particleStage,
  autoInvalidate: false
});
// Auto key
useFrame((state) => { ... });

// Explicit key
useFrame('camera-update', (state) => { ... });

// With options
useFrame('particles', (state) => { ... }, {
  stage: particleStage,
  autoInvalidate: false
});

Return value

useFrame returns a handle for controlling the task:

Field Type Description
task { key: FrameKey; stage: FrameKey } Task and stage identifiers
start() () => void Enables the task (resumes execution)
stop() () => void Disables the task (paused, not removed)
started CurrentReadable<boolean> Reactive store for effective running state
const handle = useFrame('animation', (state) => {
  state.setUniform('uTime', state.time);
});

// Pause animation
handle.stop();

// Resume
handle.start();
const handle = useFrame('animation', (state) => {
  state.setUniform('uTime', state.time);
});

// Pause animation
handle.stop();

// Resume
handle.start();

FrameState callback API

The state parameter passed to your callback provides everything you need for per-frame logic:

Field Type Description
time number requestAnimationFrame timestamp in seconds (monotonic clock)
delta number Time since last frame in seconds (clamped by maxDelta)
canvas HTMLCanvasElement Active canvas element
renderMode RenderMode Current render mode
autoRender boolean Current auto-render state
setUniform(name, value) function Set a runtime uniform override
setTexture(name, value) function Set a runtime texture source
writeStorageBuffer(name, data, options?) function Queue a CPU → GPU write into a declared storage buffer
readStorageBuffer(name) function Async GPU → CPU readback from a declared storage buffer
invalidate(token?) function Trigger invalidation for on-demand mode
advance() function Request one frame in manual mode

Example: complete runtime component

<script lang="ts"> import { useFrame, usePointer } from '@motion-core/motion-gpu/svelte'; const pointer = usePointer(); useFrame((state) => { state.setUniform('uTime', state.time); state.setUniform('uMouse', pointer.state.current.uv); }); </script>

UseFrameOptions

Option Type Default Description
autoStart boolean true Whether the task starts enabled
autoInvalidate boolean true Auto-invalidate every frame (for on-demand mode)
invalidation FrameTaskInvalidation Implicit Explicit invalidation policy override
stage FrameKey FrameStage Main stage Which stage to assign the task to
before task ref or list undefined This task must run before the specified task(s)
after task ref or list undefined This task must run after the specified task(s)
running () => boolean undefined Dynamic gate — task is skipped when this returns false

Invalidation policies

The invalidation policy controls whether a task’s execution triggers a re-render in on-demand mode:

Simple policies

Value Behaviour
'never' Task never triggers invalidation
'always' Task always triggers invalidation

Object policies

// Always invalidate with a token
{ mode: 'always', token: 'my-task' }

// Never invalidate
{ mode: 'never' }

// On-change: invalidate only when token value changes
{ mode: 'on-change', token: () => someReactiveValue }
// Always invalidate with a token
{ mode: 'always', token: 'my-task' }

// Never invalidate
{ mode: 'never' }

// On-change: invalidate only when token value changes
{ mode: 'on-change', token: () => someReactiveValue }

on-change semantics

With on-change, the scheduler compares the current resolved token against the previous frame’s token. Invalidation fires only when they differ. This is ideal for state-driven updates:

useFrame('background-inset', (state) => {
  state.setUniform('uColor', currentColor);
}, {
  autoInvalidate: false,
  invalidation: {
    mode: 'on-change',
    token: () => currentColor.join(',')
  }
});
useFrame('background-inset', (state) => {
  state.setUniform('uColor', currentColor);
}, {
  autoInvalidate: false,
  invalidation: {
    mode: 'on-change',
    token: () => currentColor.join(',')
  }
});

Task ordering with dependencies

Use before and after to control execution order within a stage:

const physics = useFrame('physics', (state) => {
  // Update physics simulation
});

const render = useFrame('render-update', (state) => {
  // Read physics results
}, {
  after: physics.task
});
const physics = useFrame('physics', (state) => {
  // Update physics simulation
});

const render = useFrame('render-update', (state) => {
  // Read physics results
}, {
  after: physics.task
});

Dependencies are resolved via topological sort.

  • Circular dependencies throw a deterministic error.
  • Missing before / after references throw a deterministic error.

Lifecycle

  • useFrame automatically unregisters the task when the component is destroyed (adapter lifecycle cleanup).
  • The stop() / start() methods pause/resume without unregistering — the task remains in the schedule but is skipped.
  • The running gate is checked each frame — a stopped task or a task with running: () => false is simply skipped, not removed.
  • Re-registering the same explicit key replaces the previous task registration; stale unsubscribe handlers from the replaced task will not remove the newer one.