Integrations

TypeGPU Integration

Integrating TypeGPU TypeScript shader authoring with Motion GPU runtime rendering and scheduling.


This integration is focused on one workflow:

  • write shaders in TypeScript with TypeGPU ('use gpu')
  • transform them with unplugin-typegpu
  • pass generated WGSL to Motion GPU defineMaterial

You keep Motion GPU runtime ergonomics (FragCanvas, useFrame, usePointer, render modes, passes) and move shader authoring to typed TypeScript.

Integration model

Boundary is simple:

  1. Author shader logic in TypeScript with TypeGPU.
  2. unplugin-typegpu transforms shader functions.
  3. tgpu.resolve(...) returns WGSL (string).
  4. Motion GPU consumes that WGSL in defineMaterial({ fragment }).
  5. Motion GPU still enforces fragment contract: fn frag(uv: vec2f) -> vec4f

1. Enable transformer in build pipeline

Vite example:

import { defineConfig } from 'vite';
import typegpu from 'unplugin-typegpu/vite';

export default defineConfig({
  plugins: [typegpu()]
});
import { defineConfig } from 'vite';
import typegpu from 'unplugin-typegpu/vite';

export default defineConfig({
  plugins: [typegpu()]
});

If you bundle inside a worker pipeline, use the matching integration (for example Rolldown/Rollup plugin variant) and include it in that worker bundler as well.

2. Author shader in TypeScript (shader.ts)

// shader.ts
import tgpu, { d } from 'typegpu';

const resolution = tgpu['~unstable'].rawCodeSnippet('motiongpuFrame.resolution', d.vec2u, 'uniform');
const time = tgpu['~unstable'].rawCodeSnippet('motiongpuFrame.time', d.f32, 'uniform');

const frag = tgpu
  .fn([d.vec2f], d.vec4f)((uv) => {
    'use gpu';

    const r = d.vec2f(resolution.$);
    const fitX = r.x / Math.max(r.y, 1);
    const px = (uv.x - 0.5) * fitX;
    const py = uv.y - 0.5;
    const wave = 0.5 + 0.5 * Math.sin((px + py) * 12 + time.$ * 1.4);

    return d.vec4f(wave, 0.4 + 0.6 * wave, 1 - wave, 1);
  })
  .$name('frag');

export const fragment = tgpu.resolve([frag], { names: 'strict' });
// shader.ts
import tgpu, { d } from 'typegpu';

const resolution = tgpu['~unstable'].rawCodeSnippet('motiongpuFrame.resolution', d.vec2u, 'uniform');
const time = tgpu['~unstable'].rawCodeSnippet('motiongpuFrame.time', d.f32, 'uniform');

const frag = tgpu
  .fn([d.vec2f], d.vec4f)((uv) => {
    'use gpu';

    const r = d.vec2f(resolution.$);
    const fitX = r.x / Math.max(r.y, 1);
    const px = (uv.x - 0.5) * fitX;
    const py = uv.y - 0.5;
    const wave = 0.5 + 0.5 * Math.sin((px + py) * 12 + time.$ * 1.4);

    return d.vec4f(wave, 0.4 + 0.6 * wave, 1 - wave, 1);
  })
  .$name('frag');

export const fragment = tgpu.resolve([frag], { names: 'strict' });

3. Use generated WGSL in Motion GPU (App.svelte / runtime file)

import { defineMaterial } from '@motion-core/motion-gpu';
import { fragment } from './shader';

const material = defineMaterial({ fragment });
import { defineMaterial } from '@motion-core/motion-gpu';
import { fragment } from './shader';

const material = defineMaterial({ fragment });

Access Motion GPU runtime bindings in TypeGPU

Motion GPU provides built-ins in shader scope:

  • motiongpuFrame.resolution
  • motiongpuFrame.time
  • motiongpuFrame.delta

In TypeGPU, expose them via rawCodeSnippet(...) as shown above.

For user uniforms declared in defineMaterial({ uniforms }), reference:

  • motiongpuUniforms.<name>

Example:

const uStrength = tgpu['~unstable'].rawCodeSnippet('motiongpuUniforms.uStrength', d.f32, 'uniform');
const uStrength = tgpu['~unstable'].rawCodeSnippet('motiongpuUniforms.uStrength', d.f32, 'uniform');

You must still declare uStrength in defineMaterial({ uniforms: { uStrength: ... } }).

Recommended file split

For this integration, keep a strict 2-file split:

  1. shader.ts: TypeGPU authoring ('use gpu') + tgpu.resolve
  2. runtime component (.svelte / .tsx): Motion GPU FragCanvas, defineMaterial, useFrame / usePointer

This keeps shader logic isolated from runtime orchestration and makes testing/refactoring easier.

Common pitfalls

Problem Cause Fix
Missing metadata ... unplugin-typegpu 'use gpu' shader path without transformer Enable unplugin-typegpu in your build pipeline
createRequire is not a function in browser bundle Node-targeted plugin entry used in browser worker Use browser-safe plugin entry/integration for your bundler pipeline
WGSL compile error: missing frag(uv) Generated function not named/signed as MotionGPU contract Ensure final WGSL exports fn frag(uv: vec2f) -> vec4f
Unknown motiongpuUniforms.<x> Uniform referenced in shader but not declared in material Add uniform to defineMaterial({ uniforms })
Visual jitter from frame-time coupling Scaling animation speed directly by delta Prefer stable base on time; use delta only when intentionally integrating velocities

Related docs