Skip to content

@typegpu/kit/hosting, @typegpu/kit/scenes, @typegpu/kit/scenes/react, @typegpu/kit/runtimeshosting · scenes · react · runtimes

Hosting scenes

Run a scene file on a web or React Native canvas, with its root, surface, loop, errors and teardown handled in order.

kit.hosting

platform

Provides shared hosting code without importing React Native.

hosting.local(create, options?) → Owned

hosting.stage(root, surface, createScene, options, hosting) → Promise<Staged>

hosting.watch(device, onError)

createtakes
makes the value, here
Ownedreturns
a handle you send closures to and destroy
rootborrows
the root
surfacetakes
the surface the scene draws into, which the host keeps
createScenetakes
the scene file's createScene
optionstakes
the scene's third argument
hostingtakes
where frames come from, what takes their errors and what the host runs beside
Promise<Staged>returns
the stage, which you destroy before the root and the surface
devicetakes
the device it listens to
onErrortakes
takes the first error, once
types
CallbackOwnedSceneCreateSceneStageHostedPausingCanvasOptionsHostOptionsStaged
OwnOptionslocal · 1 field
onError?

Callback<[unknown]>= console.error on the runtime that owns the value

Receives errors from create, sent closures and calls that worklets cannot schedule.

StageOptionsstage · 6 fields
schedule?

Schedule | false= requestAnimationFrame

Supplies animation frames to animate.

onError

(error: unknown) => void

Receives the first scene error once, after the frames stop: from a frame, onFrame, a rejected ready, the watched device or staged.fail.

watch?

GPUDevice= none

Watches the device from scene creation until destroy, and fails the stage on its first uncaptured error or unexpected loss.

onFrame?

(stage: Stage<S, Canvas>, frame: Frame) => void= none

Runs after the scene's frame in every frame, with the stage.

attach?

(stage: Stage<S, Canvas>) => Disposable | void= none

Runs with the stage once it is up; destroy disposes what it returns before the scene.

release?

() => void= none

Releases host resources, such as the surface and the root, at the end of destroy, also when the scene's destroy throws.

owns

a stage's frames and their animation ·what its `attach` returned ·and the scene ·which it destroys

lifecycle

destroy()[Symbol.dispose]

A scene is the createScene(root, surface, options) function a scene file exports, which returns the scene’s frame and destroy. Hosting it requires a device, error listeners, a surface, the scene and a scheduled frame loop, with cleanup in order. web.host handles these on the web, and useScene on React Native, for the same scene file.

On the web, web.host runs a scene on a canvas:

import * as
import web
web
from '@typegpu/kit/web';
const
const hosted: web.Hosted<Game>
hosted
=
import web
web
.
function host<{
board: number;
}, Game>(createScene: web.CreateScene<{
board: number;
}, Game>, options: {
board: number;
}, hosting: web.HostOptions<Game>): web.Hosted<Game> (+1 overload)

Runs a scene on a canvas with a root, surface, frame loop and animation. destroy tears them down in order. Use send to control the scene or onFrame to read it. If scene options are optional, call host(createScene, { canvas }). Throws if both device and root are supplied.

@param ― createScene the scene file's createScene

@param ― options the scene's third argument

@param ― hosting the canvas, the device or root, and what the page runs beside the scene

@returns ― a hosted scene you send closures to and destroy

host
(
function createScene(root: TgpuRoot, surface: Surface, options: {
board: number;
}): Game
createScene
,
{
board: number
board
: 1 },
{
HostOptions<S extends Scene>.canvas: HTMLCanvasElement

Supplies the canvas for the scene.

canvas
,
SurfaceOptions.maxPixelRatio?: number | undefined

Caps drawing-buffer pixels per CSS pixel unless size is set. Default 2.

maxPixelRatio
: 1.5,
HostOptions<S extends Scene>.optionalFeatures?: Iterable<GPUFeatureName> | undefined

Requests these features when the adapter supports them, such as timestamp-query. Default none.

optionalFeatures
: ['timestamp-query', 'indirect-first-instance'],
HostOptions<Game, Surface>.onFrame?: ((stage: Stage<Game, web.Surface>, frame: Frame) => void) | undefined

Runs after the scene's frame each frame, for page work such as a HUD that reads scene state. An error stops the frames. Default none.

onFrame
: ({
scene: Game
scene
}) => (
const hud: HTMLElement
hud
.
Element.textContent: string | null
textContent
= `${
scene: Game
scene
.
Game.score: number
score
}`),
HostOptions<S extends Scene, Canvas extends Surface = Surface>.onError?: Callback<[unknown]> | undefined

Receives the first scene error, from createScene, a frame, onFrame, ready or the device, which stops the frames. Also receives each error a sent closure throws, which leaves the frames running. Default console.error.

onError
: (
error: unknown
error
) => (
const hud: HTMLElement
hud
.
Element.textContent: string | null
textContent
=
var String: StringConstructor
(value?: any) => string

Allows manipulation and formatting of text strings and determination and location of substrings within strings.

String
(
error: unknown
error
)),
},
);
const canvas: HTMLCanvasElement
canvas
.
HTMLCanvasElement.addEventListener<"pointerdown">(type: "pointerdown", listener: (this: HTMLCanvasElement, ev: PointerEvent) => any, options?: boolean | AddEventListenerOptions): void (+1 overload)

The addEventListener() method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.

MDN Reference

addEventListener
('pointerdown', ({
offsetX: number

The offsetX read-only property of the MouseEvent interface provides the offset in the X coordinate of the mouse pointer between that event and the padding edge of the target node.

MDN Reference

offsetX
,
offsetY: number

The offsetY read-only property of the MouseEvent interface provides the offset in the Y coordinate of the mouse pointer between that event and the padding edge of the target node.

MDN Reference

offsetY
}) =>
const hosted: web.Hosted<Game>
hosted
.
Owned<Stage<Game, Surface>>.send<[]>(fn: (value: Stage<Game, web.Surface>) => void): void

Runs fn with the value on its runtime, after earlier sent calls. Any runtime can call it, so a gesture on the UI runtime can control a scene on a worker. Worklets copies args and caches objects by identity. If you change an object in place and send it again, the runtime receives the first copy. Send a new object after a change.

send
(({
scene: Game
scene
}) =>
scene: Game
scene
.
Game.point(x: number, y: number): void
point
(
offsetX: number

The offsetX read-only property of the MouseEvent interface provides the offset in the X coordinate of the mouse pointer between that event and the padding edge of the target node.

MDN Reference

offsetX
,
offsetY: number

The offsetY read-only property of the MouseEvent interface provides the offset in the Y coordinate of the mouse pointer between that event and the padding edge of the target node.

MDN Reference

offsetY
)),
);
var window: Window & typeof globalThis

The window property of a Window object points to the window object itself.

MDN Reference

window
.
addEventListener<"pagehide">(type: "pagehide", listener: (this: Window, ev: PageTransitionEvent) => any, options?: boolean | AddEventListenerOptions): void (+1 overload)

The addEventListener() method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.

MDN Reference

