Skip to content

feat: Stabilize tgpu.fn & remove deprecated .does API #1411

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jun 30, 2025
Merged
31 changes: 14 additions & 17 deletions apps/typegpu-docs/src/content/docs/fundamentals/functions/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,17 @@ Then the actual WGSL implementation is passed in as an argument to a shell invoc
The following code defines a function that accepts one argument and returns one value.

```ts
const getGradientColor = tgpu['~unstable']
.fn([d.f32], d.vec4f)(/* wgsl */ `(ratio: f32) -> vec4f {
let color = mix(vec4f(0.769, 0.392, 1.0, 1), vec4f(0.114, 0.447, 0.941, 1), ratio);
return color;
}`);
const getGradientColor = tgpu.fn([d.f32], d.vec4f)(/* wgsl */ `(ratio: f32) -> vec4f {
let color = mix(vec4f(0.769, 0.392, 1.0, 1), vec4f(0.114, 0.447, 0.941, 1), ratio);
return color;
}`);

// or

const getGradientColor = tgpu['~unstable']
.fn([d.f32], d.vec4f) /* wgsl */`(ratio: f32) -> vec4f {
let color = mix(vec4f(0.769, 0.392, 1.0, 1), vec4f(0.114, 0.447, 0.941, 1), ratio);
return color;
};
const getGradientColor = tgpu.fn([d.f32], d.vec4f) /* wgsl */`(ratio: f32) -> vec4f {
let color = mix(vec4f(0.769, 0.392, 1.0, 1), vec4f(0.114, 0.447, 0.941, 1), ratio);
return color;
};
```

If you're using Visual Studio Code, you can use an [extension](https://marketplace.visualstudio.com/items?itemName=ggsimm.wgsl-literal) that brings syntax highlighting to the code fragments marked with `/* wgsl */` comments.
Expand All @@ -47,18 +45,17 @@ Functions can use external resources passed inside a record via the `$uses` meth
Externals can be any value or TypeGPU resource that can be resolved to WGSL (functions, buffer usages, slots, accessors, constants, variables, declarations, vectors, matrices, textures, samplers etc.).

