Skip to content

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.

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.

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.Vec3f
export vec3f

Schema representing vec3f - a vector with 3 elements of type f32. Also a constructor function for this vector value.

@example const vector = d.vec3f(); // (0.0, 0.0, 0.0) const vector = d.vec3f(1); // (1.0, 1.0, 1.0) const vector = d.vec3f(1, 2, 3.5); // (1.0, 2.0, 3.5)

@example const buffer = root.createBuffer(d.vec3f, d.vec3f(0, 1, 2)); // buffer holding a d.vec3f value, with an initial value of vec3f(0, 1, 2);

vec3f
],
import d
d
.
const vec3f: d.Vec3f
export vec3f

Schema representing vec3f - a vector with 3 elements of type f32. Also a constructor function for this vector value.

@example const vector = d.vec3f(); // (0.0, 0.0, 0.0) const vector = d.vec3f(1); // (1.0, 1.0, 1.0) const vector = d.vec3f(1, 2, 3.5); // (1.0, 2.0, 3.5)

@example const buffer = root.createBuffer(d.vec3f, d.vec3f(0, 1, 2)); // buffer holding a d.vec3f value, with an initial value of vec3f(0, 1, 2);

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.

@example const vector = d.vec3f(); // (0.0, 0.0, 0.0) const vector = d.vec3f(1); // (1.0, 1.0, 1.0) const vector = d.vec3f(1, 2, 3.5); // (1.0, 2.0, 3.5)

@example const buffer = root.createBuffer(d.vec3f, d.vec3f(0, 1, 2)); // buffer holding a d.vec3f value, with an initial value of vec3f(0, 1, 2);

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.

@example const vector = d.vec3f(); // (0.0, 0.0, 0.0) const vector = d.vec3f(1); // (1.0, 1.0, 1.0) const vector = d.vec3f(1, 2, 3.5); // (1.0, 2.0, 3.5)

@example const buffer = root.createBuffer(d.vec3f, d.vec3f(0, 1, 2)); // buffer holding a d.vec3f value, with an initial value of vec3f(0, 1, 2);

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);
}

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

.

@paramtypeSchema The type of data that this buffer will hold.

@paraminitial The initial value of the buffer. (optional)

createMutable
(
import d
d
.
const i32: d.I32
export 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.

@example const value = i32(); // 0

@example const value = i32(3.14); // 3

@example const value = i32(-3.9); // -3

@example const value = i32(10000000000) // 1410065408

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);
}

Slots allow libraries to expose customization points to their users. They enable internal behavior to be modified without sacrificing type safety or performance.

physics.ts
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.Vec2f
export vec2f

Schema representing vec2f - a vector with 2 elements of type f32. Also a constructor function for this vector value.

@example const vector = d.vec2f(); // (0.0, 0.0) const vector = d.vec2f(1); // (1.0, 1.0) const vector = d.vec2f(0.5, 0.1); // (0.5, 0.1)

@example const buffer = root.createBuffer(d.vec2f, d.vec2f(0, 1)); // buffer holding a d.vec2f value, with an initial value of vec2f(0, 1);

vec2f
],
import d
d
.
const vec2f: d.Vec2f
export vec2f

Schema representing vec2f - a vector with 2 elements of type f32. Also a constructor function for this vector value.

@example const vector = d.vec2f(); // (0.0, 0.0) const vector = d.vec2f(1); // (1.0, 1.0) const vector = d.vec2f(0.5, 0.1); // (0.5, 0.1)

@example const buffer = root.createBuffer(d.vec2f, d.vec2f(0, 1)); // buffer holding a d.vec2f value, with an initial value of vec2f(0, 1);

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.

@example const vector = d.vec2f(); // (0.0, 0.0) const vector = d.vec2f(1); // (1.0, 1.0) const vector = d.vec2f(0.5, 0.1); // (0.5, 0.1)

@example const buffer = root.createBuffer(d.vec2f, d.vec2f(0, 1)); // buffer holding a d.vec2f value, with an initial value of vec2f(0, 1);

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
$
));
}
});
main.ts
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.
});

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.

@example const vector = d.vec3f(); // (0.0, 0.0, 0.0) const vector = d.vec3f(1); // (1.0, 1.0, 1.0) const vector = d.vec3f(1, 2, 3.5); // (1.0, 2.0, 3.5)

@example const buffer = root.createBuffer(d.vec3f, d.vec3f(0, 1, 2)); // buffer holding a d.vec3f value, with an initial value of vec3f(0, 1, 2);

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.Vec3f
export vec3f

Schema representing vec3f - a vector with 3 elements of type f32. Also a constructor function for this vector value.

@example const vector = d.vec3f(); // (0.0, 0.0, 0.0) const vector = d.vec3f(1); // (1.0, 1.0, 1.0) const vector = d.vec3f(1, 2, 3.5); // (1.0, 2.0, 3.5)

@example const buffer = root.createBuffer(d.vec3f, d.vec3f(0, 1, 2)); // buffer holding a d.vec3f value, with an initial value of vec3f(0, 1, 2);

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);
}