addEventListener
('pagehide', () =>
const hosted: web.Hosted<Game>
hosted
.
Owned<Stage<Game, Surface>>.destroy(): void

Calls destroy on the value's runtime after earlier sent calls, then releases the value.

destroy
(), {
AddEventListenerOptions.once?: boolean | undefined
once
: true });
  • send(fn, ...args) waits for scene creation and earlier sent work, then calls fn with { scene, root, surface, loop, ready }. run does the same and returns a promise for the result. It awaits async functions where the scene runs.
  • onFrame runs after the scene’s frame in every frame, for page code such as a HUD.
  • attach runs once the scene is up, with the stage. What it returns is disposed with the scene, so controls made there go with it: attach: ({ scene }) => web.orbitControls(scene.view, canvas).
  • stage.ready stays false while the scene’s ready promise is pending. Keep the loading screen visible until it resolves. A rejection reports a scene error.
  • The first scene error, from createScene, a frame, onFrame, ready or the device (an uncaptured error or its loss), goes to onError and stops the frames, so it shows once.
  • A sent closure that throws goes to onError each time and leaves the frames running, as an event handler’s error leaves requestAnimationFrame running.
  • To reach the stage directly from page code, take it once with const stage = await hosted.run((s) => s).
  • pause() and resume() pause the scene’s surface, and its frames and time with it. Like send, they wait for scene creation.
  • device takes a device of your own, which the host leaves alive. A test passes a recording root’s.
  • To time each pass of the scene, install the GPU debug panel before calling host.
  • destroy() releases everything in order, after what was sent before it, even while the scene is starting. Call it on pagehide, and from Vite’s import.meta.hot.dispose.
  • Omit scene options when they are optional, as in web.host(createScene, { canvas }).

In a React component, start the host in an effect and destroy it in the effect’s cleanup:

import * as
import web
web
from '@typegpu/kit/web';
import {
function createScene(root: TgpuRoot, surface: Surface): Promise<{
view: OrbitCamera;
frame(frame: Frame): void;
destroy(): void;
}>
createScene
} from './scene.ts';
// In a component that renders <canvas ref={canvasRef} />
function useEffect(effect: () => () => void, deps: readonly unknown[]): void
useEffect
(() => {
const
const canvas: HTMLCanvasElement
canvas
=
const canvasRef: {
readonly current: HTMLCanvasElement | null;
}
canvasRef
.
current: HTMLCanvasElement | null
current
!;
const
const hosted: web.Hosted<{
view: OrbitCamera;
frame(frame: Frame): void;
destroy(): void;
}>
hosted
=
import web
web
.
function host<{
view: OrbitCamera;
frame(frame: Frame): void;
destroy(): void;
}>(createScene: web.CreateScene<Record<never, never>, {
view: OrbitCamera;
frame(frame: Frame): void;
destroy(): void;
}>, hosting: web.HostOptions<{
view: OrbitCamera;
frame(frame: Frame): void;
destroy(): void;
}>): web.Hosted<{
view: OrbitCamera;
frame(frame: Frame): void;
destroy(): void;
}> (+1 overload)

Runs a scene on a canvas with a root, surface, frame loop and animation. destroy tears them down in order. Use send to control the scene or onFrame to read it. If scene options are optional, call host(createScene, { canvas }). Throws if both device and root are supplied.

@param ― createScene the scene file's createScene

@param ― options the scene's third argument

@param ― hosting the canvas, the device or root, and what the page runs beside the scene

@returns ― a hosted scene you send closures to and destroy

host
(
function createScene(root: TgpuRoot, surface: Surface): Promise<{
view: OrbitCamera;
frame(frame: Frame): void;
destroy(): void;
}>
createScene
, {
HostOptions<S extends Scene>.canvas: HTMLCanvasElement

Supplies the canvas for the scene.

canvas
,
HostOptions<{ view: OrbitCamera; frame(frame: Frame): void; destroy(): void; }, Surface>.attach?: ((stage: Stage<{
view: OrbitCamera;
frame(frame: Frame): void;
destroy(): void;
}, web.Surface>) => Disposable | void) | undefined

Connects page code, such as orbit controls, to the stage after scene creation. The host disposes its return value with the scene. Default none.

attach
: ({
scene: {
view: OrbitCamera;
frame(frame: Frame): void;
destroy(): void;
}
scene
}) =>
import web
web
.
function orbitControls(orbit: {
readonly goal: web.OrbitGoal;
}, element: HTMLElement, options?: web.OrbitControlsOptions): web.OrbitControls

Connects pointer input to an orbit camera's goal. Drag to orbit and use the wheel or pinch to zoom. With pan, drag the other button to move the target. update eases the camera towards the goal. Throws if pan is enabled but the goal has no target.

@param ― orbit an orbit camera, whose goal it writes

@param ― element what takes the drag, the wheel and the pinch

@returns ― the controls, which you destroy to remove the listeners

orbitControls
(
scene: {
view: OrbitCamera;
frame(frame: Frame): void;
destroy(): void;
}
scene
.
view: OrbitCamera
view
,
const canvas: HTMLCanvasElement
canvas
),
});
return () =>
const hosted: web.Hosted<{
view: OrbitCamera;
frame(frame: Frame): void;
destroy(): void;
}>
hosted
.
Owned<Stage<{ view: OrbitCamera; frame(frame: Frame): void; destroy(): void; }, Surface>>.destroy(): void

Calls destroy on the value's runtime after earlier sent calls, then releases the value.

destroy
();
}, []);

A page of several canvases shares one root rather than a device per scene. root takes a root you made, which the host leaves alive. schedule replaces requestAnimationFrame, and false runs no frames: the page runs them through the stage’s loop, such as to draw only the canvases on screen:

const
const root: TgpuRoot
root
= await
const tgpu: {
const: typeof import("node_modules/typegpu/core/constant/tgpuConstant").constant;
fn: typeof import("node_modules/typegpu/core/function/tgpuFn").fn;
comptime: typeof import("node_modules/typegpu/core/function/comptime").comptime;
resolve: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolve;
resolveWithContext: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolveWithContext;
init: typeof import("node_modules/typegpu/core/root/init").init;
initFromDevice: typeof import("node_modules/typegpu/core/root/init").initFromDevice;
slot: typeof import("node_modules/typegpu/core/slot/slot").slot;
lazy: typeof import("node_modules/typegpu/core/slot/lazy").lazy;
... 10 more ...;
'~unstable': typeof import("node_modules/typegpu/tgpuUnstable");
}

@module ― typegpu

tgpu
.
init: (options?: InitOptions) => Promise<TgpuRoot>

Requests a new GPU device and creates a root around it. If a specific device should be used instead, use

@see ― initFromDevice. *

@example

When given no options, the function will ask the browser for a suitable GPU device.

const root = await tgpu.init();

@example

If there are specific options that should be used when requesting a device, you can pass those in.

const adapterOptions: GPURequestAdapterOptions = ...;
const deviceDescriptor: GPUDeviceDescriptor = ...;
const root = await tgpu.init({ adapter: adapterOptions, device: deviceDescriptor });

