Slots
Slots are a powerful dependency injection mechanism in TypeGPU that allows you to write shader logic without having to tightly couple to specific resources. Similarly to bind group layouts, you can think of slots as of typed “holes” in TypeGPU shaders, that can be filled in later on from the outside. The main differences between slots and bound resources include the following:
- Slots are filled in before the shader module compile time, instead of just before the shader execution starts.
- Slots can contain not only buffers, but also basically anything that is allowed in TGSL, even TypeGPU functions.
Main use cases for slots include:
- Generics — instead of rewriting a function for each generic parameter, it suffices to fill a slot with a different value.
- Passing callbacks — high-level libraries based on TypeGPU can leave slots for user-defined functions to call.
Basic usage
Section titled “Basic usage”A slot is created using the tgpu.slot()
function and can optionally take a default value:
const const filterColorSlot: TgpuSlot<d.v3f>
filterColorSlot = const tgpu: { fn: { <Args extends d.AnyData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>; <Args extends d.AnyData[] | [], Return extends d.AnyData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>; }; ... 7 more ...; '~unstable': { ...; };}
tgpu.slot: <d.v3f>(defaultValue?: d.v3f | undefined) => TgpuSlot<d.v3f>
slot<import d
d.export v3f
Interface representing its WGSL vector type counterpart: vec3f or vec3.
A vector with 3 elements of type f32
v3f>(); // Slot for a 3D vector.const const mySlot: TgpuSlot<number>
mySlot = const tgpu: { fn: { <Args extends d.AnyData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>; <Args extends d.AnyData[] | [], Return extends d.AnyData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>; }; ... 7 more ...; '~unstable': { ...; };}
tgpu.slot: <number>(defaultValue?: number | undefined) => TgpuSlot<number>
slot<number>(42); // Slot with a default value.
interface interface Config
Config { Config.fogEnabled: boolean
fogEnabled: boolean; Config.tint: d.v3f
tint: import d
d.export v3f
Interface representing its WGSL vector type counterpart: vec3f or vec3.
A vector with 3 elements of type f32
v3f;}
const const configSlot: TgpuSlot<Config>
configSlot = const tgpu: { fn: { <Args extends d.AnyData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>; <Args extends d.AnyData[] | [], Return extends d.AnyData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>; }; ... 7 more ...; '~unstable': { ...; };}
tgpu.slot: <Config>(defaultValue?: Config | undefined) => TgpuSlot<Config>
slot<interface Config
Config>();
You can access a slot’s value using either:
.value
property,.$
shorthand property.
const value = mySlot.value; // or mySlot.$
Slots are resolved during the TypeGPU resolution phase. The slot object itself does not hold any value. Instead, you can bind it in one of two ways.
Binding after defining a function
Section titled “Binding after defining a function”The first way to bind a value to a slot is to call the with
method on a TgpuFn
instance:
const const filterColorSlot: TgpuSlot<d.v3f>
filterColorSlot = const tgpu: { fn: { <Args extends d.AnyData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>; <Args extends d.AnyData[] | [], Return extends d.AnyData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>; }; ... 7 more ...; '~unstable': { ...; };}
tgpu.slot: <d.v3f>(defaultValue?: d.v3f | undefined) => TgpuSlot<d.v3f>
slot<import d
d.export v3f
Interface representing its WGSL vector type counterpart: vec3f or vec3.
A vector with 3 elements of type f32
v3f>();
const const filter: TgpuFn<(color: d.Vec3f) => d.Vec3f>
filter = const tgpu: { fn: { <Args extends d.AnyData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>; <Args extends d.AnyData[] | [], Return extends d.AnyData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>; }; ... 7 more ...; '~unstable': { ...; };}
tgpu.fn: <[d.Vec3f], d.Vec3f>(argTypes: [d.Vec3f], returnType: d.Vec3f) => TgpuFnShell<[d.Vec3f], d.Vec3f> (+1 overload)
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], 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)((color: d.v3f
color) => { const const filterColor: d.v3f
filterColor = const filterColorSlot: TgpuSlot<d.v3f>
filterColorSlot.TgpuSlot<v3f>.$: d.v3f
$; return mul<d.v3f>(lhs: d.v3f, rhs: d.v3f): d.v3f (+7 overloads)
mul(color: d.v3f
color, const filterColor: d.v3f
filterColor);});
// Bind the filter function with a red color.const const filterWithRed: TgpuFn<(color: d.Vec3f) => d.Vec3f>
filterWithRed = const filter: TgpuFn<(color: d.Vec3f) => d.Vec3f>
filter .TgpuFnBase<(color: Vec3f) => Vec3f>.with<d.v3f>(slot: TgpuSlot<d.v3f>, value: Eventual<d.v3f>): TgpuFn<(color: d.Vec3f) => d.Vec3f> (+1 overload)
with(const filterColorSlot: TgpuSlot<d.v3f>
filterColorSlot, import d
d.function vec3f(x: number, y: number, z: number): d.v3f (+5 overloads)export vec3f
Schema representing vec3f - a vector with 3 elements of type f32.
Also a constructor function for this vector value.
vec3f(1, 0, 0));
// Bind the filter function with a green color.const const filterWithGreen: TgpuFn<(color: d.Vec3f) => d.Vec3f>
filterWithGreen = const filter: TgpuFn<(color: d.Vec3f) => d.Vec3f>
filter .TgpuFnBase<(color: Vec3f) => Vec3f>.with<d.v3f>(slot: TgpuSlot<d.v3f>, value: Eventual<d.v3f>): TgpuFn<(color: d.Vec3f) => d.Vec3f> (+1 overload)
with(const filterColorSlot: TgpuSlot<d.v3f>
filterColorSlot, import d
d.function vec3f(x: number, y: number, z: number): d.v3f (+5 overloads)export vec3f
Schema representing vec3f - a vector with 3 elements of type f32.
Also a constructor function for this vector value.
vec3f(0, 1, 0));
In the example above, after resolution we are left with two different WGSL functions:
fn filter_0(color: vec3f) -> vec3f{ var filterColor = vec3f(1, 0, 0); return (color * filterColor);}
fn filter_1(color: vec3f) -> vec3f{ var filterColor = vec3f(0, 1, 0); return (color * filterColor);}
Binding during pipeline creation
Section titled “Binding during pipeline creation”The other way to fill in a slot is to call the with
method during the creation of a TgpuComputePipeline
or TgpuRenderPipeline
:
const const resultBuffer: TgpuMutable<d.I32>
resultBuffer = const root: TgpuRoot
root.TgpuRoot.createMutable<d.I32>(typeSchema: d.I32, initial?: number | undefined): TgpuMutable<d.I32> (+1 overload)
Allocates memory on the GPU, allows passing data between host and shader.
Can be mutated in-place on the GPU. For a general-purpose buffer,
use
TgpuRoot.createBuffer
.
createMutable(import d
d.const i32: d.I32export i32
A schema that represents a signed 32-bit integer value. (equivalent to i32
in WGSL)
Can also be called to cast a value to an i32 in accordance with WGSL casting rules.
i32, 0);const const multiplierSlot: TgpuSlot<number>
multiplierSlot = const tgpu: { fn: { <Args extends d.AnyData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>; <Args extends d.AnyData[] | [], Return extends d.AnyData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>; }; ... 7 more ...; '~unstable': { ...; };}
tgpu.slot: <number>(defaultValue?: number | undefined) => TgpuSlot<number>
slot<number>();
const const computeMultiply: TgpuComputeFn<{}>
computeMultiply = const tgpu: { fn: { <Args extends d.AnyData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>; <Args extends d.AnyData[] | [], Return extends d.AnyData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>; }; ... 7 more ...; '~unstable': { ...; };}
tgpu['~unstable'] .computeFn: (options: { workgroupSize: number[];}) => TgpuComputeFnShell<{}> (+1 overload)
computeFn({ workgroupSize: number[]
workgroupSize: [1] })(() => { const resultBuffer: TgpuMutable<d.I32>
resultBuffer.TgpuMutable<I32>.$: number
$ = const resultBuffer: TgpuMutable<d.I32>
resultBuffer.TgpuMutable<I32>.$: number
$ * const multiplierSlot: TgpuSlot<number>
multiplierSlot.TgpuSlot<number>.$: number
$; });
const const pipeline: TgpuComputePipeline
pipeline = const root: TgpuRoot
root['~unstable'] .with<number>(slot: TgpuSlot<number>, value: Eventual<number>): WithBinding (+1 overload)
with(const multiplierSlot: TgpuSlot<number>
multiplierSlot, 3) .WithBinding.withCompute<{}>(entryFn: TgpuComputeFn<{}>): WithCompute
withCompute(const computeMultiply: TgpuComputeFn<{}>
computeMultiply) .WithCompute.createPipeline(): TgpuComputePipeline
createPipeline();
The pipeline above resolves to the following WGSL:
@group(0) @binding(0) var<storage, read_write> resultBuffer_1: i32;
@compute @workgroup_size(1) fn computeMultiply_0(){ resultBuffer_1 = (resultBuffer_1 * 3);}
Inversion of control
Section titled “Inversion of control”Slots allow libraries to expose customization points to their users. They enable internal behavior to be modified without sacrificing type safety or performance.
const const defaultGravity: TgpuFn<(pos: d.Vec2f) => d.Vec2f>
defaultGravity = const tgpu: { fn: { <Args extends d.AnyData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>; <Args extends d.AnyData[] | [], Return extends d.AnyData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>; }; ... 7 more ...; '~unstable': { ...; };}
tgpu.fn: <[d.Vec2f], d.Vec2f>(argTypes: [d.Vec2f], returnType: d.Vec2f) => TgpuFnShell<[d.Vec2f], d.Vec2f> (+1 overload)
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], 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)((pos: d.v2f
pos) => { return 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(0, -9.8);});
export const gravitySlot = const tgpu: { fn: { <Args extends d.AnyData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>; <Args extends d.AnyData[] | [], Return extends d.AnyData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>; }; ... 7 more ...; '~unstable': { ...; };}
tgpu.slot: <TgpuFn<(pos: d.Vec2f) => d.Vec2f>>(defaultValue?: TgpuFn<(pos: d.Vec2f) => d.Vec2f> | undefined) => TgpuSlot<TgpuFn<(pos: d.Vec2f) => d.Vec2f>>
slot(const defaultGravity: TgpuFn<(pos: d.Vec2f) => d.Vec2f>
defaultGravity);const gravitySlot: TgpuSlot<TgpuFn<(pos: d.Vec2f) => d.Vec2f>>
export const const stepPhysics: TgpuFn<() => d.Void>
stepPhysics = const tgpu: { fn: { <Args extends d.AnyData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>; <Args extends d.AnyData[] | [], Return extends d.AnyData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>; }; ... 7 more ...; '~unstable': { ...; };}
tgpu.fn: <[]>(argTypes: [], returnType?: undefined) => TgpuFnShell<[], d.Void> (+1 overload)
fn([])(() => { for (const const obj: { position: d.v2f; velocity: d.v2f;}
obj of const objects: TgpuReadonly<d.WgslArray<d.WgslStruct<{ position: d.Vec2f; velocity: d.Vec2f;}>>>
objects.TgpuReadonly<WgslArray<WgslStruct<{ position: Vec2f; velocity: Vec2f; }>>>.$: { position: d.v2f; velocity: d.v2f;}[]
$) { // Calling whatever implementation was provided. const const gravity: d.v2f
gravity = const gravitySlot: TgpuSlot<TgpuFn<(pos: d.Vec2f) => d.Vec2f>>
gravitySlot.TgpuSlot<TgpuFn<(pos: Vec2f) => Vec2f>>.$: (pos: d.v2f) => d.v2f
$(const obj: { position: d.v2f; velocity: d.v2f;}
obj.position: d.v2f
position);
const obj: { position: d.v2f; velocity: d.v2f;}
obj.velocity: d.v2f
velocity = add<d.v2f>(lhs: d.v2f, rhs: d.v2f): d.v2f (+4 overloads)
add(const obj: { position: d.v2f; velocity: d.v2f;}
obj.velocity: d.v2f
velocity, mul<d.v2f>(lhs: d.v2f, rhs: number): d.v2f (+7 overloads)
mul(const gravity: d.v2f
gravity, const deltaTime: TgpuUniform<d.F32>
deltaTime.TgpuUniform<F32>.$: number
$)); }});
import { stepPhysics, gravitySlot } from './physics.ts';
const gravityTowardsCenter = tgpu.fn([vec2f], vec2f)((pos) => { return mul(normalize(pos), -1);});
const stepPhysicsCustomized = stepPhysics .with(gravitySlot, gravityTowardsCenter);
const main = tgpu['~unstable'].computeFn()(() => { stepPhysicsCustomized(); // <- Will use altered gravity.});
Slots in raw WGSL
Section titled “Slots in raw WGSL”It is possible to use slots even in TypeGPU functions that are implemented in WGSL:
const const colorSlot: TgpuSlot<d.v3f>
colorSlot = const tgpu: { fn: { <Args extends d.AnyData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>; <Args extends d.AnyData[] | [], Return extends d.AnyData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>; }; ... 7 more ...; '~unstable': { ...; };}
tgpu.slot: <d.v3f>(defaultValue?: d.v3f | undefined) => TgpuSlot<d.v3f>
slot(import d
d.function vec3f(x: number, y: number, z: number): d.v3f (+5 overloads)export vec3f
Schema representing vec3f - a vector with 3 elements of type f32.
Also a constructor function for this vector value.
vec3f(1, 0, 0));
const const getColor: TgpuFn<() => d.Vec3f>
getColor = const tgpu: { fn: { <Args extends d.AnyData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>; <Args extends d.AnyData[] | [], Return extends d.AnyData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>; }; ... 7 more ...; '~unstable': { ...; };}
tgpu.fn: <[], d.Vec3f>(argTypes: [], returnType: d.Vec3f) => TgpuFnShell<[], d.Vec3f> (+1 overload)
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)`() { return colorSlot;}`.TgpuFnBase<() => Vec3f>.$uses(dependencyMap: Record<string, unknown>): TgpuFn<() => d.Vec3f>
$uses({ colorSlot: TgpuSlot<d.v3f>
colorSlot });
The code above resolves to the following WGSL:
fn getColor() -> vec3f { return vec3f(1, 0, 0);}