Repository: software-mansion/TypeGPU
Pull request #1424 (2025-06-27 16:09:20)

feat: Stabilize slots

github-actions[bot]: **pkg.pr.new**

*packages*
```
pnpm i https://pkg.pr.new/software-mansion/TypeGPU/typegpu@1424
pnpm i https://pkg.pr.new/software-mansion/TypeGPU/typegpu@12b189fe3cfedc1bc80d5c47e6b65cf9e05caf5a
```

*benchmark*
[view benchmark](https://docs.swmansion.com/TypeGPU/benchmark?p=npm-latest_pr-1424)

*commit*
[view commit](https://github.com/software-mansion/TypeGPU/commit/undefined)

Patch:
diff --git a/apps/typegpu-docs/src/content/docs/reference/naming-convention.mdx b/apps/typegpu-docs/src/content/docs/reference/naming-convention.mdx
index 3cfbe26c..cd75e9b4 100644
--- a/apps/typegpu-docs/src/content/docs/reference/naming-convention.mdx
+++ b/apps/typegpu-docs/src/content/docs/reference/naming-convention.mdx
@@ -67,7 +67,7 @@ const cells3 = root.createReadonly(Cells);
 We give slots *camelCase* names with the `Slot` suffix.
 
 ```ts
-const colorSlot = tgpu['~unstable'].slot(vec4f(1, 0, 0, 1));
+const colorSlot = tgpu.slot(vec4f(1, 0, 0, 1));
 ```
 
 ## Accessors
diff --git a/apps/typegpu-docs/src/content/examples/rendering/perlin-noise/index.ts b/apps/typegpu-docs/src/content/examples/rendering/perlin-noise/index.ts
index c94bae84..590af93e 100644
--- a/apps/typegpu-docs/src/content/examples/rendering/perlin-noise/index.ts
+++ b/apps/typegpu-docs/src/content/examples/rendering/perlin-noise/index.ts
@@ -30,9 +30,7 @@ const tanhSharpen = tgpu.fn([d.f32, d.f32], d.f32)((n, sharpness) =>
   tanh(n * (1 + sharpness * 10))
 );
 
-const sharpenFnSlot = tgpu['~unstable'].slot<
-  TgpuFn<(n: d.F32, sharpness: d.F32) => d.F32>
->(
+const sharpenFnSlot = tgpu.slot<TgpuFn<(n: d.F32, sharpness: d.F32) => d.F32>>(
   exponentialSharpen,
 );
 
diff --git a/apps/typegpu-docs/src/content/examples/simple/oklab/index.ts b/apps/typegpu-docs/src/content/examples/simple/oklab/index.ts
index a34f7e88..50fbfded 100644
--- a/apps/typegpu-docs/src/content/examples/simple/oklab/index.ts
+++ b/apps/typegpu-docs/src/content/examples/simple/oklab/index.ts
@@ -87,7 +87,7 @@ const patternSolid = patternFn(() => {
   return 1;
 });
 
-const patternSlot = tgpu['~unstable'].slot(patternSolid);
+const patternSlot = tgpu.slot(patternSolid);
 
 // #endregion
 
diff --git a/apps/typegpu-docs/src/content/examples/simulation/fluid-double-buffering/index.ts b/apps/typegpu-docs/src/content/examples/simulation/fluid-double-buffering/index.ts
index 77667e6d..0d48d2b4 100644
--- a/apps/typegpu-docs/src/content/examples/simulation/fluid-double-buffering/index.ts
+++ b/apps/typegpu-docs/src/content/examples/simulation/fluid-double-buffering/index.ts
@@ -23,10 +23,10 @@ const BoxObstacle = d.struct({
 
 const gridSize = 256;
 
-const inputGridSlot = tgpu['~unstable'].slot<
+const inputGridSlot = tgpu.slot<
   TgpuBufferReadonly<GridData> | TgpuBufferMutable<GridData>
 >();
-const outputGridSlot = tgpu['~unstable'].slot<TgpuBufferMutable<GridData>>();
+const outputGridSlot = tgpu.slot<TgpuBufferMutable<GridData>>();
 
 const MAX_OBSTACLES = 4;
 const BoxObstacleArray = d.arrayOf(BoxObstacle, MAX_OBSTACLES);
diff --git a/apps/typegpu-docs/src/content/examples/simulation/gravity/schemas.ts b/apps/typegpu-docs/src/content/examples/simulation/gravity/schemas.ts
index a836b64f..330e3d52 100644
--- a/apps/typegpu-docs/src/content/examples/simulation/gravity/schemas.ts
+++ b/apps/typegpu-docs/src/content/examples/simulation/gravity/schemas.ts
@@ -62,10 +62,8 @@ export const renderSkyBoxVertexLayout = tgpu.vertexLayout((n) =>
 );
 
 export const cameraAccess = tgpu['~unstable'].accessor(Camera);
-export const filteringSamplerSlot = tgpu['~unstable'].slot<TgpuSampler>();
-export const skyBoxSlot = tgpu['~unstable'].slot<
-  TgpuSampledTexture<'cube', d.F32>
->();
+export const filteringSamplerSlot = tgpu.slot<TgpuSampler>();
+export const skyBoxSlot = tgpu.slot<TgpuSampledTexture<'cube', d.F32>>();
 export const lightSourceAccess = tgpu['~unstable'].accessor(d.vec3f);
 export const timeAccess = tgpu['~unstable'].accessor(Time);
 
diff --git a/packages/typegpu-color/src/oklab.ts b/packages/typegpu-color/src/oklab.ts
index 22ff7789..99866bc1 100644
--- a/packages/typegpu-color/src/oklab.ts
+++ b/packages/typegpu-color/src/oklab.ts
@@ -324,7 +324,7 @@ const gamutClipAdaptiveL0cusp = tgpu.fn([vec3f], vec3f)((lab) => {
   return vec3f(L_clipped, C_clipped * a_, C_clipped * b_);
 });
 
-export const oklabGamutClipSlot = tgpu['~unstable'].slot(gamutClipAdaptiveL05);
+export const oklabGamutClipSlot = tgpu.slot(gamutClipAdaptiveL05);
 export const oklabGamutClip = {
   preserveChroma: gamutClipPreserveChroma,
   adaptiveL05: gamutClipAdaptiveL05,
diff --git a/packages/typegpu-noise/src/generator.ts b/packages/typegpu-noise/src/generator.ts
index 05ed140c..3fd3141a 100644
--- a/packages/typegpu-noise/src/generator.ts
+++ b/packages/typegpu-noise/src/generator.ts
@@ -50,5 +50,6 @@ export const BPETER: StatefulGenerator = (() => {
 // The default (Can change between releases to improve uniformity).
 export const DefaultGenerator: StatefulGenerator = BPETER;
 
-export const randomGeneratorSlot: TgpuSlot<StatefulGenerator> =
-  tgpu['~unstable'].slot(DefaultGenerator);
+export const randomGeneratorSlot: TgpuSlot<StatefulGenerator> = tgpu.slot(
+  DefaultGenerator,
+);
diff --git a/packages/typegpu-noise/src/perlin-2d.ts b/packages/typegpu-noise/src/perlin-2d.ts
index 77cd3858..40f6cc1d 100644
--- a/packages/typegpu-noise/src/perlin-2d.ts
+++ b/packages/typegpu-noise/src/perlin-2d.ts
@@ -9,7 +9,7 @@ export const computeJunctionGradient = tgpu.fn([d.vec2i], d.vec2f)((pos) => {
   return randOnUnitCircle();
 });
 
-export const getJunctionGradientSlot = tgpu['~unstable'].slot(
+export const getJunctionGradientSlot = tgpu.slot(
   computeJunctionGradient,
 );
 
diff --git a/packages/typegpu-noise/src/perlin-3d/algorithm.ts b/packages/typegpu-noise/src/perlin-3d/algorithm.ts
index f4766a07..78a30d9b 100644
--- a/packages/typegpu-noise/src/perlin-3d/algorithm.ts
+++ b/packages/typegpu-noise/src/perlin-3d/algorithm.ts
@@ -9,9 +9,7 @@ export const computeJunctionGradient = tgpu.fn([d.vec3i], d.vec3f)((pos) => {
   return randOnUnitSphere();
 });
 
-export const getJunctionGradientSlot = tgpu['~unstable'].slot(
-  computeJunctionGradient,
-);
+export const getJunctionGradientSlot = tgpu.slot(computeJunctionGradient);
 
 const dotProdGrid = tgpu.fn([d.vec3f, d.vec3f], d.f32)((pos, junction) => {
   const relative = sub(pos, junction);
diff --git a/packages/typegpu-noise/src/perlin-3d/dynamic-cache.ts b/packages/typegpu-noise/src/perlin-3d/dynamic-cache.ts
index b1b016a9..ad31221e 100644
--- a/packages/typegpu-noise/src/perlin-3d/dynamic-cache.ts
+++ b/packages/typegpu-noise/src/perlin-3d/dynamic-cache.ts
@@ -131,7 +131,7 @@ export function dynamicCacheConfig<Prefix extends string>(
 ): DynamicPerlin3DCacheConfig<Prefix> {
   const { prefix = DefaultPerlin3DLayoutPrefix as Prefix } = options ?? {};
 
-  const valuesSlot = tgpu['~unstable'].slot<LayoutValue<Prefix>>();
+  const valuesSlot = tgpu.slot<LayoutValue<Prefix>>();
 
   const cleanValuesSlot = tgpu['~unstable'].derived(() => {
     return {
diff --git a/packages/typegpu/src/index.ts b/packages/typegpu/src/index.ts
index fd8e4b5d..c9bc0f68 100644
--- a/packages/typegpu/src/index.ts
+++ b/packages/typegpu/src/index.ts
@@ -23,6 +23,7 @@ export const tgpu = {
   fn,
   bindGroupLayout,
   vertexLayout,
+  slot,
 
   init,
   initFromDevice,
@@ -43,6 +44,9 @@ export const tgpu = {
      */
     vertexLayout,
     derived,
+    /**
+     * @deprecated This feature is now stable, use tgpu.slot.
+     */
     slot,
     accessor,
     privateVar,


Test patch:
diff --git a/packages/typegpu/tests/derived.test.ts b/packages/typegpu/tests/derived.test.ts
index d710fda8..09d0cf16 100644
--- a/packages/typegpu/tests/derived.test.ts
+++ b/packages/typegpu/tests/derived.test.ts
@@ -7,7 +7,7 @@ import { parse, parseResolved } from './utils/parseResolved.ts';
 
 describe('TgpuDerived', () => {
   it('memoizes results of transitive "derived"', () => {
-    const foo = tgpu['~unstable'].slot<number>(1);
+    const foo = tgpu.slot<number>(1);
     const computeDouble = vi.fn(() => {
       return foo.value * 2;
     });
@@ -31,7 +31,7 @@ describe('TgpuDerived', () => {
   });
 
   it('memoizes functions using derived values', () => {
-    const foo = tgpu['~unstable'].slot<number>();
+    const foo = tgpu.slot<number>();
     const double = tgpu['~unstable'].derived(() => foo.value * 2);
 
     const getDouble = tgpu.fn([], d.f32)(() => {
@@ -68,7 +68,7 @@ describe('TgpuDerived', () => {
   });
 
   it('can use slot values from its surrounding context', () => {
-    const gridSizeSlot = tgpu['~unstable'].slot<number>();
+    const gridSizeSlot = tgpu.slot<number>();
 
     const fill = tgpu['~unstable'].derived(() => {
       const gridSize = gridSizeSlot.value;
@@ -106,7 +106,7 @@ describe('TgpuDerived', () => {
   });
 
   it('allows access to value in tgsl functions through the .value property ', ({ root }) => {
-    const vectorSlot = tgpu['~unstable'].slot(d.vec3f(1, 2, 3));
+    const vectorSlot = tgpu.slot(d.vec3f(1, 2, 3));
     const doubledVectorSlot = tgpu['~unstable'].derived(() => {
       const vec = vectorSlot.value;
 
@@ -200,13 +200,13 @@ describe('TgpuDerived', () => {
   });
 
   it('does not allow defining derived values at resolution', () => {
-    const gridSizeSlot = tgpu['~unstable'].slot<number>(2);
-    const derived = tgpu['~unstable'].derived(() =>
+    const gridSizeSlot = tgpu.slot<number>(2);
+    const absGridSize = tgpu['~unstable'].derived(() =>
       gridSizeSlot.$ > 0
         ? tgpu['~unstable'].derived(() => gridSizeSlot.$).$
         : tgpu['~unstable'].derived(() => -gridSizeSlot.$).$
     );
-    const fn = tgpu.fn([], d.u32)(() => derived.$);
+    const fn = tgpu.fn([], d.u32)(() => absGridSize.$);
 
     expect(() => parseResolved({ fn })).toThrow(
       'Cannot create tgpu.derived objects at the resolution stage.',
diff --git a/packages/typegpu/tests/slot.test.ts b/packages/typegpu/tests/slot.test.ts
index ccb2ed9c..a91f4a42 100644
--- a/packages/typegpu/tests/slot.test.ts
+++ b/packages/typegpu/tests/slot.test.ts
@@ -9,7 +9,7 @@ const GREEN = 'vec3f(0., 1., 0.)';
 
 describe('tgpu.slot', () => {
   it('resolves to default value if no value provided', () => {
-    const colorSlot = tgpu['~unstable'].slot(RED); // red by default
+    const colorSlot = tgpu.slot(RED); // red by default
 
     const getColor = tgpu.fn([], d.vec3f)`() {
         return colorSlot;
@@ -28,7 +28,7 @@ describe('tgpu.slot', () => {
   });
 
   it('resolves to provided value rather than default value', () => {
-    const colorSlot = tgpu['~unstable'].slot(RED); // red by default
+    const colorSlot = tgpu.slot(RED); // red by default
 
     const getColor = tgpu.fn([], d.vec3f)`() {
         return colorSlot;
@@ -59,7 +59,7 @@ describe('tgpu.slot', () => {
   });
 
   it('resolves to provided value', () => {
-    const colorSlot = tgpu['~unstable'].slot<string>(); // no default
+    const colorSlot = tgpu.slot<string>(); // no default
 
     const getColor = tgpu.fn([], d.vec3f)`() {
         return colorSlot;
@@ -91,7 +91,7 @@ describe('tgpu.slot', () => {
   });
 
   it('throws error when no default nor value provided', () => {
-    const colorSlot = tgpu['~unstable'].slot<string>().$name('color');
+    const colorSlot = tgpu.slot<string>().$name('color');
 
     const getColor = tgpu.fn([], d.vec3f)`() {
         return colorSlot;
@@ -108,7 +108,7 @@ describe('tgpu.slot', () => {
   });
 
   it('prefers closer scope', () => {
-    const colorSlot = tgpu['~unstable'].slot<string>(); // no default
+    const colorSlot = tgpu.slot<string>(); // no default
 
     const getColor = tgpu.fn([], d.vec3f)`() -> vec3f {
         return colorSlot;
@@ -155,8 +155,8 @@ describe('tgpu.slot', () => {
   });
 
   it('reuses common nested functions', () => {
-    const sizeSlot = tgpu['~unstable'].slot<1 | 100>();
-    const colorSlot = tgpu['~unstable'].slot<typeof RED | typeof GREEN>();
+    const sizeSlot = tgpu.slot<1 | 100>();
+    const colorSlot = tgpu.slot<typeof RED | typeof GREEN>();
 
     const getSize = tgpu.fn([], d.f32)`() { return sizeSlot; }`
       .$uses({ sizeSlot });
@@ -270,10 +270,10 @@ describe('tgpu.slot', () => {
   });
 
   it('unwraps layers of slots', () => {
-    const slotA = tgpu['~unstable'].slot(1);
-    const slotB = tgpu['~unstable'].slot(2);
-    const slotC = tgpu['~unstable'].slot(3);
-    const slotD = tgpu['~unstable'].slot(4);
+    const slotA = tgpu.slot(1);
+    const slotB = tgpu.slot(2);
+    const slotC = tgpu.slot(3);
+    const slotD = tgpu.slot(4);
 
     const fn1 = tgpu.fn([])`() { let value = slotA; }`
       .$uses({ slotA });
@@ -302,7 +302,7 @@ describe('tgpu.slot', () => {
   });
 
   it('allows access to value in tgsl functions through the .value property ', ({ root }) => {
-    const vectorSlot = tgpu['~unstable'].slot(d.vec3f(1, 2, 3));
+    const vectorSlot = tgpu.slot(d.vec3f(1, 2, 3));
     const Boid = d
       .struct({
         pos: d.vec3f,
@@ -312,12 +312,12 @@ describe('tgpu.slot', () => {
 
     const buffer = root.createBuffer(Boid).$usage('uniform').$name('boid');
     const uniform = buffer.as('uniform');
-    const uniformSlot = tgpu['~unstable'].slot(uniform);
-    const uniformSlotSlot = tgpu['~unstable'].slot(uniformSlot);
+    const uniformSlot = tgpu.slot(uniform);
+    const uniformSlotSlot = tgpu.slot(uniformSlot);
 
     const getColor = tgpu.fn([], d.vec3f)(() => d.vec3f(1, 2, 3));
     const colorAccess = tgpu['~unstable'].accessor(d.vec3f, getColor);
-    const colorAccessSlot = tgpu['~unstable'].slot(colorAccess);
+    const colorAccessSlot = tgpu.slot(colorAccess);
 
     const func = tgpu.fn([])(() => {
       const pos = vectorSlot.value;
diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts
index cb5dcb09..351fe7d8 100644
--- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts
+++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts
@@ -19,7 +19,7 @@ const { NodeTypeCatalog: NODE } = tinyest;
 
 const transpiler = new JitTranspiler();
 
-const numberSlot = tgpu['~unstable'].slot(44);
+const numberSlot = tgpu.slot(44);
 const derivedV4u = tgpu['~unstable'].derived(() =>
   std.mul(d.u32(numberSlot.value), d.vec4u(1, 2, 3, 4))
 );
@@ -711,7 +711,7 @@ describe('wgslGenerator', () => {
     const testBuffer = root.createBuffer(UnfortunateStruct).$usage('storage');
 
     const testUsage = testBuffer.as('mutable');
-    const testSlot = tgpu['~unstable'].slot(testUsage);
+    const testSlot = tgpu.slot(testUsage);
     const testFn = tgpu.fn([], d.f32)(() => {
       const value = testSlot.value.value;
       return value.x + value.y + value.z;
diff --git a/packages/typegpu/tests/unplugin/autoname.test.ts b/packages/typegpu/tests/unplugin/autoname.test.ts
index a57d7c76..1e90a065 100644
--- a/packages/typegpu/tests/unplugin/autoname.test.ts
+++ b/packages/typegpu/tests/unplugin/autoname.test.ts
@@ -7,23 +7,23 @@ import { it } from '../utils/extendedIt.ts';
 
 describe('autonaming', () => {
   it('autonames resources created using tgpu', () => {
+    const mySlot = tgpu.slot<number>();
     const myLayout = tgpu.bindGroupLayout({ foo: { uniform: d.vec3f } });
     const myVertexLayout = tgpu.vertexLayout((n: number) =>
       d.arrayOf(d.i32, n)
     );
 
+    expect(getName(mySlot)).toBe('mySlot');
     expect(getName(myLayout)).toBe('myLayout');
     expect(getName(myVertexLayout)).toBe('myVertexLayout');
   });
 
   it("autonames resources created using tgpu['~unstable']", () => {
-    const mySlot = tgpu['~unstable'].slot<number>();
     const myAccessor = tgpu['~unstable'].accessor(d.f32);
     const myPrivateVar = tgpu['~unstable'].privateVar(d.vec2f);
     const myWorkgroupVar = tgpu['~unstable'].workgroupVar(d.f32);
     const myConst = tgpu['~unstable'].const(d.f32, 1);
 
-    expect(getName(mySlot)).toBe('mySlot');
     expect(getName(myAccessor)).toBe('myAccessor');
     expect(getName(myPrivateVar)).toBe('myPrivateVar');
     expect(getName(myWorkgroupVar)).toBe('myWorkgroupVar');