init
();
const
const stages: web.Stage<{
frame(f: Frame): void;
destroy(): void;
}>[]
stages
:
import web
web
.
type Stage<S extends web.Scene> = Stage<S, web.Surface>

Provides the stage to sent closures, onFrame and attach.

Stage
<
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any

Obtain the return type of a function type

ReturnType
<typeof
function createScene(root: TgpuRoot, surface: Surface): {
frame(f: Frame): void;
destroy(): void;
}
createScene
>>[] = [];
for (const
const canvas: HTMLCanvasElement
canvas
of
const canvases: HTMLCanvasElement[]
canvases
) {
import web
web
.
function host<{
frame(f: Frame): void;
destroy(): void;
}>(createScene: web.CreateScene<Record<never, never>, {
frame(f: Frame): void;
destroy(): void;
}>, hosting: web.HostOptions<{
frame(f: Frame): void;
destroy(): void;
}>): web.Hosted<{
frame(f: Frame): void;
destroy(): void;
}> (+1 overload)

Runs a scene on a canvas with a root, surface, frame loop and animation. destroy tears them down in order. Use send to control the scene or onFrame to read it. If scene options are optional, call host(createScene, { canvas }). Throws if both device and root are supplied.

@param ― createScene the scene file's createScene

@param ― options the scene's third argument

@param ― hosting the canvas, the device or root, and what the page runs beside the scene

@returns ― a hosted scene you send closures to and destroy

host
(
function createScene(root: TgpuRoot, surface: Surface): {
frame(f: Frame): void;
destroy(): void;
}
createScene
, {
HostOptions<S extends Scene>.canvas: HTMLCanvasElement

Supplies the canvas for the scene.

canvas
,
HostOptions<S extends Scene, Canvas extends Surface = Surface>.root?: TgpuRoot | undefined

Supplies a root that the host keeps alive, so scenes can share it. On React Native, requires on: 'js'. Default a root that the host creates on device and destroys with the scene.

root
,
HostOptions<S extends Scene, Canvas extends Surface = Surface>.schedule?: false | Schedule | undefined

Supplies animation frames. With false, call stage.loop.run(seconds) to draw on demand. Default requestAnimationFrame, or display vsync on a React Native worker.

schedule
: false,
HostOptions<{ frame(f: Frame): void; destroy(): void; }, Surface>.attach?: ((stage: Stage<{
frame(f: Frame): void;
destroy(): void;
}, web.Surface>) => Disposable | void) | undefined

Connects page code, such as orbit controls, to the stage after scene creation. The host disposes its return value with the scene. Default none.

attach
: (
stage: Stage<{
frame(f: Frame): void;
destroy(): void;
}, web.Surface>
stage
) => void
const stages: web.Stage<{
frame(f: Frame): void;
destroy(): void;
}>[]
stages
.
Array<Stage<{ frame(f: Frame): void; destroy(): void; }>>.push(...items: web.Stage<{
frame(f: Frame): void;
destroy(): void;
}>[]): number

Appends new elements to the end of an array, and returns the new length of the array.

@param ― items New elements to add to the array.

push
(
stage: Stage<{
frame(f: Frame): void;
destroy(): void;
}, web.Surface>
stage
),
});
}
function requestAnimationFrame(callback: FrameRequestCallback): number
requestAnimationFrame
(function
function (local function) tick(now: number): void
tick
(
now: number
now
) {
for (const {
const loop: Frames
loop
} of
const stages: web.Stage<{
frame(f: Frame): void;
destroy(): void;
}>[]
stages
)
const loop: Frames
loop
.
Frames.run(seconds: number): boolean

Records and submits one frame with a fresh Frame and encoder. Requires finite, nondecreasing seconds. Throws for an invalid time, a nested frame or an asynchronous recorder. Returns false if the loop is destroyed before or during recording. Runs all after-submit callbacks. If any throw, the frame still counts and run rethrows their errors.

run
(
now: number
now
/ 1000);
function requestAnimationFrame(callback: FrameRequestCallback): number
requestAnimationFrame
(
function (local function) tick(now: number): void
tick
);
});
  • Each host watches the device, and WebGPU doesn’t say which scene made an error, so the first uncaptured error or the loss reaches every scene’s onError.
  • Without a schedule the host has no frames to stop at an error; stop running the loop from onError.

@typegpu/kit/hosting is what web.host and React Native’s useScene are built from. It imports nothing from React Native.

  • stage(root, surface, createScene, options, { schedule?, onError, watch?, onFrame?, attach?, release? }) creates a scene and scheduled frame loop. schedule: false disables automatic frames. The first error from a frame, onFrame, ready or the watch device stops the frames and reaches onError once; fail(error) does the same for an error the host finds itself, and nothing once the stage is destroyed. destroy() stops frames, disposes attach’s result, destroys the loop and scene, then calls release to free host resources, even when the scene’s destroy throws.
  • watch(device, onError) calls onError with the device’s first uncaptured error, or its loss unless it was destroyed on purpose, until you dispose it.
  • local(create) keeps a value on the calling thread with send and run, matching runtimes.own on a worker, as in a test or on a web page.
  • Scene has frame, destroy and an optional ready. CreateScene creates it from (root, surface, options), and Stage holds it with host resources. HostOptions describes shared web.host and scenes.host options.

A view that draws only when asked, such as a model viewer, stages its scene on a shared root without a schedule, and runs a frame when the view moves:

import {
function stage<Options, S extends web.Scene, Canvas extends Pausing>(root: TgpuRoot, surface: Canvas, createScene: web.CreateScene<Options, S>, options: Options, hosting: StageOptions<S, Canvas>): Promise<Staged<S, Canvas>>

Creates a scene on the host's root and surface, then creates its frames and animation. Destroys them in reverse order. Owns the rule every host follows: the first error from a frame, onFrame, ready or the watched device stops the frames and reaches onError once. If setup fails after scene creation, destroys those resources and rethrows the error. The host keeps its root and surface and destroys them in release.

@param ― createScene the scene file's createScene

@param ― surface the surface the scene draws into, which the host keeps

@param ― options the scene's third argument

@param ― hosting where frames come from, what takes their errors and what the host runs beside

@returns ― the stage, which you destroy before the root and the surface

stage
} from '@typegpu/kit/hosting';
const
const surface: web.Surface
surface
=
import web
web
.
function surface(root: TgpuRoot, canvas: HTMLCanvasElement, options?: web.SurfaceOptions): web.Surface

Configures a canvas for the root. Matches its drawing buffer to the layout size or the fixed options.size. Throws if the fixed size is invalid.

@param ― canvas the canvas it configures and sizes

@returns ― a surface you draw into and destroy

