React Native Worklets
With react-native-worklets, per-frame GPU work can be scheduled on the UI thread, unaffected by tasks running on the default React Native thread (or RN thread). TypeGPU resources created on the RN thread can be captured by worklets directly, they are transferred between runtimes automatically.
Follow the React Native guide first, then install react-native-worklets and enable its babel plugin with Bundle Mode:
const workletsPluginOptions = { bundleMode: true, importForwarding: { moduleNames: ['typegpu'], // Directories with your module-scope shader definitions relativePaths: ['my-app/components'], },};
module.exports = (api) => { api.cache(true); return { presets: ['babel-preset-expo'], plugins: [ 'unplugin-typegpu/babel', ['react-native-worklets/plugin', workletsPluginOptions], ], };};No extra imports are needed - @typegpu/react detects react-native-worklets at runtime and registers the transfer support for TypeGPU resources automatically.
While that integration is enabled, every useFrame callback must be marked with the 'worklet' directive and runs on the UI thread.
A plain callback throws; use disableWorklets to run frame callbacks on the RN thread instead.
After changing the babel config, clear the Metro cache with npx expo start --clear.
Example
Section titled “Example”Create resources on the RN thread, then use them freely inside a useFrame worklet:
import { useMemo } from 'react';import { Canvas } from 'react-native-webgpu';import tgpu, { common, d } from 'typegpu';import { useConfigureContext, useFrame, useRoot, useUniform } from '@typegpu/react';
export function Pulse() { const root = useRoot(); const color = useUniform(d.vec3f, { initial: d.vec3f(0.114, 0.447, 0.941) });
const pipeline = useMemo( () => root.createRenderPipeline({ vertex: common.fullScreenTriangle, fragment: () => { 'use gpu'; return d.vec4f(color.$, 1); }, }), [root, color], );
const { ref, ctxRef } = useConfigureContext({ alphaMode: 'premultiplied' });
// Runs each frame on the UI thread useFrame(({ elapsedSeconds }) => { 'worklet'; const ctx = ctxRef.current; if (!ctx) return;
color.write(d.vec3f(0.5 + Math.sin(elapsedSeconds) * 0.5, 0.447, 0.941)); pipeline.withColorAttachment({ view: ctx }).draw(3); ctx.present?.(); });
return <Canvas ref={ref} style={{ aspectRatio: 1 }} transparent />;}The color uniform and pipeline captured by the worklet are transferred to the UI runtime on first use.
Both runtimes share the same underlying GPU objects, and transferring the same resource again yields the same object back.
This works for buffers (including createUniform/createMutable/createReadonly), textures, samplers, bind groups and their layouts, vertex layouts, query sets, pipelines, roots, slots, accessors, and consts.
Schemas stored by those resources are reconstructed automatically.
Standalone data schemas and vector/matrix instances cannot be captured directly by worklets.
Opting out
Section titled “Opting out”To keep everything on the RN thread even with react-native-worklets installed, pass disableWorklets to the Root provider:
<Root disableWorklets> <Pulse /></Root>useFrame then runs its callbacks on the RN thread, and useConfigureContext returns a plain object of the same shape, so the code above keeps working unchanged.
Rules of transfer
Section titled “Rules of transfer”Definitions are runtime-local.
Shader functions (tgpu.fn, entry functions) and tgpu.comptime cannot be serialized.
Make sure to create them on the runtime they will be used on, or keep them at module scope in files covered by importForwarding; worklets then re-import them natively on the UI runtime instead of transferring them.
Functions crossing runtimes must be worklets.
Callbacks stored directly by a transferred resource (e.g. a withPerformanceCallback callback) have to be marked with the 'worklet' directive.
Attachments and passes stay on their runtime.
A pipeline carrying a color attachment, a depth-stencil attachment, or a bound pass or command encoder throws when transferred, since those hold views and encoders local to the runtime that made them.
Transfer the bare pipeline and apply them on the other side, as the example above does with withColorAttachment.
Shader logs stay on the runtime that resolved the pipeline.
A transferred pipeline still runs its console.log calls, but only the runtime that resolved it prints them.
Query sets resolve and read on one runtime.
A transferred query set shares the underlying GPUQuerySet, but each runtime keeps its own resolve and read buffers.
Call resolve() and read() on the same side, and do not resolve into one query set from both.