11---
22title : Slots
33description : Slots can be used to inject arbitrary values into shader code at a granular level.
4- draft : true
54---
5+ Slots are a powerful dependency injection mechanism in TypeGPU that allows you to write shader logic without having to tightly couple to specific resources.
6+ 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.
7+ The main differences between slots and bound resources include the following:
68
7- You can think of slots as holes in the shader that can be filled in from the outside.
9+ - Slots are filled in before the shader module compile time, instead of just before the shader execution starts.
10+ - Slots can contain not only buffers, but also basically anything that is allowed in TGSL, even TypeGPU functions.
811
9- ``` ts
10- const filterColorSlot = tgpu .slot <vec3f >(); // => TgpuSlot<vec3f>
12+ Main use cases for slots include:
13+
14+ - Generics -- instead of rewriting a function for each generic parameter, it suffices to fill a slot with a different value.
15+ - Passing callbacks -- high-level libraries based on TypeGPU can leave slots for user-defined functions to call.
16+ { /* - Conditional compilation -- an `if` statement with a boolean slot can be pruned at compile time if the slot is filled with `false`.*/ }
17+
18+ ## Basic usage
19+
20+ A slot is created using the ` tgpu.slot() ` function and can optionally take a default value:
21+
22+ ``` ts twoslash
23+ import tgpu from ' typegpu' ;
24+ import * as d from ' typegpu/data' ;
25+ // ---cut---
26+ const filterColorSlot = tgpu .slot <d .v3f >(); // Slot for a 3D vector.
27+ const mySlot = tgpu .slot <number >(42 ); // Slot with a default value.
28+
29+ interface Config {
30+ fogEnabled: boolean ;
31+ tint: d .v3f ;
32+ }
1133
12- const filter = tgpu .fn ([vec3f ])((color ) => {
13- const filterColor = filterColorSlot .$ ; // => vec3f
34+ const configSlot = tgpu .slot <Config >();
35+ ```
36+
37+ You can access a slot's value using either:
38+
39+ - ` .value ` property,
40+ - ` .$ ` shorthand property.
41+
42+ ``` typescript
43+ const value = mySlot .value ; // or mySlot.$
44+ ```
1445
46+ :::caution
47+ Slot values can only be accessed inside TypeGPU functions.
48+ Attempting to access them outside will throw an error.
49+ :::
50+
51+ Slots are resolved during the TypeGPU resolution phase.
52+ The slot object itself does not hold any value.
53+ Instead, you can bind it in one of two ways.
54+
55+ ### Binding after defining a function
56+
57+ The first way to bind a value to a slot is to call the ` with ` method on a ` TgpuFn ` instance:
58+
59+ ``` ts twoslash
60+ import tgpu from ' typegpu' ;
61+ import * as d from ' typegpu/data' ;
62+ import { mul } from ' typegpu/std' ;
63+ // ---cut---
64+ const filterColorSlot = tgpu .slot <d .v3f >();
65+
66+ const filter = tgpu .fn ([d .vec3f ], d .vec3f )((color ) => {
67+ const filterColor = filterColorSlot .$ ;
1568 return mul (color , filterColor );
1669});
1770
1871// Bind the filter function with a red color.
1972const filterWithRed = filter
20- .with (filterColorSlot , vec3f (1 , 0 , 0 ));
73+ .with (filterColorSlot , d . vec3f (1 , 0 , 0 ));
2174
2275// Bind the filter function with a green color.
2376const filterWithGreen = filter
24- .with (filterColorSlot , vec3f (0 , 1 , 0 ));
77+ .with (filterColorSlot , d . vec3f (0 , 1 , 0 ));
2578```
2679
27- ### Conditional compilation
80+ In the example above, after resolution we are left with two different WGSL functions:
2881
29- ``` ts
30- const safeLoopSlot = tgpu .slot (false );
82+ ``` wgsl
83+ fn filter_0(color: vec3f) -> vec3f{
84+ var filterColor = vec3f(1, 0, 0);
85+ return (color * filterColor);
86+ }
3187
32- const processObjects = tgpu .fn ([f32 ], f32 )(() => {
33- let len = objects .length ;
88+ fn filter_1(color: vec3f) -> vec3f{
89+ var filterColor = vec3f(0, 1, 0);
90+ return (color * filterColor);
91+ }
92+ ```
3493
35- if (safeLoopSlot .$ ) { // <- evaluated at compile time
36- // This will be included in the shader code only if
37- // `safeLoopSlot` is bound to true in the caller's scope.
38- len = max (len , 9999 );
39- }
94+ ### Binding during pipeline creation
4095
41- for (let i = 0 ; i < len ; ++ i ) {
42- // ...
43- }
44- });
96+ The other way to fill in a slot is to call the ` with ` method during the creation of a ` TgpuComputePipeline ` or ` TgpuRenderPipeline ` :
97+
98+ ``` ts twoslash
99+ import tgpu from ' typegpu' ;
100+ import * as d from ' typegpu/data' ;
101+
102+ const root = await tgpu .init ();
103+ // ---cut---
104+ const resultBuffer = root .createMutable (d .i32 , 0 );
105+ const multiplierSlot = tgpu .slot <number >();
106+
107+ const computeMultiply = tgpu [' ~unstable' ]
108+ .computeFn ({ workgroupSize: [1 ] })(() => {
109+ resultBuffer .$ = resultBuffer .$ * multiplierSlot .$ ;
110+ });
111+
112+ const pipeline = root [' ~unstable' ]
113+ .with (multiplierSlot , 3 )
114+ .withCompute (computeMultiply )
115+ .createPipeline ();
45116```
46117
47- ### Inversion of control
118+ The pipeline above resolves to the following WGSL:
48119
49- ``` ts
120+ ``` wgsl
121+ @group(0) @binding(0) var<storage, read_write> resultBuffer_1: i32;
122+
123+ @compute @workgroup_size(1) fn computeMultiply_0(){
124+ resultBuffer_1 = (resultBuffer_1 * 3);
125+ }
126+ ```
127+
128+ :::note
129+ Static code analysis does not verify whether all slots have been filled.
130+ If you forget to assign a value to a slot, a runtime error will be thrown instead.
131+ :::
132+
133+ ## Inversion of control
134+
135+ Slots allow libraries to expose customization points to their users.
136+ They enable internal behavior to be modified without sacrificing type safety or performance.
137+
138+ ``` ts twoslash
50139// physics.ts
140+ import tgpu from ' typegpu' ;
141+ import * as d from ' typegpu/data' ;
142+ import { add , mul } from ' typegpu/std' ;
51143
52- const defaultGravity = tgpu .fn ([ vec2f ], vec2f )(() => vec2f ( 0 , 9.8 ) );
144+ const root = await tgpu .init ( );
53145
54- export const getGravitySlot = tgpu .slot (defaultGravity );
55- // ^ TgpuSlot<TgpuFn<[Vec2f], Vec2f>>
146+ const Obj = d .struct ({
147+ position: d .vec2f ,
148+ velocity: d .vec2f ,
149+ });
150+
151+ const objects = root .createReadonly (d .arrayOf (Obj , 256 ));
152+ const deltaTime = root .createUniform (d .f32 );
153+
154+ // ---cut---
155+ const defaultGravity = tgpu .fn ([d .vec2f ], d .vec2f )((pos ) => {
156+ return d .vec2f (0 , - 9.8 );
157+ });
158+
159+ export const gravitySlot = tgpu .slot (defaultGravity );
160+ // ^?
56161
57162export const stepPhysics = tgpu .fn ([])(() => {
58163 for (const obj of objects .$ ) {
59164 // Calling whatever implementation was provided.
60- const gravity = getGravitySlot .$ (obj .position );
165+ const gravity = gravitySlot .$ (obj .position );
61166
62167 obj .velocity = add (obj .velocity , mul (gravity , deltaTime .$ ));
63168 }
@@ -67,16 +172,80 @@ export const stepPhysics = tgpu.fn([])(() => {
67172``` ts
68173// main.ts
69174
70- import { stepPhysics , getGravitySlot } from ' ./physics' ;
175+ import { stepPhysics , gravitySlot } from ' ./physics.ts' ;
176+
177+ const gravityTowardsCenter = tgpu .fn ([vec2f ], vec2f )((pos ) => {
178+ return mul (normalize (pos ), - 1 );
179+ });
71180
72- const getGravityTowardsCenter = tgpu
73- .fn ([ vec2f ], vec2f )(( position ) => mul ( normalize ( position ), - 1 ) );
181+ const stepPhysicsCustomized = stepPhysics
182+ .with ( gravitySlot , gravityTowardsCenter );
74183
75- const stepPhysicsAltered = stepPhysics
76- .with (getGravitySlot , getGravityTowardsCenter );
184+ const main = tgpu [' ~unstable' ].computeFn ()(() => {
185+ stepPhysicsCustomized (); // <- Will use altered gravity.
186+ });
187+ ```
77188
78- const main = tgpu
79- .computeFn ()(() => {
80- stepPhysicsAltered (); // <- Will use altered gravity.
81- });
82- ```
189+ { /*
190+ TODO: re-add and rewrite this section back when we actually prune branches.
191+
192+ ## Conditional compilation
193+
194+ Slots can be used for conditional compilation of segments of shaders.
195+ When the slot holding a boolean value is filled with `false`, all `if` statements depending on the value of that slot will be pruned by our tree-shaking mechanism.
196+
197+ ```ts twoslash
198+ import tgpu from 'typegpu';
199+ import * as d from 'typegpu/data';
200+ import { max } from 'typegpu/std';
201+
202+ const root = await tgpu.init();
203+
204+ const Obj = d.struct({
205+ pos: d.vec3f,
206+ });
207+
208+ const objects = root.createReadonly(d.arrayOf(Obj, 256));
209+
210+ // ---cut---
211+ const safeLoopSlot = tgpu.slot(false);
212+
213+ const processObjects = tgpu.fn([])(() => {
214+ let len = objects.$.length;
215+
216+ if (safeLoopSlot.$) { // <- evaluated at compile time
217+ // This will be included in the shader code only if
218+ // `safeLoopSlot` is bound to true in the caller's scope.
219+ len = max(len, 9999);
220+ }
221+
222+ for (let i = 0; i < len; ++i) {
223+ // ...
224+ }
225+ });
226+ ```
227+
228+ */ }
229+
230+ ## Slots in raw WGSL
231+
232+ It is possible to use slots even in TypeGPU functions that are implemented in WGSL:
233+
234+ ``` ts twoslash
235+ import tgpu from ' typegpu' ;
236+ import * as d from ' typegpu/data' ;
237+ // ---cut---
238+ const colorSlot = tgpu .slot (d .vec3f (1 , 0 , 0 ));
239+
240+ const getColor = tgpu .fn ([], d .vec3f )` () {
241+ return colorSlot;
242+ } ` .$uses ({ colorSlot });
243+ ```
244+
245+ The code above resolves to the following WGSL:
246+
247+ ``` wgsl
248+ fn getColor() -> vec3f {
249+ return vec3f(1, 0, 0);
250+ }
251+ ```
0 commit comments