surface
(
const root: TgpuRoot
root
,
const canvas: HTMLCanvasElement
canvas
, {
CanvasOptions.alphaMode?: GPUCanvasAlphaMode | undefined

Controls canvas transparency. With premultiplied, alpha below 1 reveals what is behind the canvas, and colours must be premultiplied by alpha. Default opaque.

alphaMode
: 'premultiplied' });
const
const staged: Staged<{
frame(f: Frame): void;
destroy(): void;
}, web.Surface>
staged
= await
stage<{}, {
frame(f: Frame): void;
destroy(): void;
}, web.Surface>(root: TgpuRoot, surface: web.Surface, createScene: web.CreateScene<{}, {
frame(f: Frame): void;
destroy(): void;
}>, options: {}, hosting: StageOptions<{
frame(f: Frame): void;
destroy(): void;
}, web.Surface>): Promise<Staged<{
frame(f: Frame): void;
destroy(): void;
}, web.Surface>>

Creates a scene on the host's root and surface, then creates its frames and animation. Destroys them in reverse order. Owns the rule every host follows: the first error from a frame, onFrame, ready or the watched device stops the frames and reaches onError once. If setup fails after scene creation, destroys those resources and rethrows the error. The host keeps its root and surface and destroys them in release.

@param ― createScene the scene file's createScene

@param ― surface the surface the scene draws into, which the host keeps

@param ― options the scene's third argument

@param ― hosting where frames come from, what takes their errors and what the host runs beside

@returns ― the stage, which you destroy before the root and the surface

stage
(
const root: TgpuRoot
root
,
const surface: web.Surface
surface
,
function createScene(root: TgpuRoot, surface: Surface): {
frame(f: Frame): void;
destroy(): void;
}
createScene
,
{},
{
StageOptions<S extends Scene, Canvas extends Surface = Surface>.schedule?: false | Schedule | undefined

Supplies animation frames to animate. With false, call staged.loop.run(seconds) to draw on demand. Default requestAnimationFrame.

schedule
: false,
StageOptions<S extends Scene, Canvas extends Surface = Surface>.watch?: GPUDevice | undefined

Watches the device from scene creation until destroy, and fails the stage on its first uncaptured error or unexpected loss. Device events reach only the React Native JS thread, so pass it there or on the web. Default none.

watch
:
const root: TgpuRoot
root
.
TgpuRoot.device: GPUDevice

The GPU device associated with this root.

device
,
StageOptions<{ frame(f: Frame): void; destroy(): void; }, Surface>.onError: (error: unknown) => void

Receives the first scene error once, after the frames stop: from a frame, onFrame, a rejected ready, the watched device or staged.fail. stage rethrows what createScene throws instead.

onError
:
function show(error: unknown): void
show
,
StageOptions<{ frame(f: Frame): void; destroy(): void; }, Surface>.release?: (() => void) | undefined

Releases host resources, such as the surface and the root, at the end of destroy, also when the scene's destroy throws. Does not run if stage throws. Default none.

release
: () =>
const surface: web.Surface
surface
.
Surface.destroy(): void

Releases the drawing destination. Canvas surfaces unconfigure their contexts. Web surfaces also stop observing the canvas. Later calls to view throw. Also [Symbol.dispose].

destroy
() },
);
new
var ResizeObserver: new (callback: ResizeObserverCallback) => ResizeObserver

The ResizeObserver interface reports changes to the dimensions of an Element's content or border box, or the bounding box of an SVGElement.

MDN Reference

ResizeObserver
(() =>
const staged: Staged<{
frame(f: Frame): void;
destroy(): void;
}, web.Surface>
staged
.
Stage<{ frame(f: Frame): void; destroy(): void; }, Surface>.loop: Frames
loop
.
Frames.run(seconds: number): boolean

Records and submits one frame with a fresh Frame and encoder. Requires finite, nondecreasing seconds. Throws for an invalid time, a nested frame or an asynchronous recorder. Returns false if the loop is destroyed before or during recording. Runs all after-submit callbacks. If any throw, the frame still counts and run rethrows their errors.

run
(0)).
ResizeObserver.observe(target: Element, options?: ResizeObserverOptions): void

The observe() method of the ResizeObserver interface starts observing the specified Element or SVGElement.

MDN Reference

observe
(
const canvas: HTMLCanvasElement
canvas
);
var window: Window & typeof globalThis

The window property of a Window object points to the window object itself.

MDN Reference

window
.
addEventListener<"pagehide">(type: "pagehide", listener: (this: Window, ev: PageTransitionEvent) => any, options?: boolean | AddEventListenerOptions): void (+1 overload)

The addEventListener() method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.

MDN Reference

addEventListener
('pagehide', () =>
const staged: Staged<{
frame(f: Frame): void;
destroy(): void;
}, web.Surface>
staged
.
Staged<{ frame(f: Frame): void; destroy(): void; }, Surface>.destroy(): void

Stops the frames, disposes what attach returned, destroys the loop and the scene, then runs release. Also [Symbol.dispose].

destroy
());

kit.scenes

platform

Hosts a scene on a React Native canvas on a worker, the UI runtime or the JS thread.

scenes.host(createScene, options, hosting) → Hosted

createScenetakes
a worklet unless on is 'js', which calls the scene file's createScene
optionstakes
the scene's third argument
hostingtakes
the device, the canvas's context and size, and where the scene runs
Hostedreturns
a hosted scene you resize, send closures to and destroy
types
CreateSceneScenePlacementStageHosted
HostOptionshost · 18 fields
on?

Placement= 'worker', a runtime named after createScene and paced by the display

Selects the scene runtime.

context

GPUCanvasContext & { present(): void }

Supplies the canvas context.

width

number

Sets the canvas layout width in points.

height

number

Sets the canvas layout height in points.

pixelRatio

number

Sets the display pixel ratio from PixelRatio.get().

maxPixelRatio?

number= 2

Caps drawing-buffer pixels per point unless size is set.

size?

FixedSize= the layout size multiplied by the capped pixel ratio

Fixes the drawing-buffer size, as in native.surface.

device?

GPUDevice= a device that the web host requests and destroys with the scene

Supplies a device that the host keeps alive.

root?

TgpuRoot= a root that the host creates on device and destroys with the scene

Supplies a root that the host keeps alive, so scenes can share it.

schedule?

Schedule | false= requestAnimationFrame, or display vsync on a React Native worker

Supplies animation frames.

onFrame?

(stage: Stage<S, Canvas>, frame: Frame) => void= none

Runs after the scene's frame each frame, for page work such as a HUD that reads scene state.

attach?

(stage: Stage<S, Canvas>) => Disposable | void= none

Connects page code, such as orbit controls, to the stage after scene creation.

onError?

Callback<[unknown]>= console.error

Receives the first scene error, from createScene, a frame, onFrame, ready or the device, which stops the frames.

format?

GPUTextureFormat= navigator.gpu.getPreferredCanvasFormat()

Sets the canvas texture format.

alphaMode?

GPUCanvasAlphaMode= opaque

Controls canvas transparency.

usage?

GPUTextureUsageFlags= GPUTextureUsage.RENDER_ATTACHMENT

Sets the canvas texture usage flags.

colorSpace?

PredefinedColorSpace= 'srgb'

Sets the colour space of canvas values.

toneMapping?

GPUCanvasToneMapping= { mode: 'standard' }

Controls the range of canvas values for display.

owns

a root on the device it is given unless it was given a root ·the canvas's configuration ·a worker runtime unless it is given one ·the scene's stage and what its `attach` returned