```ts
const getBlue = tgpu['~unstable'].fn([], d.vec4f)`() -> vec4f {
const getBlue = tgpu.fn([], d.vec4f)`() -> vec4f {
return vec4f(0.114, 0.447, 0.941, 1);
}`;

const purple = d.vec4f(0.769, 0.392, 1.0, 1);

const getGradientColor = tgpu['~unstable']
.fn([d.f32], d.vec4f)`(ratio: f32) -> vec4f {
let color = mix(purple, getBlue(), ratio);
return color;
}
`.$uses({ purple, getBlue });
const getGradientColor = tgpu.fn([d.f32], d.vec4f)`(ratio: f32) -> vec4f {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should mention in the guide that you can skip the types in the function header, if we don't already

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That gonna be another PR 🫡

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let color = mix(purple, getBlue(), ratio);
return color;
}
`.$uses({ purple, getBlue });
```

The `getGradientColor` function, when resolved to WGSL, includes the definitions of all used external resources:
Expand Down
32 changes: 15 additions & 17 deletions apps/typegpu-docs/src/content/docs/fundamentals/tgsl.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,24 +35,22 @@ const obstacles = root
enabled: d.u32,
}), MAX_OBSTACLES));

const isInsideObstacle = tgpu['~unstable'].fn([d.i32, d.i32], d.bool)(
(x, y) => {
for (let obsIdx = 0; obsIdx < MAX_OBSTACLES; obsIdx++) {
const obs = obstacles.$[obsIdx];
if (obs.enabled === 0) {
continue;
}
const minX = std.max(0, obs.center.x - d.i32(obs.size.x / 2));
const maxX = std.min(gridSize, obs.center.x + d.i32(obs.size.x / 2));
const minY = std.max(0, obs.center.y - d.i32(obs.size.y / 2));
const maxY = std.min(gridSize, obs.center.y + d.i32(obs.size.y / 2));
if (x >= minX && x <= maxX && y >= minY && y <= maxY) {
return true;
}
const isInsideObstacle = tgpu.fn([d.i32, d.i32], d.bool)((x, y) => {
for (let obsIdx = 0; obsIdx < MAX_OBSTACLES; obsIdx++) {
const obs = obstacles.$[obsIdx];
if (obs.enabled === 0) {
continue;
}
return false;
},
);
const minX = std.max(0, obs.center.x - d.i32(obs.size.x / 2));
const maxX = std.min(gridSize, obs.center.x + d.i32(obs.size.x / 2));
const minY = std.max(0, obs.center.y - d.i32(obs.size.y / 2));
const maxY = std.min(gridSize, obs.center.y + d.i32(obs.size.y / 2));
if (x >= minX && x <= maxX && y >= minY && y <= maxY) {
return true;
}
}
return false;
});
```

```ts
Expand Down
10 changes: 4 additions & 6 deletions apps/typegpu-docs/src/content/docs/tooling/unplugin-typegpu.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,14 @@ The package includes the following functionalities:
import tgpu from 'typegpu';
import * as d from 'typegpu/data';

const add = tgpu['~unstable'].fn([d.u32, d.u32], d.u32)(
(a, b) => a + b,
);
const add = tgpu.fn([d.u32, d.u32], d.u32)((a, b) => a + b);
```

However, if the implementation function, or the shell, is referenced via a variable, the plugin will not recognize it as TGSL,
thus to make it work, the function needs to be marked with a `"kernel"` directive.

```ts
const addFn = tgpu['~unstable'].fn([d.u32, d.u32], d.u32);
const addFn = tgpu.fn([d.u32, d.u32], d.u32);

const add = addFn((a, b) => {
'kernel';
Expand All @@ -46,14 +44,14 @@ The package includes the following functionalities:
return a + b;
};

const add = tgpu['~unstable'].fn([d.u32, d.u32], d.u32)(addImpl);
const add = tgpu.fn([d.u32, d.u32], d.u32)(addImpl);
```

After transpiling the function, the JS implementation is removed from the bundle in order to save space.
To be able to invoke the function both on GPU and CPU, it needs to be marked with `"kernel & js"` directive;

```ts
const add = tgpu['~unstable'].fn([d.u32, d.u32], d.u32)((a, b) => {
const add = tgpu.fn([d.u32, d.u32], d.u32)((a, b) => {
'kernel & js';
return a + b;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import { computeLayout } from './types.ts';
const tileA = tgpu['~unstable'].workgroupVar(d.arrayOf(d.i32, TILE_SIZE ** 2));
const tileB = tgpu['~unstable'].workgroupVar(d.arrayOf(d.i32, TILE_SIZE ** 2));

export const getIndex = tgpu['~unstable'].fn([d.u32, d.u32, d.u32], d.u32)(
export const getIndex = tgpu.fn([d.u32, d.u32, d.u32], d.u32)(
(row, col, columns) => {
return col + row * columns;
},
);

const getTileIndex = tgpu['~unstable'].fn([d.u32, d.u32], d.u32)((row, col) => {
const getTileIndex = tgpu.fn([d.u32, d.u32], d.u32)((row, col) => {
return col + row * TILE_SIZE;
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const displayModes = {
* Adapted from the original Shadertoy implementation by movAX13h:
* https://www.shadertoy.com/view/lssGDj
*/
const characterFn = tgpu['~unstable'].fn([d.u32, d.vec2f], d.f32)((n, p) => {
const characterFn = tgpu.fn([d.u32, d.vec2f], d.f32)((n, p) => {
// Transform texture coordinates to character bitmap coordinates (5x5 grid)
const pos = std.floor(std.add(std.mul(p, d.vec2f(-4, 4)), 2.5));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,7 @@ export const vertexShader = tgpu['~unstable'].vertexFn({
};
});

const sampleTexture = tgpu['~unstable'].fn(
[d.vec2f],
d.vec4f,
) /* wgsl */`(uv: vec2f) -> vec4f {
const sampleTexture = tgpu.fn([d.vec2f], d.vec4f)`(uv) {
return textureSample(layout.$.modelTexture, layout.$.sampler, uv);
}
`.$uses({ layout });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,52 +3,51 @@ import * as d from 'typegpu/data';
import * as std from 'typegpu/std';
import { Line3 } from './schemas.ts';

export const projectPointOnLine = tgpu['~unstable'].fn(
[d.vec3f, Line3],
d.vec3f,
)((point, line) => {
const pointVector = std.sub(point, line.origin);
const projection = std.dot(pointVector, line.dir);
const closestPoint = std.add(
line.origin,
std.mul(projection, line.dir),
);
return closestPoint;
});
export const projectPointOnLine = tgpu.fn([d.vec3f, Line3], d.vec3f)(
(point, line) => {
const pointVector = std.sub(point, line.origin);
const projection = std.dot(pointVector, line.dir);
const closestPoint = std.add(
line.origin,
std.mul(projection, line.dir),
);
return closestPoint;
},
);

export const PosAndNormal = d.struct({
position: d.vec3f,
normal: d.vec3f,
});

export const applySinWave = tgpu['~unstable'].fn(
[d.u32, PosAndNormal, d.f32],
PosAndNormal,
)((index, vertex, time) => {
const a = -60.1;
const b = 0.8;
const c = 6.1;
// z += sin(index + (time / a + x) / b) / c
export const applySinWave = tgpu.fn([d.u32, PosAndNormal, d.f32], PosAndNormal)(
(index, vertex, time) => {
const a = -60.1;
const b = 0.8;
const c = 6.1;
// z += sin(index + (time / a + x) / b) / c

const positionModification = d.vec3f(
0,
0,
std.sin(d.f32(index) + (time / a + vertex.position.x) / b) / c,
);
const positionModification = d.vec3f(
0,
0,
std.sin(d.f32(index) + (time / a + vertex.position.x) / b) / c,
);

const coeff = std.cos(d.f32(index) + (time / a + vertex.position.x) / b) / c;
const newOX = std.normalize(d.vec3f(1, 0, coeff));
const newOZ = d.vec3f(-newOX.z, 0, newOX.x);
const newNormalXZ = std.add(
std.mul(vertex.normal.x, newOX),
std.mul(vertex.normal.z, newOZ),
);
const coeff = std.cos(d.f32(index) + (time / a + vertex.position.x) / b) /
c;
const newOX = std.normalize(d.vec3f(1, 0, coeff));
const newOZ = d.vec3f(-newOX.z, 0, newOX.x);
const newNormalXZ = std.add(
std.mul(vertex.normal.x, newOX),
std.mul(vertex.normal.z, newOZ),
);

const wavedNormal = d.vec3f(newNormalXZ.x, vertex.normal.y, newNormalXZ.z);
const wavedPosition = std.add(vertex.position, positionModification);
const wavedNormal = d.vec3f(newNormalXZ.x, vertex.normal.y, newNormalXZ.z);
const wavedPosition = std.add(vertex.position, positionModification);

return PosAndNormal({
position: wavedPosition,
normal: wavedNormal,
});
});
return PosAndNormal({
position: wavedPosition,
normal: wavedNormal,
});
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ const uniforms = root.createUniform(Uniforms);

// functions

const getBoxIntersection = tgpu['~unstable'].fn(
const getBoxIntersection = tgpu.fn(
[AxisAlignedBounds, Ray],
IntersectionStruct,
) /* wgsl */`(bounds: AxisAlignedBounds, ray: Ray) -> IntersectionStruct {
) /* wgsl */`(bounds, ray) {
var tMin: f32;
var tMax: f32;
var tMinY: f32;
Expand Down Expand Up @@ -154,7 +154,7 @@ const getBoxIntersection = tgpu['~unstable'].fn(

return IntersectionStruct(tMin > 0 && tMax > 0, tMin, tMax);
}`
.$uses({ AxisAlignedBounds, Ray, IntersectionStruct });
.$uses({ IntersectionStruct });

const Varying = {
rayWorldOrigin: d.vec3f,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ const mainVertex = tgpu['~unstable'].vertexFn({
* Given a coordinate, it returns a grayscale floor tile pattern at that
* location.
*/
const tilePattern = tgpu['~unstable'].fn([d.vec2f], d.f32)((uv) => {
const tilePattern = tgpu.fn([d.vec2f], d.f32)((uv) => {
const tiledUv = std.fract(uv);
const proximity = std.abs(std.sub(std.mul(tiledUv, 2), 1));
const maxProximity = std.max(proximity.x, proximity.y);
return std.clamp(std.pow(1 - maxProximity, 0.6) * 5, 0, 1);
});

const caustics = tgpu['~unstable'].fn([d.vec2f, d.f32, d.vec3f], d.vec3f)(
const caustics = tgpu.fn([d.vec2f, d.f32, d.vec3f], d.vec3f)(
(uv, time, profile) => {
const distortion = perlin3d.sample(d.vec3f(std.mul(uv, 0.5), time * 0.2));
// Distorting UV coordinates
Expand All @@ -37,20 +37,18 @@ const caustics = tgpu['~unstable'].fn([d.vec2f, d.f32, d.vec3f], d.vec3f)(
},
);

const clamp01 = tgpu['~unstable'].fn([d.f32], d.f32)((v) => {
return std.clamp(v, 0, 1);
});
const clamp01 = tgpu.fn([d.f32], d.f32)((v) => std.clamp(v, 0, 1));

/**
* Returns a transformation matrix that represents an `angle` rotation
* in the XY plane (around the imaginary Z axis)
*/
const rotateXY = tgpu['~unstable'].fn([d.f32], d.mat2x2f)((angle) => {
return d.mat2x2f(
const rotateXY = tgpu.fn([d.f32], d.mat2x2f)((angle) =>
d.mat2x2f(
/* right */ d.vec2f(std.cos(angle), std.sin(angle)),
/* up */ d.vec2f(-std.sin(angle), std.cos(angle)),
);
});
)
);

const root = await tgpu.init();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,27 @@ import tgpu from 'typegpu';
import * as d from 'typegpu/data';
import * as std from 'typegpu/std';

export const unpackVec2u = tgpu['~unstable'].fn([d.vec2u], d.vec4f)(
(packed) => {
const xy = std.unpack2x16float(packed.x);
const zw = std.unpack2x16float(packed.y);
return d.vec4f(xy, zw);
},
);
export const unpackVec2u = tgpu.fn([d.vec2u], d.vec4f)((packed) => {
const xy = std.unpack2x16float(packed.x);
const zw = std.unpack2x16float(packed.y);
return d.vec4f(xy, zw);
});

export const packVec2u = tgpu['~unstable'].fn([d.vec4f], d.vec2u)((toPack) => {
export const packVec2u = tgpu.fn([d.vec4f], d.vec2u)((toPack) => {
const xy = std.pack2x16float(toPack.xy);
const zw = std.pack2x16float(toPack.zw);
return d.vec2u(xy, zw);
});

export const getAverageNormal = tgpu['~unstable'].fn([
d.vec4f,
d.vec4f,
d.vec4f,
], d.vec4f)((v1, v2, v3) => {
'kernel & js';
const edge1 = std.sub(v2.xyz, v1.xyz);
const edge2 = std.sub(v3.xyz, v1.xyz);
return std.normalize(d.vec4f(std.cross(edge1, edge2), 0));
});
export const getAverageNormal = tgpu.fn([d.vec4f, d.vec4f, d.vec4f], d.vec4f)(
(v1, v2, v3) => {
'kernel & js';
const edge1 = std.sub(v2.xyz, v1.xyz);
const edge2 = std.sub(v3.xyz, v1.xyz);
return std.normalize(d.vec4f(std.cross(edge1, edge2), 0));
},
);

export const calculateMidpoint = tgpu['~unstable'].fn(
[d.vec4f, d.vec4f],
d.vec4f,
)((v1, v2) => {
return d.vec4f(std.mul(0.5, std.add(v1.xyz, v2.xyz)), 1);
});
export const calculateMidpoint = tgpu.fn([d.vec4f, d.vec4f], d.vec4f)(
(v1, v2) => d.vec4f(std.mul(0.5, std.add(v1.xyz, v2.xyz)), 1),
);
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ const gridSizeAccess = tgpu['~unstable'].accessor(d.f32);
const timeAccess = tgpu['~unstable'].accessor(d.f32);
const sharpnessAccess = tgpu['~unstable'].accessor(d.f32);

const exponentialSharpen = tgpu['~unstable'].fn([d.f32, d.f32], d.f32)(
(n, sharpness) => sign(n) * pow(abs(n), 1 - sharpness),
const exponentialSharpen = tgpu.fn([d.f32, d.f32], d.f32)((n, sharpness) =>
sign(n) * pow(abs(n), 1 - sharpness)
);

const tanhSharpen = tgpu['~unstable'].fn([d.f32, d.f32], d.f32)(
(n, sharpness) => tanh(n * (1 + sharpness * 10)),
const tanhSharpen = tgpu.fn([d.f32, d.f32], d.f32)((n, sharpness) =>
tanh(n * (1 + sharpness * 10))
);

const sharpenFnSlot = tgpu['~unstable'].slot<
Expand Down
Loading