Migrating to 0.12
TypeGPU 0.12 removes several APIs that were deprecated in previous releases. This guide covers the required changes, as well as behavioral changes in companion packages.
Use the named tgpu export
Section titled “Use the named tgpu export”The default export is still available, but the named export is now recommended. It improves tree-shaking and keeps all TypeGPU imports consistent.
import tgpu, { d, std } from 'typegpu';import { tgpu, d, std } from 'typegpu';Replace .value with .$
Section titled “Replace .value with .$”The deprecated .value alias has been removed from TypeGPU resources. Use .$ to access their GPU-side value.
This applies to buffers created with createUniform, createReadonly and createMutable, as well as constants, variables, slots, accessors, lazy values, textures, samplers, raw code snippets and bind group layouts.
const time = root.createUniform(d.f32);const scale = tgpu.const(d.f32, 2);
const main = () => { 'use gpu'; return time.value * scale.value; return time.$ * scale.$;};Replace layout.bound with layout.$
Section titled “Replace layout.bound with layout.$”The deprecated layout.bound object has been removed. Layout entries are accessed directly through layout.$.
const layout = tgpu.bindGroupLayout({ size: { uniform: d.vec2u },});
const getSize = () => { 'use gpu'; return layout.bound.size.$; return layout.$.size;};Do not use strings as shader values
Section titled “Do not use strings as shader values”Plain strings are no longer treated as raw shader expressions when passed through slots or other resolvable APIs. Use a typed TypeGPU value instead. Strings can still be used for comptime branching when they do not become part of the generated shader.
const colorSlot = tgpu.slot<string>('vec3f(1, 0, 0)');const colorSlot = tgpu.slot<d.v3f>(d.vec3f(1, 0, 0));
const getColor = tgpu.fn([], d.vec3f)`() { return colorSlot;}`.$uses({ colorSlot });
const green = getColor.with(colorSlot, 'vec3f(0, 1, 0)');const green = getColor.with(colorSlot, d.vec3f(0, 1, 0));For intentional raw WGSL integration, use tgpu['~unstable'].rawCodeSnippet(...) or tgpu['~unstable'].declare(...) so that the expression’s type and dependencies are explicit.
Replace the old pipeline builder
Section titled “Replace the old pipeline builder”The deprecated withVertex(...).withFragment(...).createPipeline() and withCompute(...).createPipeline() APIs have been removed. Pass the stages and their configuration directly to createRenderPipeline or createComputePipeline.
const renderPipeline = root .withVertex(vertex, { position: vertexLayout.attrib }) .withFragment(fragment, { format: 'rgba8unorm' }) .createPipeline();const renderPipeline = root.createRenderPipeline({ vertex, fragment, attribs: { position: vertexLayout.attrib }, targets: { format: 'rgba8unorm' },});
const computePipeline = root .withCompute(compute) .createPipeline();const computePipeline = root.createComputePipeline({ compute });Update texture bind group layouts
Section titled “Update texture bind group layouts”The old string-based texture layout descriptors have been removed. Use texture data schemas instead.
const layout = tgpu.bindGroupLayout({ sampled: { texture: 'float', viewDimension: '2d' }, sampled: { texture: d.texture2d(d.f32) },
output: { storageTexture: 'rgba8unorm', access: 'writeonly', viewDimension: '2d', }, output: { storageTexture: d.textureStorage2d('rgba8unorm', 'write-only') },
frame: { externalTexture: {} }, frame: { externalTexture: d.textureExternal() },});For sampled float textures that cannot be used with a filtering sampler, keep the schema as d.texture2d(d.f32) and add sampleType: 'unfilterable-float' to the layout entry.
Make image resizing explicit
Section titled “Make image resizing explicit”Writing an image source to a texture now requires the texture to have the 'render' usage. If the source and texture dimensions differ, pass { fit: 'stretch' } to opt into resampling; otherwise, .write() throws instead of resizing implicitly.
const texture = root.createTexture({ size: [256, 256], format: 'rgba8unorm',}).$usage('sampled');}).$usage('sampled', 'render');
texture.write(image);texture.write(image, { fit: 'stretch' });If the image already has the same dimensions as the texture, no fit option is needed, but the 'render' usage is still required. Writes from an ArrayBuffer, typed array or DataView are unchanged.
Use WGSL-compatible expression types
Section titled “Use WGSL-compatible expression types”Runtime expressions now reject JavaScript truthiness and comparisons that WGSL cannot represent directly:
&&,||and unary!require boolean operands.<,<=,>and>=require numeric scalar operands.===and!==require numeric or boolean scalar operands.
Convert numeric values explicitly with d.bool, and use the component-wise helpers from std for vectors.
const check = tgpu.fn([d.u32, d.bool, d.vec3f, d.vec3f])((count, enabled, a, b) => { 'use gpu'; const isEmpty = !count; const isVisible = count && enabled; const compared = a < b; const isEmpty = !d.bool(count); const isVisible = d.bool(count) && enabled; const compared = std.lt(a, b);});The d.bool constructor itself now accepts only booleans and numeric scalars. std.not accepts booleans and boolean vectors; use d.bool(value) before negating a numeric scalar.
Runtime ternaries also reject struct, array, vector or matrix branches that alias existing values, because WGSL’s select returns a copy while JavaScript would keep a reference. Copy the branches explicitly or use an if/else statement.
const position = enabled ? boid.position : boid.velocity;const position = enabled ? d.vec3f(boid.position) : d.vec3f(boid.velocity);Update command recording callbacks
Section titled “Update command recording callbacks”The experimental callback-based beginRenderPass and beginRenderBundleEncoder APIs have been replaced by explicit encoder objects.
root['~unstable'].beginRenderPass( { colorAttachments: [{ view: context }] }, (pass) => { scenePipeline.with(pass).draw(sceneVertexCount); overlayPipeline.with(pass).draw(overlayVertexCount); },);const encoder = root['~unstable'].createCommandEncoder();const pass = encoder.beginRenderPass({ colorAttachments: { view: context },});scenePipeline.with(pass).draw(sceneVertexCount);overlayPipeline.with(pass).draw(overlayVertexCount);pass.end();encoder.submit();For render bundles, replace beginRenderBundleEncoder(descriptor, callback) with createRenderBundleEncoder(descriptor), record commands on the returned encoder, and call .finish() yourself.
Unified buffer bindings
Section titled “Unified buffer bindings”root.createUniform(...), root.createReadonly(...), root.createMutable(...) and buffer.as(...) now return the same kind of object, called a buffer binding. As a result, bindings can be passed directly to matching bind group entries.
const size = root.createUniform(d.vec2u, d.vec2u(800, 600));const layout = tgpu.bindGroupLayout({ size: { uniform: d.vec2u },});
root.createBindGroup(layout, { size });The TgpuBufferUniform, TgpuBufferReadonly and TgpuBufferMutable type aliases are deprecated. Replace them with TgpuUniform, TgpuReadonly and TgpuMutable respectively. isBufferShorthand is also deprecated in favor of isBufferBinding.
If you inspect resourceType at runtime, note that a value returned from buffer.as(...) now reports 'uniform', 'readonly' or 'mutable' instead of 'buffer-usage'. Prefer the isUniformBinding, isReadonlyBinding and isMutableBinding type guards.
Remove calls to flush()
Section titled “Remove calls to flush()”root['~unstable'].flush() was deprecated and had no effect. Remove the call. Direct pipeline executions submit their work immediately, while the new command encoder API submits work when you call encoder.submit().
Custom shader generators passed to roots
Section titled “Custom shader generators passed to roots”The unstable root option for a custom shader generator now accepts a class, so that TypeGPU can create a fresh generator for every resolution.
const root = await tgpu.init({ shaderGenerator: new CustomGenerator(),});const root = await tgpu.init({ unstable_shaderGeneratorClass: CustomGenerator,});This only affects tgpu.init and tgpu.initFromDevice. The tgpu.resolve option remains unstable_shaderGenerator and still accepts an instance.
React Native useFrame with Worklets
Section titled “React Native useFrame with Worklets”When react-native-worklets is installed, @typegpu/react now runs useFrame callbacks on the UI thread. Mark every callback with the 'worklet' directive.
useFrame(({ elapsedSeconds }) => { 'worklet'; time.write(elapsedSeconds); pipeline.withColorAttachment({ view: ctxRef.current }).draw(3);});If you want to keep frame callbacks on the RN thread, opt out for that subtree instead:
<Root disableWorklets> <App /></Root>Refer to the React Native Worklets guide for Babel setup and the rules around transferring TypeGPU resources.
New pseudo-random number generator in @typegpu/noise
Section titled “New pseudo-random number generator in @typegpu/noise”Along with @typegpu/noise v0.12.0, a more robust and efficient pseudo-random number generator became the default: Xoroshiro64**, introduced by David Blackman and Sebastiano Vigna in “Scrambled Linear Pseudorandom Number Generators”.
This changes the sequence returned by randf for the same seed. If your app depends on the previous implementation’s exact behavior, you can use the following code to keep using the legacy generator:
import { const tgpu: { const: typeof import("node_modules/typegpu/src/core/constant/tgpuConstant").constant; fn: typeof import("node_modules/typegpu/src/core/function/tgpuFn").fn; comptime: typeof import("node_modules/typegpu/src/core/function/comptime").comptime; resolve: typeof import("node_modules/typegpu/src/core/resolve/tgpuResolve").resolve; resolveWithContext: typeof import("node_modules/typegpu/src/core/resolve/tgpuResolve").resolveWithContext; init: typeof import("node_modules/typegpu/src/core/root/init").init; initFromDevice: typeof import("node_modules/typegpu/src/core/root/init").initFromDevice; slot: typeof import("node_modules/typegpu/src/core/slot/slot").slot; lazy: typeof import("node_modules/typegpu/src/core/slot/lazy").lazy; ... 10 more ...; '~unstable': typeof import("node_modules/typegpu/src/tgpuUnstable");}
tgpu, import d
d, import std
std } from 'typegpu';import { const randf: { seed: typeof randSeed; seed2: typeof randSeed2; seed3: typeof randSeed3; seed4: typeof randSeed4; sample: typeof randFloat01; sampleExclusive: typeof randUniformExclusive; normal: typeof randNormal; exponential: typeof randExponential; cauchy: typeof randCauchy; bernoulli: typeof randBernoulli; ... 7 more ...; onUnitSphere: typeof randOnUnitSphere;}
randf, const randomGeneratorSlot: TgpuSlot<StatefulGenerator>
randomGeneratorSlot, const randomGeneratorShell: TgpuFnShell<[], d.F32>
randomGeneratorShell, type (alias) interface StatefulGeneratorimport StatefulGenerator
StatefulGenerator,} from '@typegpu/noise';
/** * Incorporated from https://www.cg.tuwien.ac.at/research/publications/2023/PETER-2023-PSW/PETER-2023-PSW-.pdf * "Particle System in WebGPU" by Benedikt Peter */const const BPETER11: StatefulGenerator
Incorporated from https://www.cg.tuwien.ac.at/research/publications/2023/PETER-2023-PSW/PETER-2023-PSW-.pdf
"Particle System in WebGPU" by Benedikt Peter
BPETER11: (alias) interface StatefulGeneratorimport StatefulGenerator
StatefulGenerator = (() => { const const seed: TgpuVar<"private", d.Vec2f>
seed = const tgpu: { const: typeof import("node_modules/typegpu/src/core/constant/tgpuConstant").constant; fn: typeof import("node_modules/typegpu/src/core/function/tgpuFn").fn; comptime: typeof import("node_modules/typegpu/src/core/function/comptime").comptime; resolve: typeof import("node_modules/typegpu/src/core/resolve/tgpuResolve").resolve; resolveWithContext: typeof import("node_modules/typegpu/src/core/resolve/tgpuResolve").resolveWithContext; init: typeof import("node_modules/typegpu/src/core/root/init").init; initFromDevice: typeof import("node_modules/typegpu/src/core/root/init").initFromDevice; slot: typeof import("node_modules/typegpu/src/core/slot/slot").slot; lazy: typeof import("node_modules/typegpu/src/core/slot/lazy").lazy; ... 10 more ...; '~unstable': typeof import("node_modules/typegpu/src/tgpuUnstable");}
tgpu.privateVar: <d.Vec2f>(dataType: d.Vec2f, initialValue?: d.v2f | undefined) => TgpuVar<"private", d.Vec2f>
Defines a variable scoped to each entry function (private).
privateVar(import d
d.const vec2f: d.Vec2fexport vec2f
Schema representing vec2f - a vector with 2 elements of type f32.
Also a constructor function for this vector value.
vec2f);
return { StatefulGenerator.seed?: ((seed: number) => void) | undefined
seed: const tgpu: { const: typeof import("node_modules/typegpu/src/core/constant/tgpuConstant").constant; fn: typeof import("node_modules/typegpu/src/core/function/tgpuFn").fn; comptime: typeof import("node_modules/typegpu/src/core/function/comptime").comptime; resolve: typeof import("node_modules/typegpu/src/core/resolve/tgpuResolve").resolve; resolveWithContext: typeof import("node_modules/typegpu/src/core/resolve/tgpuResolve").resolveWithContext; init: typeof import("node_modules/typegpu/src/core/root/init").init; initFromDevice: typeof import("node_modules/typegpu/src/core/root/init").initFromDevice; slot: typeof import("node_modules/typegpu/src/core/slot/slot").slot; lazy: typeof import("node_modules/typegpu/src/core/slot/lazy").lazy; ... 10 more ...; '~unstable': typeof import("node_modules/typegpu/src/tgpuUnstable");}
tgpu.fn: <[d.F32]>(argTypes: [d.F32], returnType?: undefined) => TgpuFnShell<[d.F32], d.Void> (+2 overloads)
fn([import d
d.const f32: d.F32export f32
A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)
Can also be called to cast a value to an f32.
f32])((value: number
value) => { const seed: TgpuVar<"private", d.Vec2f>
seed.TgpuVar<"private", Vec2f>.$: d.v2f
$ = import d
d.function vec2f(x: number, y: number): d.v2f (+3 overloads)export vec2f
Schema representing vec2f - a vector with 2 elements of type f32.
Also a constructor function for this vector value.
vec2f(value: number
value, 0); }),
StatefulGenerator.seed2?: ((seed: d.v2f) => void) | undefined
seed2: const tgpu: { const: typeof import("node_modules/typegpu/src/core/constant/tgpuConstant").constant; fn: typeof import("node_modules/typegpu/src/core/function/tgpuFn").fn; comptime: typeof import("node_modules/typegpu/src/core/function/comptime").comptime; resolve: typeof import("node_modules/typegpu/src/core/resolve/tgpuResolve").resolve; resolveWithContext: typeof import("node_modules/typegpu/src/core/resolve/tgpuResolve").resolveWithContext; init: typeof import("node_modules/typegpu/src/core/root/init").init; initFromDevice: typeof import("node_modules/typegpu/src/core/root/init").initFromDevice; slot: typeof import("node_modules/typegpu/src/core/slot/slot").slot; lazy: typeof import("node_modules/typegpu/src/core/slot/lazy").lazy; ... 10 more ...; '~unstable': typeof import("node_modules/typegpu/src/tgpuUnstable");}
tgpu.fn: <[d.Vec2f]>(argTypes: [d.Vec2f], returnType?: undefined) => TgpuFnShell<[d.Vec2f], d.Void> (+2 overloads)
fn([import d
d.const vec2f: d.Vec2fexport vec2f
Schema representing vec2f - a vector with 2 elements of type f32.
Also a constructor function for this vector value.
vec2f])((value: d.v2f
value) => { const seed: TgpuVar<"private", d.Vec2f>
seed.TgpuVar<"private", Vec2f>.$: d.v2f
$ = import d
d.function vec2f(v: AnyNumericVec2Instance): d.v2f (+3 overloads)export vec2f
Schema representing vec2f - a vector with 2 elements of type f32.
Also a constructor function for this vector value.
vec2f(value: d.v2f
value); }),
StatefulGenerator.seed3?: ((seed: d.v3f) => void) | undefined
seed3: const tgpu: { const: typeof import("node_modules/typegpu/src/core/constant/tgpuConstant").constant; fn: typeof import("node_modules/typegpu/src/core/function/tgpuFn").fn; comptime: typeof import("node_modules/typegpu/src/core/function/comptime").comptime; resolve: typeof import("node_modules/typegpu/src/core/resolve/tgpuResolve").resolve; resolveWithContext: typeof import("node_modules/typegpu/src/core/resolve/tgpuResolve").resolveWithContext; init: typeof import("node_modules/typegpu/src/core/root/init").init; initFromDevice: typeof import("node_modules/typegpu/src/core/root/init").initFromDevice; slot: typeof import("node_modules/typegpu/src/core/slot/slot").slot; lazy: typeof import("node_modules/typegpu/src/core/slot/lazy").lazy; ... 10 more ...; '~unstable': typeof import("node_modules/typegpu/src/tgpuUnstable");}
tgpu.fn: <[d.Vec3f]>(argTypes: [d.Vec3f], returnType?: undefined) => TgpuFnShell<[d.Vec3f], d.Void> (+2 overloads)
fn([import d
d.const vec3f: d.Vec3fexport vec3f
Schema representing vec3f - a vector with 3 elements of type f32.
Also a constructor function for this vector value.
vec3f])((value: d.v3f
value) => { 'use gpu'; const seed: TgpuVar<"private", d.Vec2f>
seed.TgpuVar<"private", Vec2f>.$: d.v2f
$ = value: d.v3f
value.xy: d.v2f
xy + import d
d.function vec2f(xy: number): d.v2f (+3 overloads)export vec2f
Schema representing vec2f - a vector with 2 elements of type f32.
Also a constructor function for this vector value.
vec2f(value: d.v3f
value.v3f.z: number
z); }),
StatefulGenerator.seed4?: ((seed: d.v4f) => void) | undefined
seed4: const tgpu: { const: typeof import("node_modules/typegpu/src/core/constant/tgpuConstant").constant; fn: typeof import("node_modules/typegpu/src/core/function/tgpuFn").fn; comptime: typeof import("node_modules/typegpu/src/core/function/comptime").comptime; resolve: typeof import("node_modules/typegpu/src/core/resolve/tgpuResolve").resolve; resolveWithContext: typeof import("node_modules/typegpu/src/core/resolve/tgpuResolve").resolveWithContext; init: typeof import("node_modules/typegpu/src/core/root/init").init; initFromDevice: typeof import("node_modules/typegpu/src/core/root/init").initFromDevice; slot: typeof import("node_modules/typegpu/src/core/slot/slot").slot; lazy: typeof import("node_modules/typegpu/src/core/slot/lazy").lazy; ... 10 more ...; '~unstable': typeof import("node_modules/typegpu/src/tgpuUnstable");}
tgpu.fn: <[d.Vec4f]>(argTypes: [d.Vec4f], returnType?: undefined) => TgpuFnShell<[d.Vec4f], d.Void> (+2 overloads)
fn([import d
d.const vec4f: d.Vec4fexport vec4f
Schema representing vec4f - a vector with 4 elements of type f32.
Also a constructor function for this vector value.
vec4f])((value: d.v4f
value) => { 'use gpu'; const seed: TgpuVar<"private", d.Vec2f>
seed.TgpuVar<"private", Vec2f>.$: d.v2f
$ = value: d.v4f
value.xy: d.v2f
xy + value: d.v4f
value.zw: d.v2f
zw; }),
StatefulGenerator.sample: () => number
sample: randomGeneratorShell<() => number>(implementation: () => number): TgpuFn<() => d.F32> (+2 overloads)
randomGeneratorShell(() => { 'use gpu'; const const a: number
a = import std
std.dot<d.v2f>(lhs: d.v2f, rhs: d.v2f): numberexport dot
dot(const seed: TgpuVar<"private", d.Vec2f>
seed.TgpuVar<"private", Vec2f>.$: d.v2f
$, import d
d.function vec2f(x: number, y: number): d.v2f (+3 overloads)export vec2f
Schema representing vec2f - a vector with 2 elements of type f32.
Also a constructor function for this vector value.
vec2f(23.14077926, 232.61690225)); const const b: number
b = import std
std.dot<d.v2f>(lhs: d.v2f, rhs: d.v2f): numberexport dot
dot(const seed: TgpuVar<"private", d.Vec2f>
seed.TgpuVar<"private", Vec2f>.$: d.v2f
$, import d
d.function vec2f(x: number, y: number): d.v2f (+3 overloads)export vec2f
Schema representing vec2f - a vector with 2 elements of type f32.
Also a constructor function for this vector value.
vec2f(54.47856553, 345.84153136)); const seed: TgpuVar<"private", d.Vec2f>
seed.TgpuVar<"private", Vec2f>.$: d.v2f
$.v2f.x: number
x = import std
std.function fract(value: number): number (+1 overload)export fract
fract(import std
std.function cos(value: number): number (+1 overload)export cos
cos(const a: number
a) * 136.8168); const seed: TgpuVar<"private", d.Vec2f>
seed.TgpuVar<"private", Vec2f>.$: d.v2f
$.v2f.y: number
y = import std
std.function fract(value: number): number (+1 overload)export fract
fract(import std
std.function cos(value: number): number (+1 overload)export cos
cos(const b: number
b) * 534.7645); return const seed: TgpuVar<"private", d.Vec2f>
seed.TgpuVar<"private", Vec2f>.$: d.v2f
$.v2f.y: number
y; }).TgpuNamable.$name(label: string): TgpuFn<() => d.F32>
$name('sample'), };})();
const const root: TgpuRoot
root = await const tgpu: { const: typeof import("node_modules/typegpu/src/core/constant/tgpuConstant").constant; fn: typeof import("node_modules/typegpu/src/core/function/tgpuFn").fn; comptime: typeof import("node_modules/typegpu/src/core/function/comptime").comptime; resolve: typeof import("node_modules/typegpu/src/core/resolve/tgpuResolve").resolve; resolveWithContext: typeof import("node_modules/typegpu/src/core/resolve/tgpuResolve").resolveWithContext; init: typeof import("node_modules/typegpu/src/core/root/init").init; initFromDevice: typeof import("node_modules/typegpu/src/core/root/init").initFromDevice; slot: typeof import("node_modules/typegpu/src/core/slot/slot").slot; lazy: typeof import("node_modules/typegpu/src/core/slot/lazy").lazy; ... 10 more ...; '~unstable': typeof import("node_modules/typegpu/src/tgpuUnstable");}
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
init();const const pipeline: TgpuGuardedComputePipeline<[]>
pipeline = const root: TgpuRoot
root .Withable<WithBinding>.with<StatefulGenerator>(slot: TgpuSlot<StatefulGenerator>, value: Eventual<StatefulGenerator>): WithBinding (+2 overloads)
with(const randomGeneratorSlot: TgpuSlot<StatefulGenerator>
randomGeneratorSlot, const BPETER11: StatefulGenerator
Incorporated from https://www.cg.tuwien.ac.at/research/publications/2023/PETER-2023-PSW/PETER-2023-PSW-.pdf
"Particle System in WebGPU" by Benedikt Peter
BPETER11) .WithBinding.createGuardedComputePipeline<[]>(callback: () => void): TgpuGuardedComputePipeline<[]>
Creates a compute pipeline that executes the given callback in an exact number of threads.
This is different from createComputePipeline() in that it does a bounds check on the
thread id, where as regular pipelines do not and work in units of workgroups.
createGuardedComputePipeline(() => { 'use gpu'; const const value: number
value = const randf: { seed: typeof randSeed; seed2: typeof randSeed2; seed3: typeof randSeed3; seed4: typeof randSeed4; sample: typeof randFloat01; sampleExclusive: typeof randUniformExclusive; normal: typeof randNormal; exponential: typeof randExponential; cauchy: typeof randCauchy; bernoulli: typeof randBernoulli; ... 7 more ...; onUnitSphere: typeof randOnUnitSphere;}
randf.sample: () => number
Returns a random f32 value in [0, 1) range.
sample(); });