lifecycle

destroy()[Symbol.dispose]

platform

nativeonly

Press send to run a closure when the scene runtime is free, then lengthen a frame beyond one vsync. In this model at one twenty-fifth speed, 'ui' drops app frames, while a worker keeps the UI running and counts missed scene frames in missed().

scenes.host hosts a scene, the createScene(root, surface, options) function that web.host takes, on a React Native canvas. It runs the scene on a worker runtime by default, or on the UI runtime or the JS thread. It owns a root on your device, the canvas’s configuration, the scene and the worker it creates, and schedules frames at display vsync. @typegpu/kit/runtimes provides the same runtime controls for code outside a kit scene. useScene wraps the host for a React Native canvas.

kit.react

platform

Hosts a scene on a react-native-webgpu <Canvas> through useScene.

useScene(createScene, options, hosting?) → SceneHandle

createScenetakes
a worklet unless on is 'js'
optionstakes
the scene's third argument
hosting?takes
where the scene runs, its device and whether it is paused
SceneHandlereturns
props for the <Canvas>, the hosted scene once it runs, and its first error
types
CreateSceneHostedPlacementSceneStageSceneHandle
UseSceneOptionsuseScene · 6 fields
on?

Placement= 'worker', one per component named after createScene

Selects the scene runtime.

paused?

boolean= false

Pauses frames while true, such as for a canvas outside the viewport.

device?

GPUDevice= the nearest <GPUDeviceProvider> device, or a device requested for this scene alone

Supplies a device shared by scenes.

optionalFeatures?

Iterable<GPUFeatureName>= none

Requests these features when the adapter supports them, such as indirect-first-instance.

maxPixelRatio?

number= 2

Caps drawing-buffer pixels per point.

alphaMode?

GPUCanvasAlphaMode= opaque

Controls canvas transparency.

owns

what `scenes.host` owns ·plus a requested device

Releases them when the component unmounts.

platform

nativeonly

Call useScene in a component. Beyond what the host does, it obtains a device, sizes the surface, pauses with the app and reports errors.

Blockfall.tsx
import { useScene, type Stage } from '@typegpu/kit/scenes/react';
import { useState } from 'react';
import { Text } from 'react-native';
import {
GestureDetector,
usePanGesture,
useSimultaneousGestures,
useTapGesture,
} from 'react-native-gesture-handler';
import { Canvas } from 'react-native-webgpu';
import type { Score } from './game.ts';
import { createScene } from './scene.ts';
type Blockfall = Awaited<ReturnType<typeof createScene>>;
const blockfall: typeof createScene = (root, surface, options) => {
'worklet';
return createScene(root, surface, options);
};
function drop({ scene }: Stage<Blockfall>) {
'worklet';
scene.game.drop();
}
function orbit({ scene }: Stage<Blockfall>, dx: number) {
'worklet';
scene.view.goal.yaw -= dx * 0.01;
}
export function BlockfallScreen() {
const [score, setScore] = useState<Score>();
const { canvas, scene, error } = useScene(blockfall, { onScore: setScore });
const send = scene?.send;
const tap = useTapGesture({
onActivate: () => {
'worklet';
send?.(drop);
},
});
const pan = usePanGesture({
onUpdate: (e) => {
'worklet';
send?.(orbit, e.changeX);
},
});
if (error) return <Text>{error.message}</Text>;
return (
<GestureDetector gesture={useSimultaneousGestures(tap, pan)}>
<Canvas {...canvas} style={{ flex: 1 }} />
</GestureDetector>
);
}

Blockfall’s scene.ts runs unchanged on the web and on React Native. The screen passes blockfall, a worklet that calls createScene through a named import, so worklets loads scene.ts on the scene’s runtime with its shaders and modules. A 'worklet' line in createScene itself would copy the shaders it captures instead. If createScene isn’t a worklet, the hook’s error shows the line to write.

What the hook does for you:

  • A worker runs the scene by default, paced by vsync, with the timer floor set by runtimes.worker. Each component reuses one worker named after createScene, because worklets can’t destroy runtimes.
  • The hook uses the nearest <GPUDeviceProvider>’s device and checks it with native.checkDevice. Without a provider, it requests a device with the optionalFeatures the adapter has and destroys that device after releasing the scene.
  • The hook listens for uncaptured errors and device.lost on the JS thread with hosting.watch. The first error or loss releases the scene and sets error. Deliberate device destruction doesn’t count as loss. On a shared device, the error stops every scene because WebGPU doesn’t identify its source scene.
  • Remount the component with a different key to restart. A new <Canvas> under the same hook, such as one inside a remounted parent, restarts the scene on it. If a provider’s device is lost, remount the provider too.
  • Canvas layout changes resize the surface. AppState pauses and resumes it. Use paused to stop a canvas scrolled out of view.
  • Scheduled work catches errors. The first error sets error once. Every error but a sent closure’s stops the frames.
  • Top-level option callbacks use post to call the latest render’s function. An inline arrow doesn’t restart the scene.
  • Initial options come from the render that starts the scene. Send changes from an effect, as in useEffect(() => send?.(setWeather, weather), [send, weather]). Restarting the scene creates another send, so the effect resends the value.
  • Teardown runs on the scene’s runtime after any frame in flight and unconfigures the canvas. A remounted scene, including a StrictMode remount, starts after the previous teardown finishes.

on, in the hook’s third argument with the other options the card lists, takes 'worker', 'ui', 'js' or a runtime of your own. The scene is the same everywhere:

const { canvas, scene, error } = useScene(blockfall, { onScore: setScore }, { on: 'ui' });
  • 'worker' keeps scene work off the UI runtime. Pass a runtime as on to share one worker between components.
  • 'ui' shares the UI runtime with Reanimated. Long scene frames delay the app’s UI. Use it when Reanimated must read shared values in the same frame.
  • 'js' runs at most 60 Hz and needs no worklet wrapper. React Native DevTools can debug this runtime. Use it to debug a scene that normally runs on a worker.

send runs a closure on the scene’s runtime with the stage, which holds the scene, root, surface and loop. A closure reaches anything there, so the scene needs no message schema. run does the same and returns the result to React Native as a promise.

Call scenes.host directly in an app with its own lifecycle. Pass the device, the canvas’s context, its layout size and the pixel ratio, and call resize, pause, resume and destroy yourself.

  • It accepts web.host’s schedule, onFrame, attach and onError options. It also accepts canvas format, alphaMode, usage, colorSpace, toneMapping and a fixed size. Off the JS thread, onFrame and attach must be worklets. The host throws an error naming a callback that isn’t one.
  • Supply root instead of a device only with on: 'js'. A root must stay on the thread that created it.
  • schedule replaces display vsync. With false, no frames run automatically. Send ({ loop }) => loop.run(seconds) yourself.
  • The device’s errors reach only the JS thread. With on: 'js' the host watches the device; elsewhere, listen with hosting.watch(device, onError), as useScene does.

For a scene with two canvases or none, start it with the runtime pieces below.

kit.runtimes

platform

Runs GPU work on React Native worklet runtimes.

runtimes.worker(options?)

runtimes.vsync(runtime) → Vsync

runtimes.own(runtime, create, options?) → Owned

runtimes.callable(fn) → Callback

and runtimes.post, runtimes.cell, runtimes.camera, runtimes.local

runtimetakes
the only runtime allowed to request these frames
Vsyncreturns
a schedule you destroy to stop the relay
runtimetakes
where the value lives
createtakes
a worklet that makes the value, there
Ownedreturns
a handle you send closures to and destroy
fntakes
a worklet, or a React Native function
Callbackreturns
a worklet that calls it
types
CallbackOwnOptionsOwnedScheduleRuntimeVsyncCellOrientationCameraFrameCameraSource
WorkerOptionsworker · 2 fields
name?

string= 'TypeGPU Kit'

Labels the runtime in errors and the debugger.

timerFloor?

number= 2

Sets the minimum delay of setTimeout(0), in milliseconds.

lifecycle

destroy()[Symbol.dispose]

platform

nativeonly

@typegpu/kit/runtimes provides runtime creation, scheduling and cross-runtime calls. Hosted code uses the kit’s normal API on its own runtime.

Piece What it does
runtimes.worker() A runtime with WebGPU, whose zero-delay timers wait 2 ms, so react-native-webgpu’s pump can’t spin it
runtimes.vsync(runtime) Frames for a worker at the display’s vsync, relayed from the UI runtime; a worker’s own requestAnimationFrame is a 16 ms timer
runtimes.own(runtime, create) Keeps a value on its runtime, reached by closures that run there, and catches what they throw
runtimes.cell(fields) Numbers and flags any runtime writes and reads without a lock or a wait for the UI thread
runtimes.post(fn) Calls fn on React Native with its arguments copied first
runtimes.callable(fn) Runs a worklet where it is called, and any other function through post
runtimes.local(create) The same handle as own on the calling thread, without worklets: the JS thread, the web and tests
runtimes.camera(runtime) VisionCamera frames for a video feed on runtime, handed over from the camera thread

Start a scene on a worker using these runtime functions:

blockfall-host.ts
import {
function animate(loop: Pick<Frames, "run"> & Partial<Pick<Frames, "seconds">>, options?: AnimateOptions): Animation

Runs the loop on each scheduled frame. Time advances by at most maxDt per frame, never steps back, and stops while the surface is paused. Destroying the animation keeps the loop alive. Another animation on the same loop continues from its recorded time. A frame error stops the animation.

@param ― loop the loop to run on each unpaused frame. Continues from its seconds if it ran before

@returns ― an animation you destroy to stop the frames

animate
,
function create(root: TgpuRoot, record: Recorder): Frames

Records a frame into one encoder and submits once, with a clock. Borrows the root. The synchronous recorder borrows each frame's encoder.

@submits ― always once a frame after your recorder returns, and again at each frame.submit()

@param ― record your frame, recorded into each frame's encoder

@returns ― a loop you run with each frame's time and destroy

create
as
function createFrames(root: TgpuRoot, record: Recorder): Frames

Records a frame into one encoder and submits once, with a clock. Borrows the root. The synchronous recorder borrows each frame's encoder.

@submits ― always once a frame after your recorder returns, and again at each frame.submit()

@param ― record your frame, recorded into each frame's encoder

@returns ― a loop you run with each frame's time and destroy

createFrames
} from '@typegpu/kit/frames';
import {
function surface(root: TgpuRoot, canvas: Canvas, options: SurfaceOptions): Surface

Creates a react-native-webgpu canvas surface for presentation on the JS thread. Throws if the canvas has no WebGPU context or the fixed size is invalid.

@param ― canvas the CanvasRef from a <Canvas ref>; its context's canvas holds the size

@param ― options.pixelRatio PixelRatio.get()

@returns ― a surface you resize, pause, resume and destroy

surface
as
function canvasSurface(root: TgpuRoot, canvas: Canvas, options: SurfaceOptions): Surface

Creates a react-native-webgpu canvas surface for presentation on the JS thread. Throws if the canvas has no WebGPU context or the fixed size is invalid.

@param ― canvas the CanvasRef from a <Canvas ref>; its context's canvas holds the size

@param ― options.pixelRatio PixelRatio.get()

@returns ― a surface you resize, pause, resume and destroy

canvasSurface
} from '@typegpu/kit/native';
import * as
import runtimes
runtimes
from '@typegpu/kit/runtimes';
import {
const tgpu: {
const: typeof import("node_modules/typegpu/core/constant/tgpuConstant").constant;
fn: typeof import("node_modules/typegpu/core/function/tgpuFn").fn;
comptime: typeof import("node_modules/typegpu/core/function/comptime").comptime;
resolve: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolve;
resolveWithContext: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolveWithContext;
init: typeof import("node_modules/typegpu/core/root/init").init;
initFromDevice: typeof import("node_modules/typegpu/core/root/init").initFromDevice;
slot: typeof import("node_modules/typegpu/core/slot/slot").slot;
lazy: typeof import("node_modules/typegpu/core/slot/lazy").lazy;
... 10 more ...;
'~unstable': typeof import("node_modules/typegpu/tgpuUnstable");
}

@module ― typegpu

tgpu
} from 'typegpu';
import {
function createScene(root: TgpuRoot, surface: Surface, options: {
onScore: (score: number) => void;
}): Promise<{
frame(frame: Frame): void;
destroy(): void;
game: {
drop(): void;
};
}>
createScene
} from './scene.ts';
const
const worker: WorkletRuntime
worker
=
import runtimes
runtimes
.
function worker(options?: runtimes.WorkerOptions): WorkletRuntime

Creates a worker runtime with WebGPU installed and a minimum delay for zero-delay timers. Worklets cannot end a runtime. It lives until nothing references it.

@returns ― a runtime to own values on

worker
();
const
const display: runtimes.Vsync
display
=
import runtimes
runtimes
.
function vsync(runtime: runtimes.Runtime): runtimes.Vsync

Relays display-linked frames from the UI runtime to a worker. A worker's own requestAnimationFrame uses a 16 ms timer. Sends one frame at a time, skipping and counting display frames that arrive while it runs. Pass it as schedule to frames.animate on that runtime.

@param ― runtime the only runtime allowed to request these frames

@returns ― a schedule you destroy to stop the relay

vsync
(
const worker: WorkletRuntime
worker
);
export interface
interface Start
Start
{
readonly
Start.device: GPUDevice
device
:
interface GPUDevice

The GPUDevice interface of the WebGPU API represents a logical GPU device. This is the main interface through which the majority of WebGPU functionality is accessed. Available only in secure contexts.

MDN Reference

GPUDevice
;
readonly
Start.context: GPUCanvasContext & {
present(): void;
}
context
:
interface GPUCanvasContext

The GPUCanvasContext interface of the WebGPU API represents the WebGPU rendering context of a element, returned via an HTMLCanvasElement.getContext() call with a contextType of "webgpu". Available only in secure contexts.

MDN Reference

GPUCanvasContext
& {
function present(): void
present
(): void };
readonly
Start.size: {
readonly width: number;
readonly height: number;
}
size
: { readonly
width: number
width
: number; readonly
height: number
height
: number };
readonly
Start.pixelRatio: number
pixelRatio
: number;
readonly
Start.onScore: (score: number) => void
onScore
: (
score: number
score
: number) => void;
}
export function
function startBlockfall(start: Start): runtimes.Owned<{
scene: {
frame(frame: Frame): void;
destroy(): void;
game: {
drop(): void;
};
};
surface: Surface;
destroy: () => void;
}>
startBlockfall
(
start: Start
start
:
interface Start
Start
) {
const {
const device: GPUDevice
device
,
const context: GPUCanvasContext & {
present(): void;
}
context
,
const size: {
readonly width: number;
readonly height: number;
}
size
,
const pixelRatio: number
pixelRatio
} =
start: Start
start
;
const
const options: {
onScore: runtimes.Callback<[score: number]>;
}
options
= {
onScore: runtimes.Callback<[score: number]>
onScore
:
import runtimes
runtimes
.
function post<[score: number]>(fn: (score: number) => void): runtimes.Callback<[score: number]>

Calls fn on the React Native JS thread from any runtime. Copies arguments first, because worklets caches sent objects by identity. Otherwise, changing an object in place and sending it again delivers its first copy. Release builds omit the warning for this case.

@param ― fn a React Native function

@returns ― a worklet that calls it

post
(
start: Start
start
.
Start.onScore: (score: number) => void
onScore
) };
return
import runtimes
runtimes
.
function own<{
scene: {
frame(frame: Frame): void;
destroy(): void;
game: {
drop(): void;
};
};
surface: Surface;
destroy: () => void;
}>(runtime: runtimes.Runtime, create: () => {
scene: {
frame(frame: Frame): void;
destroy(): void;
game: {
drop(): void;
};
};
surface: Surface;
destroy: () => void;
} | Promise<{
scene: {
frame(frame: Frame): void;
destroy(): void;
game: {
drop(): void;
};
};
surface: Surface;
destroy: () => void;
}>, options?: runtimes.OwnOptions): runtimes.Owned<...>

Creates and keeps a value on runtime. create must be a worklet and may be asynchronous. Calls sent before it resolves wait in order. A worklets Shareable holds the value on that runtime. Nothing registers or looks it up by name.

@param ― runtime where the value lives

@param ― create a worklet that makes the value, there

@returns ― a handle you send closures to and destroy

own
(
const worker: WorkletRuntime
worker
,
async () => {
'worklet';
const
const root: TgpuRoot
root
=
const tgpu: {
const: typeof import("node_modules/typegpu/core/constant/tgpuConstant").constant;
fn: typeof import("node_modules/typegpu/core/function/tgpuFn").fn;
comptime: typeof import("node_modules/typegpu/core/function/comptime").comptime;
resolve: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolve;
resolveWithContext: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolveWithContext;
init: typeof import("node_modules/typegpu/core/root/init").init;
initFromDevice: typeof import("node_modules/typegpu/core/root/init").initFromDevice;
slot: typeof import("node_modules/typegpu/core/slot/slot").slot;
lazy: typeof import("node_modules/typegpu/core/slot/lazy").lazy;
... 10 more ...;
'~unstable': typeof import("node_modules/typegpu/tgpuUnstable");
}

@module ― typegpu

tgpu
.
initFromDevice: (options: InitFromDeviceOptions) => TgpuRoot

Creates a root from the given device, instead of requesting it like

@see ― init. *

@example

const device: GPUDevice = ...;
const root = tgpu.initFromDevice({ device });

initFromDevice
({
device: GPUDevice
device
});
const
const surface: Surface
surface
=
function canvasSurface(root: TgpuRoot, canvas: Canvas, options: SurfaceOptions): Surface

Creates a react-native-webgpu canvas surface for presentation on the JS thread. Throws if the canvas has no WebGPU context or the fixed size is invalid.

@param ― canvas the CanvasRef from a <Canvas ref>; its context's canvas holds the size

@param ― options.pixelRatio PixelRatio.get()

@returns ― a surface you resize, pause, resume and destroy

canvasSurface
(
const root: TgpuRoot
root
, {
Canvas.getContext(type: "webgpu"): (GPUCanvasContext & {
present(): void;
}) | null
getContext
: () =>
const context: GPUCanvasContext & {
present(): void;
}
context
}, {
SurfaceOptions.pixelRatio: number

Sets the display pixel ratio from PixelRatio.get().

pixelRatio
});
const surface: Surface
surface
.
Surface.resize(width: number, height: number): void

Sizes the drawing buffer from the canvas's layout size in points, as onLayout reports it.

resize
(
const size: {
readonly width: number;
readonly height: number;
}
size
.
width: number
width
,
const size: {
readonly width: number;
readonly height: number;
}
size
.
height: number
height
);
const
const scene: {
frame(frame: Frame): void;
destroy(): void;
game: {
drop(): void;
};
}
scene
= await
function createScene(root: TgpuRoot, surface: Surface, options: {
onScore: (score: number) => void;
}): Promise<{
frame(frame: Frame): void;
destroy(): void;
game: {
drop(): void;
};
}>
createScene
(
const root: TgpuRoot
root
,
const surface: Surface
surface
,
const options: {
onScore: runtimes.Callback<[score: number]>;
}
options
);
const
const loop: Frames
loop
=
function createFrames(root: TgpuRoot, record: Recorder): Frames

Records a frame into one encoder and submits once, with a clock. Borrows the root. The synchronous recorder borrows each frame's encoder.

@submits ― always once a frame after your recorder returns, and again at each frame.submit()

@param ― record your frame, recorded into each frame's encoder

@returns ― a loop you run with each frame's time and destroy

createFrames
(
const root: TgpuRoot
root
, (
frame: Frame
frame
) =>
const scene: {
frame(frame: Frame): void;
destroy(): void;
game: {
drop(): void;
};
}
scene
.
function frame(frame: Frame): void
frame
(
frame: Frame
frame
));
const
const animation: Animation
animation
=
function animate(loop: Pick<Frames, "run"> & Partial<Pick<Frames, "seconds">>, options?: AnimateOptions): Animation

Runs the loop on each scheduled frame. Time advances by at most maxDt per frame, never steps back, and stops while the surface is paused. Destroying the animation keeps the loop alive. Another animation on the same loop continues from its recorded time. A frame error stops the animation.

@param ― loop the loop to run on each unpaused frame. Continues from its seconds if it ran before

@returns ― an animation you destroy to stop the frames

animate
(
const loop: Frames
loop
, {
AnimateOptions.surface?: {
readonly paused: boolean;
} | undefined

Stops frames and elapsed time while paused is true. The first frame after resume() has a dt of 0. On React Native, pass the canvas surface and pause it from AppState. On the web, pause it from an IntersectionObserver or a pause menu. Without a surface, frames run until the animation is destroyed. Default none.

surface
,
AnimateOptions.schedule?: runtimes.Schedule | undefined

Supplies animation frames. Use runtimes.vsync(worker) on a React Native worker runtime, or a schedule you step by hand in a test. Default requestAnimationFrame.

schedule
:
const display: runtimes.Vsync
display
,
AnimateOptions.onError?: ((error: unknown) => void) | undefined

Receives a frame error after the animation stops. Without a handler, the animation frame callback throws the error. Default none.

onError
:
var console: Console
console
.
Console.error(...data: any[]): void (+1 overload)

The console.error() static method outputs a message to the console at the "error" log level. The message is only displayed to the user if the console is configured to display error output. In most cases, the log level is configured within the console UI. The message may be formatted as an error, with red colors and call stack information.

MDN Reference

error
});
const
const destroy: () => void
destroy
= () => {
const animation: Animation
animation
.
Animation.destroy(): void

Stops requesting frames, even when called from a frame. Keeps the loop alive. Also [Symbol.dispose].

destroy
();
const loop: Frames
loop
.
Frames.destroy(): void

Stops future submissions and keeps borrowed resources alive. Also [Symbol.dispose].

destroy
();
const scene: {
frame(frame: Frame): void;
destroy(): void;
game: {
drop(): void;
};
}
scene
.
function destroy(): void
destroy
();
const surface: Surface
surface
.
Surface.destroy(): void

Releases the drawing destination. Canvas surfaces unconfigure their contexts. Web surfaces also stop observing the canvas. Later calls to view throw. Also [Symbol.dispose].

destroy
();
const root: TgpuRoot
root
.
TgpuRoot.destroy(): void

Releases internal resource caches (e.g. utility texture pipelines), letting the GC reclaim them.

Buffers, textures and other resources created through the root are NOT destroyed.

If the object is created via tgpu.init instead of tgpu.initFromDevice, destroys the underlying WebGPU device as well.

destroy
();
};
return {
scene: {
frame(frame: Frame): void;
destroy(): void;
game: {
drop(): void;
};
}
scene
,
surface: Surface
surface
,
destroy: () => void
destroy
};
},
{
OwnOptions.onError?: runtimes.Callback<[unknown]> | undefined

Receives errors from create, sent closures and calls that worklets cannot schedule. An uncaught error in a worklet otherwise aborts a release build. Default console.error on the runtime that owns the value.

onError
:
var console: Console
console
.
Console.error(...data: any[]): void (+1 overload)

The console.error() static method outputs a message to the console at the "error" log level. The message is only displayed to the user if the console is configured to display error output. In most cases, the log level is configured within the console UI. The message may be formatted as an error, with red colors and call stack information.

MDN Reference

error
},
);
}
App.tsx
const blockfall = startBlockfall({
device,
context,
size,
pixelRatio: PixelRatio.get(),
onScore: setScore,
});
blockfall.send(({ scene }) => {
'worklet';
scene.game.drop();
});
  • own keeps any value returned by create. The callback can use the same setup code as a web page.
  • Named imports let worklets load createScene, createFrames, animate and tgpu on the worker, including their shaders. Namespace imports such as import * as frames copy the namespace instead. Its functions can’t be called on that runtime, so the worklet uses none.
  • send(fn, ...args) runs fn with the value on its runtime, after everything sent before it; calls made while create runs wait for it. send is a worklet itself, so a Gesture Handler callback on the UI runtime sends straight to the worker.
  • run does the same from React Native and copies the result back as post does. An async closure is awaited on the value’s runtime, since a promise can’t cross runtimes.
  • send reuses cached argument copies. If you mutate an object and resend it, the receiver gets its first copy. Send another object to transfer changed values.
  • Errors from create, sent closures or scheduling send reach onError. destroy waits for earlier sent work, calls the value’s destroy on its runtime and releases it.

vsync(worker) relays the UI runtime’s display link. A worker requests a frame and the next vsync schedules it there. If the previous frame is still running, the relay skips that vsync and increments missed(). Give it to animate as schedule, which defaults to requestAnimationFrame; a test passes a schedule it steps by hand.

const pointer = runtimes.cell({ x: 0, y: 0, down: false });
// a gesture on the UI runtime
pointer.write({ x: event.x, y: event.y });
// the scene's frame, on the worker; `here` is an object the scene made once
pointer.read(here);
  • A cell is one fixed-type synchronizable per field, so a read takes no lock and allocates nothing. Fields are read one at a time, so a read can mix two writes.
  • It holds the latest value only. Send events that mustn’t be lost, like a tap or a key, through send, which queues them in order.
  • post(fn) calls React Native code with copied arguments. A reused object arrives with its values at the time of each call.
  • Worklet callbacks run on the calling runtime. Other callbacks run on React Native with copied arguments.
  • A renderer of your own. own(worker, createRenderer) holds it, and vsync paces it. Nothing needs frames or a TypeGPU Kit scene.
  • @typegpu/react. The resources it transfers keep working; runtimes registers no serializer of its own.
  • Reanimated and Gesture Handler. Gesture worklets write cells or call send. A shared value can go in a scene’s options, but reading one on a worker waits for the UI thread.
  • A runtime someone else made. own and vsync take any runtime.
  • Compiling off the frame’s runtime. own(worker, warm) compiles pipelines while the UI runtime draws. Dawn shares its pipeline cache across runtimes.

In a test, a scene builds on recordingRoot() with no worklets. local gives the same Owned on the test’s thread, and a hand-stepped schedule drives animate.

  • local lives in @typegpu/kit/hosting with Owned and the scene types, which import nothing from React Native, so a test or a web page needs no mock of worklets. runtimes re-exports it.
  • runtimes and scenes import react-native-worklets; only scenes/react imports React.
  • runtimes.cell needs worklets, so in its test a scene that reads one takes any object with the same read and write:
const values = { x: 0, active: false };
const input = {
read: (into: typeof values) => Object.assign(into, values),
write: (next: Partial<typeof values>) => void Object.assign(values, next),
};
Runtime Holds
JS thread the device, device.lost, React state, the hook, assets
UI runtime the display link and its relay, Gesture Handler’s callbacks, Reanimated
the scene’s runtime the root, the modules, recording, submit, present and readback maps

Use one runtime to record into a canvas and its encoders. Other runtimes may share the device with implicit-device-synchronization, which react-native-webgpu requests by default. They must not share that canvas or those encoders.

  • Bundle Mode with importForwarding and worklets’ patch to Metro. Setting up a project has the configs.
  • A one-line worklet that calls createScene off the JS thread, and named imports inside worklets.
  • The relay’s callback on the UI runtime at every display frame, even while the scene is paused.
  • Worklets can’t destroy runtimes. destroy releases the scene while its runtime stays alive.
  • A worker created by runtimes.worker delays zero-delay setTimeout calls by 2 ms. Set worker({ timerFloor: 0 }) to disable this floor.
  • Your own scheduled worklets still abort a Release build when they throw; code sent through send is guarded.
  • Only functions at the top level of a scene’s options run on React Native by themselves. A callback nested deeper needs runtimes.post by hand.
  • A cell has no version a reader can check, so it suits pointers and sticks, not a value that must stay whole, such as a transform.
  • No host runs a scene in a web worker.