diff --git a/apps/typegpu-docs/astro.config.mjs b/apps/typegpu-docs/astro.config.mjs
index 86add74f6f..f9bfb14155 100644
--- a/apps/typegpu-docs/astro.config.mjs
+++ b/apps/typegpu-docs/astro.config.mjs
@@ -131,11 +131,6 @@ export default defineConfig({
label: 'Functions',
slug: 'fundamentals/functions',
},
- {
- label: 'TGSL',
- slug: 'fundamentals/tgsl',
- badge: { text: 'new' },
- },
{
label: 'Pipelines',
slug: 'fundamentals/pipelines',
diff --git a/apps/typegpu-docs/package.json b/apps/typegpu-docs/package.json
index 692643bc9f..30e1445612 100644
--- a/apps/typegpu-docs/package.json
+++ b/apps/typegpu-docs/package.json
@@ -50,10 +50,10 @@
"remeda": "^2.21.2",
"sharp": "^0.34.2",
"starlight-blog": "^0.23.2",
- "starlight-typedoc": "^0.21.4",
+ "starlight-typedoc": "^0.19.0",
"tinybench": "^3.1.0",
- "typedoc": "^0.28.13",
- "typedoc-plugin-markdown": "^4.3.0",
+ "typedoc": "^0.27.9",
+ "typedoc-plugin-markdown": "4.3.0",
"typegpu": "workspace:*",
"typescript": "catalog:types",
"unplugin-typegpu": "workspace:*",
diff --git a/apps/typegpu-docs/src/content/docs/fundamentals/buffers.mdx b/apps/typegpu-docs/src/content/docs/fundamentals/buffers.mdx
index a6de95d764..9448b46089 100644
--- a/apps/typegpu-docs/src/content/docs/fundamentals/buffers.mdx
+++ b/apps/typegpu-docs/src/content/docs/fundamentals/buffers.mdx
@@ -195,7 +195,7 @@ Since the buffer is already created, you are responsible for the buffer's lifecy
## Writing to a buffer
-To write data to a buffer, you can use the `.write(value)` method. The typed schema enables auto-complete as well as static validation of this
+To write data to a buffer from the CPU, you can use the `.write(value)` method. The typed schema enables auto-complete as well as static validation of this
method's arguments.
```ts twoslash
@@ -285,7 +285,7 @@ backupParticleBuffer.copyFrom(particleBuffer);
## Reading from a buffer
-To read data from a buffer, you can use the `.read()` method.
+To read data from a buffer on the CPU, you can use the `.read()` method.
It returns a promise that resolves to the data read from the buffer.
```ts twoslash
@@ -348,32 +348,25 @@ import * as d from 'typegpu/data';
const root = await tgpu.init();
// ---cut---
-const pointsBuffer = root
- .createBuffer(d.arrayOf(d.vec2i, 100))
- .$usage('storage');
-
-const bindGroupLayout = tgpu.bindGroupLayout({
- points: { storage: d.arrayOf(d.vec2i, 100), access: 'mutable' },
+const layout = tgpu.bindGroupLayout({
+ points: { storage: d.arrayOf(d.vec2i), access: 'mutable' },
});
-const bindGroup = root
- .createBindGroup(bindGroupLayout, { points: pointsBuffer });
-
-const mainCompute = tgpu['~unstable'].computeFn({
- in: { gid: d.builtin.globalInvocationId },
- workgroupSize: [1],
-})((input) => {
+const pipeline = root['~unstable'].createGuardedComputePipeline((x) => {
+ 'use gpu';
// Access and modify the bound buffer via the layout
- bindGroupLayout.$.points[input.gid[0]] = d.vec2i(1, 2);
+ layout.$.points[x] = d.vec2i(1, 2);
});
-const pipeline = root['~unstable']
- .withCompute(mainCompute)
- .createPipeline();
+const pointsBuffer = root
+ .createBuffer(d.arrayOf(d.vec2i, 100))
+ .$usage('storage');
+
+const bindGroup = root.createBindGroup(layout, {
+ points: pointsBuffer
+});
-pipeline
- .with(bindGroupLayout, bindGroup)
- .dispatchWorkgroups(100);
+pipeline.with(bindGroup).dispatchThreads(100);
```
### Using fixed resources
@@ -394,18 +387,15 @@ import * as d from 'typegpu/data';
const root = await tgpu.init();
// ---cut---
-const pointsBuffer = root.createMutable(d.arrayOf(d.vec2i, 100));
+const pointsMutable = root.createMutable(d.arrayOf(d.vec2i, 100));
-const mainCompute = tgpu['~unstable'].computeFn({
- in: { gid: d.builtin.globalInvocationId },
- workgroupSize: [1],
-})((input) => {
+const pipeline = root['~unstable'].createGuardedComputePipeline((x) => {
+ 'use gpu';
// Access and modify the fixed buffer directly
- pointsBuffer.$[input.gid[0]] = d.vec2i();
+ pointsMutable.$[x] = d.vec2i();
});
-const pipeline = root['~unstable'].withCompute(mainCompute).createPipeline();
-pipeline.dispatchWorkgroups(100);
+pipeline.dispatchThreads(100);
```
TypeGPU automatically generates a "catch-all" bind group and populates it with the fixed resources.
diff --git a/apps/typegpu-docs/src/content/docs/fundamentals/functions/index.mdx b/apps/typegpu-docs/src/content/docs/fundamentals/functions/index.mdx
index 63014e0edd..dc7748dc08 100644
--- a/apps/typegpu-docs/src/content/docs/fundamentals/functions/index.mdx
+++ b/apps/typegpu-docs/src/content/docs/fundamentals/functions/index.mdx
@@ -3,13 +3,12 @@ title: Functions
description: A guide on how to create and use the TypeGPU typed functions.
---
-:::note[Recommended reading]
-We assume that you are familiar with the following concepts:
-- WebGPU Fundamentals
-- WebGPU Shading Language
+:::caution[May require unplugin-typegpu]
+To write TypeGPU functions in JavaScript/TypeScript, you need to install and configure [unplugin-typegpu](/TypeGPU/tooling/unplugin-typegpu).
+If you're planning on only using WGSL, you can skip installing it.
:::
-TypeGPU functions let you define shader logic in a modular and type-safe way.
+**TypeGPU functions** let you define shader logic in a modular and type-safe way.
Their signatures are fully visible to TypeScript, enabling tooling and static checks.
Dependencies, including GPU resources or other functions, are resolved automatically, with no duplication or name clashes.
This also supports distributing shader logic across multiple modules or packages.
@@ -17,35 +16,355 @@ Imported functions from external sources are automatically resolved and embedded
## Defining a function
-In order to construct a TypeGPU function, you need to start by defining its shell, an object holding only the input and output types.
+:::note[WGSL enthusiasts!]
+Don't let the JavaScript scare you! TypeGPU functions can be implemented using either WGSL or JS, both being able to call one another.
+If you're planning on only using WGSL, you can skip right over to [Implementing functions in WGSL](#implementing-functions-in-wgsl),
+though we recommend reading through anyway.
+:::
+
+The simplest and most powerful way to define TypeGPU functions is to just place `'use gpu'` at the beginning of the function body.
+
+```ts twoslash
+import tgpu from 'typegpu';
+import * as d from 'typegpu/data';
+// ---cut---
+const neighborhood = (a: number, r: number) => {
+ 'use gpu';
+ return d.vec2f(a - r, a + r);
+};
+```
+
+The `'use gpu'` directive allows the function to be picked up by our dedicated build plugin -- [unplugin-typegpu](/TypeGPU/tooling/unplugin-typegpu)
+and transformed into a format TypeGPU can understand. This doesn't alter the fact that the function is still callable from JavaScript, and behaves
+the same on the CPU and GPU.
+
+There are three main ways to use TypeGPU functions.
+
+```ts twoslash
+import tgpu from 'typegpu';
+import * as d from 'typegpu/data';
+const root = await tgpu.init();
+
+const neighborhood = (a: number, r: number) => {
+ 'use gpu';
+ return d.vec2f(a - r, a + r);
+};
+
+// ---cut---
+const main = () => {
+ 'use gpu';
+ // Call from another function
+ return neighborhood(1.1, 0.5);
+};
+
+// #1) Can be called in JS
+const range = main();
+// ^?
+
+// #2) Used to generate WGSL
+const wgsl = tgpu.resolve({ externals: { main } });
+// ^?
+
+// #3) Executed on the GPU (generates WGSL underneath)
+root['~unstable']
+ .createGuardedComputePipeline(main)
+ .dispatchThreads();
+````
+
+The contents of the `wgsl` variable would contain the following:
+
+```wgsl
+// Generated WGSL
+fn neighborhood(a: f32, r: f32) -> vec2f {
+ return vec2f(a - r, a + r);
+}
+
+fn main() -> vec2f {
+ return neighborhood(1.1, 0.5);
+}
+
+// ...
+````
+
+You can already notice a few things about TypeGPU functions:
+- Using operators like `+`, `-`, `*`, `/`, etc. is perfectly valid on numbers.
+- TS types are properly inferred, feel free to hover over the variables to see their types.
+- The generated code closely matches your source code.
+
+### Code transformation
+
+To make this all work, we perform a small transformation to functions marked with `'use gpu'`, leaving all other code untouched. Every project's setup is different, and we want to be as non-invasive as possible. The [unplugin-typegpu](/TypeGPU/tooling/unplugin-typegpu) package hooks into existing bundlers and build tools, extracts ASTs from TypeGPU functions and compacts them into our custom format called [tinyest](https://www.npmjs.com/package/tinyest). This metadata is injected into the final JS bundle, then used to efficiently generate equivalent WGSL at runtime.
+
+:::tip
+If you wish for all WGSL generation to happen at build time, combine [unplugin-macros](https://github.com/unplugin/unplugin-macros) and our [resolve API](/TypeGPU/fundamentals/resolve).
+:::
+
+
+### Type inference
+
+Let's take a closer look at `neighborhood` versus the WGSL it generates.
+
+```ts
+// TS
+const neighborhood = (a: number, r: number) => {
+ 'use gpu';
+ return d.vec2f(a - r, a + r);
+};
+```
+```wgsl
+// WGSL
+fn neighborhood(a: f32, r: f32) -> vec2f {
+ return vec2f(a - r, a + r);
+}
+```
+
+How does TypeGPU determine that `a` and `r` are of type `f32`, and that the return type is `vec2f`? You might think that we parse the TypeScript source file and use the types
+that the user provided in the function signature, **but that's not the case**.
+
+While generating WGSL, TypeGPU infers the type of each expression, which means it knows the types of values passed in at each call site.
+
+```ts twoslash "1.1, 0.5"
+import * as d from 'typegpu/data';
+const neighborhood = (a: number, r: number) => {
+ 'use gpu';
+ return d.vec2f(a - r, a + r);
+};
+// ---cut---
+const main = () => {
+ 'use gpu';
+ // A very easy case, just floating point literals, so f32 by default
+ return neighborhood(1.1, 0.5);
+};
+```
+
+TypeGPU then propagates those types into the function body and analyses the types returned by the function.
+If it cannot unify them into a single type, it will throw an error.
+
+### Polymorphism
+
+For each set of input types, TypeGPU generates a specialized version of the function.
+
+```ts twoslash
+import * as d from 'typegpu/data';
+const neighborhood = (a: number, r: number) => {
+ 'use gpu';
+ return d.vec2f(a - r, a + r);
+};
+// ---cut---
+const main = () => {
+ 'use gpu';
+ const a = neighborhood(0, 1);
+ // We can also use casts to coerce values into a specific type.
+ const b = neighborhood(d.u32(1), d.f16(5.25));
+};
+```
+
+```wgsl
+// WGSL
+fn neighborhood(a: i32, r: i32) -> vec2f {
+ return vec2f(f32(a - r), f32(a + r));
+}
+
+fn neighborhood2(a: u32, r: f16) -> vec2f {
+ return vec2f(f32(f16(a) - r), f32(f16(a) + r));
+}
+
+fn main() {
+ var a = neighborhood(0, 1);
+ var b = neighborhood2(1, 5.25);
+}
+```
+
+You can limit the types that a function can accept by using [wrapping it in a shell](#function-shells).
+
+### Generics
+
+Since TypeScript types not taken into account when generating the shader code, there is no
+limitation on use of generic types.
+
+```ts twoslash
+import tgpu from 'typegpu';
+import * as d from 'typegpu/data';
+import * as std from 'typegpu/std';
+// ---cut---
+const double = (a: T): T => {
+ 'use gpu';
+ return std.mul(a, a);
+};
+```
+
+### Standard library
+
+TypeGPU provides a set of standard functions under `typegpu/std`, which you can use in your own TypeGPU functions. Our goal is for all functions to have matching
+behavior on the CPU and GPU, which unlocks many possibilities (shader unit testing, shared business logic, and more...).
+
+```ts twoslash
+import * as d from 'typegpu/data';
+import * as std from 'typegpu/std';
+
+function manhattanDistance(a: d.v3f, b: d.v3f) {
+ 'use gpu';
+ const dx = std.abs(a.x - b.x);
+ const dy = std.abs(a.y - b.y);
+ const dz = std.abs(a.z - b.z);
+
+ return std.max(dx, std.max(dy, dz));
+}
+```
+
+You can explore the set of standard functions in the [API Reference](/TypeGPU/api/typegpu/std/functions/abs/).
+
+### The outer scope
+
+Things from the outer scope can be referenced inside TypeGPU functions, and they'll be automatically included in the
+generated shader code.
+
+```ts twoslash
+import * as d from 'typegpu/data';
+import * as std from 'typegpu/std';
+
+// ---cut---
+const from = d.vec3f(1, 0, 0);
+const to = d.vec3f(0, 1, 0);
+const constantMix = 0.5;
+
+const getColor = (t: number) => {
+ 'use gpu';
+ if (t > 0.5) {
+ // Above a certain threshold, mix the colors with a constant value
+ return std.mix(from, to, constantMix);
+ }
+ return std.mix(from, to, t);
+};
+```
+The above generates the following WGSL:
+```wgsl
+fn getColor(t: f32) -> vec3f {
+ if (t > 0.5) {
+ return vec3f(0.5, 0.5, 0);
+ }
+ return mix(vec3f(1, 0, 0), vec3f(0, 1, 0), t);
+}
+```
+
+Notice how `from` and `to` are inlined, and how `std.mix(from, to, constantMix)` was precomputed. TypeGPU leverages the
+fact that these values are known at shader compilation time, and can be optimized away. All other instructions are kept as is,
+since they use values known only during shader execution.
+
+:::tip
+To avoid inlining, use [tgpu.const](/TypeGPU/fundamentals/variables#const-variables).
+:::
+
+After seeing this, you might be tempted to use this mechanism for sharing data between the CPU and GPU, or for defining
+global variables used across functions, but values referenced by TypeGPU functions *are assumed to be constant*.
+
+```ts twoslash
+import tgpu from 'typegpu';
+import * as d from 'typegpu/data';
+
+const root = await tgpu.init();
+// ---cut---
+const settings = {
+ speed: 1,
+};
+
+const pipeline = root['~unstable'].createGuardedComputePipeline(() => {
+ 'use gpu';
+ const speed = settings.speed;
+ // ^ generates: var speed = 1;
+
+ // ...
+});
+
+pipeline.dispatchThreads();
+
+// 🚫🚫🚫 This is NOT allowed 🚫🚫🚫
+settings.speed = 1.5;
+
+// the shader doesn't get recompiled with the new value
+// of `speed`, so it's still 1.
+pipeline.dispatchThreads();
+```
+
+There are explicit mechanisms that allow you to achieve this:
+- [Use buffers to efficiently share data between the CPU and GPU](/TypeGPU/fundamentals/buffers)
+- [Use variables to share state between functions](/TypeGPU/fundamentals/variables)
+
+### Supported JavaScript functionality
+
+You can generally assume that all JavaScript syntax is supported, and in the occasion that it is not, we'll throw a
+descriptive error either at build time or at runtime (when compiling the shader).
+
+:::note
+Our aim with TypeGPU functions is not to allow arbitrary JavaScript to be supported in the context of shaders, **rather to allow for shaders to be written in JavaScript**. This distinction means we won't support every JavaScript feature, only those that make sense in the context of graphics programming.
+:::
+
+* **Calling other functions** --
+Only functions marked with `'use gpu'` can be called from within a shader. An exception to that rule is [`console.log`](/TypeGPU/fundamentals/utils#consolelog), which allows for tracking runtime behavior
+of shaders in a familiar way.
+
+* **Operators** --
+JavaScript does not support operator overloading.
+This means that, while you can still use operators for numbers,
+you have to use supplementary functions from `typegpu/std` (*add, mul, eq, lt, ge...*) for operations involving vectors and matrices, or use a fluent interface (*abc.mul(xyz), ...*)
+
+* **Math.\*** --
+Utility functions on the `Math` object can't automatically run on the GPU, but can usually be swapped with functions exported from `typegpu/std`.
+
+## Function shells
+
+In order to limit a function's signature to specific types, you can wrap it in a *shell*, an object holding only the input and output types.
The shell constructor `tgpu.fn` relies on [TypeGPU schemas](/TypeGPU/fundamentals/data-schemas), objects that represent WGSL data types and assist in generating shader code at runtime.
It accepts two arguments:
- An array of schemas representing argument types,
- (Optionally) a schema representing the return type.
-Then the actual WGSL implementation is passed in to a shell invocation using the tagged template literal.
+```ts twoslash
+import tgpu from 'typegpu';
+import * as d from 'typegpu/data';
+const neighborhood = (a: number, r: number) => {
+ 'use gpu';
+ return d.vec2f(a - r, a + r);
+};
+// ---cut---
+const neighborhoodShell = tgpu.fn([d.f32, d.f32], d.vec2f);
+
+// Works the same as `neighborhood`, but more strictly typed
+const neighborhoodF32 = neighborhoodShell(neighborhood);
+```
-The following code defines a function that accepts one argument of type `f32` and returns a `vec4f`.
+Although you can define the function and shell separately, the most common way to use shells is immediately wrapping functions with them:
```ts twoslash
import tgpu from 'typegpu';
import * as d from 'typegpu/data';
-
-const getGradientColor = tgpu.fn(
- [d.f32],
- d.vec4f
-) /* wgsl */`(ratio: f32) -> vec4f {
- var purple = vec4f(0.769, 0.392, 1.0, 1);
- var blue = vec4f(0.114, 0.447, 0.941, 1);
- return mix(purple, blue, ratio);
-}`;
+// ---cut---
+const neighborhood = tgpu.fn([d.f32, d.f32], d.vec2f)((a, r) => {
+ 'use gpu';
+ return d.vec2f(a - r, a + r);
+});
```
-:::tip
-If you're using Visual Studio Code, you can use [this extension](https://marketplace.visualstudio.com/items?itemName=ggsimm.wgsl-literal) that brings syntax highlighting to the code fragments marked with `/* wgsl */` comments.
+## Implementing functions in WGSL
+
+:::note[Recommended reading]
+We assume that you are familiar with the following concepts:
+- WebGPU Fundamentals
+- WebGPU Shading Language
:::
+Instead of passing JavaScript functions to shells, you can pass WGSL code directly:
+
+```ts twoslash
+import tgpu from 'typegpu';
+import * as d from 'typegpu/data';
+// ---cut---
+const neighborhood = tgpu.fn([d.f32, d.f32], d.vec2f)`(a: f32, r: f32) -> vec2f {
+ return vec2f(a - r, a + r);
+}`;
+```
+
Since type information is already present in the shell, the WGSL header can be simplified to include only the argument names.
```ts twoslash
@@ -53,49 +372,76 @@ import tgpu from 'typegpu';
import * as d from 'typegpu/data';
// ---cut---
-const getGradientColor = tgpu.fn([d.f32], d.vec4f) /* wgsl */`(ratio) {
- var purple = vec4f(0.769, 0.392, 1.0, 1);
- var blue = vec4f(0.114, 0.447, 0.941, 1);
- return mix(purple, blue, ratio);
+const neighborhood = tgpu.fn([d.f32, d.f32], d.vec2f)`(a, r) {
+ return vec2f(a - r, a + r);
}`;
```
-## External resources
-Functions can use external resources passed via the `$uses` method.
-Externals can include anything that can be resolved to WGSL by TypeGPU (numbers, vectors, matrices, constants, TypeGPU functions, buffer usages, textures, samplers, slots, accessors etc.).
+:::tip
+If you're using Visual Studio Code, you can use [this extension](https://marketplace.visualstudio.com/items?itemName=ggsimm.wgsl-literal) that brings syntax highlighting to code fragments marked with `/* wgsl */` comments.
+
+```ts twoslash
+import tgpu from 'typegpu';
+import * as d from 'typegpu/data';
+// ---cut---
+const neighborhood = tgpu.fn([d.f32, d.f32], d.vec2f)/* wgsl */`(a, r) {
+ return vec2f(a - r, a + r);
+}`;
+```
+:::
+
+### Including external resources
+
+Shelled WGSL functions can use external resources passed via the `$uses` method.
+*Externals* can include anything that can be resolved to WGSL by TypeGPU (numbers, vectors, matrices, constants, TypeGPU functions, buffer usages, textures, samplers, slots, accessors etc.).
```ts twoslash
import tgpu from 'typegpu';
import * as d from 'typegpu/data';
// ---cut---
-const getBlueFunction = tgpu.fn([], d.vec4f)`() {
- return vec4f(0.114, 0.447, 0.941, 1);
+const getBlue = tgpu.fn([], d.vec4f)`() {
+ return vec4f(0.114, 0.447, 0.941, 1);
}`;
-// calling a schema to create a value on the JS side
-const purple = d.vec4f(0.769, 0.392, 1.0, 1);
+// Calling a schema to create a value on the JS side
+const purple = d.vec4f(0.769, 0.392, 1.0, 1);
const getGradientColor = tgpu.fn([d.f32], d.vec4f)`(ratio) {
- return mix(purple, getBlue(), ratio);;
+ return mix(purple, get_blue(), ratio);
}
-`.$uses({ purple, getBlue: getBlueFunction });
+`.$uses({ purple, get_blue: getBlue });
```
-You can check yourself what `getGradientColor` resolves to by calling [`tgpu.resolve`](/TypeGPU/fundamentals/resolve), all relevant definitions will be automatically included:
+You can see for yourself what `getGradientColor` resolves to by calling [`tgpu.resolve`](/TypeGPU/fundamentals/resolve), all relevant definitions will be automatically included:
```wgsl
// results of calling tgpu.resolve({ externals: { getGradientColor } })
-fn getBlueFunction_1() -> vec4f{
- return vec4f(0.114, 0.447, 0.941, 1);
+
+fn getBlue_1() -> vec4f{
+ return vec4f(0.114, 0.447, 0.941, 1);
}
-fn getGradientColor_0(ratio: f32) -> vec4f{
- return mix(vec4f(0.769, 0.392, 1, 1), getBlueFunction_1(), ratio);;
+fn getGradientColor_0(ratio: f32) -> vec4f {
+ return mix(vec4f(0.769, 0.392, 1, 1), getBlue_1(), ratio);
}
```
+Notice how `purple` was inlined in the final shader, and the reference to `get_blue` was replaced with
+the function's eventual name of `getBlue_1`.
+
+### When to use JavaScript / WGSL
+Writing shader code in JavaScript has a few significant advantages.
+It allows defining utilities once and using them both on the GPU and CPU,
+as well as enables complete syntax highlighting and autocomplete in TypeGPU function definitions, leading to a better developer experience.
+
+However, there are cases where WGSL might be more suitable.
+Since JavaScript doesn't support operator overloading, functions including complex matrix or vector operations can be more readable in WGSL.
+Writing WGSL becomes a necessity whenever TypeGPU does not yet support some feature or standard library function yet.
+
+Luckily, you don't have to choose one or the other for the entire project. It is possible to mix and match WGSL and JavaScript at every step of the way, so you're not locked into one or the other.
+
## Entry functions
:::caution[Experimental]
@@ -113,9 +459,9 @@ Instead of annotating a `TgpuFn` with attributes, entry functions are defined us
To describe the input and output of an entry point function, we use `IORecord`s, JavaScript objects that map argument names to their types.
```ts
-const vertexInput = {
+const vertexInput = {
idx: d.builtin.vertexIndex,
- position: d.vec4f,
+ position: d.vec4f,
color: d.vec4f
}
```
@@ -124,7 +470,7 @@ As you may note, builtin inter-stage inputs and outputs are available on the `d.
and require no further type clarification.
Another thing to note is that there is no need to specify locations of the arguments,
-as TypeGPU tries to assign locations automatically.
+as TypeGPU tries to assign locations automatically.
If you wish to, you can assign the locations manually with the `d.location` decorator.
During WGSL generation, TypeGPU automatically generates structs corresponding to the passed `IORecord`s.
@@ -132,7 +478,7 @@ In WGSL implementation, input and output structs of the given function can be re
Headers in WGSL implementations must be omitted, all input values are accessible through the struct named `in`.
:::note
-Schemas used in `d.struct` can be wrapped in `d.size` and `d.align` decorators,
+Schemas used in `d.struct` can be wrapped in `d.size` and `d.align` decorators,
corresponding to `@size` and `@align` WGSL attributes.
Since TypeGPU wraps `IORecord`s into automatically generated structs, you can also use those decorators in `IOStruct`s.
diff --git a/apps/typegpu-docs/src/content/docs/fundamentals/tgsl.mdx b/apps/typegpu-docs/src/content/docs/fundamentals/tgsl.mdx
deleted file mode 100644
index b31d63985c..0000000000
--- a/apps/typegpu-docs/src/content/docs/fundamentals/tgsl.mdx
+++ /dev/null
@@ -1,189 +0,0 @@
----
-title: TGSL
-description: Guide on using JavaScript for WebGPU function definitions.
----
-
-:::caution[Experimental]
-This feature is under heavy development and is yet to reach stability.
-:::
-
-**TGSL (TypeGPU Shading Language)** is a subset of JavaScript used to define functions that run on the GPU via TypeGPU.
-It works by transpiling JavaScript into a compact AST format, called [tinyest](https://www.npmjs.com/package/tinyest),
-which is then used to generate equivalent WGSL.
-
-You can check the current state of supported JavaScript syntax in the [tinyest-for-wgsl repository](https://github.com/software-mansion/TypeGPU/blob/release/packages/tinyest-for-wgsl/src/parsers.ts).
-
-For the TGSL functions to work, you need to use the dedicated build plugin -- [unplugin-typegpu](/TypeGPU/tooling/unplugin-typegpu).
-
-## Usage
-
-Instead of using a WGSL code string, you can pass TGSL to the tgpu function shell as an argument instead.
-Functions from the WGSL standard library (*distance, arrayLength, workgroupBarrier, [etc.](https://github.com/software-mansion/TypeGPU/blob/release/packages/typegpu/src/std/index.ts)*) are accessible through the `typegpu/std` entrypoint.
-The package also includes functions for vector and matrix operators (*add, eq, lt...*).
-
-```ts
-import tgpu from 'typegpu';
-import * as d from 'typegpu/data';
-import * as std from 'typegpu/std';
-
-const MAX_OBSTACLES = 4;
-const obstacles = root
- .createReadonly(d.arrayOf(d.struct({
- center: d.vec2i,
- size: d.vec2i,
- enabled: d.u32,
- }), MAX_OBSTACLES));
-
-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;
- }
- 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
-import {
- oklabGamutClip,
- oklabGamutClipAlphaAccess,
- oklabGamutClipSlot,
- oklabToLinearRgb,
- oklabToRgb,
-} from '@typegpu/color';
-import tgpu from 'typegpu';
-import * as d from 'typegpu/data';
-import { any, cos, floor, gt, lt, mul, select, sin } from 'typegpu/std';
-
-const mainFragment = tgpu['~unstable'].fragmentFn({
- in: { uv: d.vec2f },
- out: d.vec4f,
-})((input) => {
- const hue = layout.$.uniforms.hue;
- const pos = scaleView(input.uv);
- const lab = d.vec3f(
- pos.y,
- mul(pos.x, d.vec2f(cos(hue), sin(hue))),
- );
- const rgb = oklabToLinearRgb(lab);
- const outOfGamut = any(lt(rgb, d.vec3f(0))) || any(gt(rgb, d.vec3f(1)));
-
- const clipLab = oklabGamutClipSlot.value(lab);
- const color = oklabToRgb(lab);
-
- const patternScaled = patternSlot.value(input.uv, clipLab) * 0.1 + 0.9;
-
- return d.vec4f(select(color, mul(patternScaled, color), outOfGamut), 1);
-});
-```
-
-:::note
-When using TGSL, you don't need to explicitly declare function externals with the `$uses` method.
-The [TypeGPU Build Plugin](/TypeGPU/tooling/unplugin-typegpu/) automatically extracts all external definitions from the function's AST.
-:::
-
-Sometimes, we are unable to recognize functions that are supposed to be TGSL. For that case, we have a *"use gpu"* directive.
-
-```ts
-const patternFn = tgpu.fn([d.vec2f], d.f32);
-
-const patternCheckers = patternFn((uv) => {
- 'use gpu';
- const suv = floor(mul(20, uv));
- return suv.x + suv.y - 2 * floor((suv.x + suv.y) * 0.5);
-});
-
-const patternSolid = patternFn(() => {
- 'use gpu';
- return 1;
-});
-```
-
-## Executing TGSL functions in JS
-
-TGSL-implemented functions can be invoked on the CPU,
-as along as they do not use any GPU-exclusive functionalities,
-like buffers or textures (regardless of whether they are marked with *"use gpu"* or not).
-
-Keep in mind that you cannot execute entry-point functions in JavaScript.
-
-## What to keep in mind
-
-* **TGSL limitations** --
-For a function to be valid TGSL, it must consist only of supported JS syntax (again, see [tinyest-for-wgsl repository](https://github.com/software-mansion/TypeGPU/blob/release/packages/tinyest-for-wgsl/src/parsers.ts)), possibly including references to bound buffers, constant variables defined outside the function, other TGSL functions etc.
-This means that, for example, `Math.sqrt(n)` calls will not work on the GPU.
-One exception to this is `console.log()`, about which you can read more [here](/TypeGPU/fundamentals/utils/#consolelog).
-
-* **Differences between JS on the CPU and GPU** --
-TGSL is developed to work on the GPU the same as on the CPU as much as possible,
-however because of the fundamental differences between the JavaScript and WGSL languages, it is not guaranteed to always be the case.
-
- Currently, the biggest known difference is that vectors, matrices and structs are treated as reference types in JavaScript and value types in WGSL.
-That is, on the WGSL side, the assignment operator copies the value instead of the reference, and two different vectors can be equal to each other if only they store the same values, unlike in JS, where they need to point to the same reference.
-To somehow alleviate this issue, when passing arguments to tgpu functions on JS side, we perform a deep copy of them (note that in WGSL arguments are immutable by default).
-
- When using TGSL on the GPU, the behavior is that of WGSL, not JS, as one would expect.
-Therefore some WGSL knowledge is still required, even when opting out for TGSL.
-
-* **.value** --
-Objects that have different types on the CPU and on the GPU (like buffers, layouts, slots etc.) need to be accessed via the `value` property in TGSL functions (or the `$` property alias).
-This is different from how they appear in WGSL-implemented ones.
-
-```ts twoslash
-import tgpu from 'typegpu';
-import * as d from 'typegpu/data';
-
-const root = await tgpu.init();
-
-const bgColor = root.createUniform(d.vec4f, d.vec4f(0.114, 0.447, 0.941, 1));
-
-const fragmentTgsl = tgpu['~unstable'].fragmentFn({ out: d.vec4f })(() => {
- return bgColor.$;
-// ^?
-});
-
-const fragmentWgsl = tgpu['~unstable'].fragmentFn({ out: d.vec4f })`{
- return bgColor;
-}
-`.$uses({ bgColor });
-```
-
-* **Operators** --
-JavaScript does not support operator overloading.
-This means that, while you can still use operators for numbers,
-you have to use supplementary functions from `typegpu/std` (*add, mul, eq, lt, ge...*) for operations involving vectors and matrices.
-
-* **Pointers** --
-Since WGSL pointers can only point to a narrow set of items called Storable Types,
-TypeGPU tries to dereference pointers used in TGSL automatically when it seems appropriate.
-For example, function arguments that are pointers to reference types are passed just as the pointed object.
-Pointers to primitive types (numbers and booleans) are currently not supported.
-
-```ts twoslash
-import tgpu from 'typegpu';
-import * as d from 'typegpu/data';
-// ---cut---
-const fn = tgpu.fn([d.ptrFn(d.vec3f)], d.vec3f)((ptr) => {
- ptr.x += 1;
-//^?
- return ptr;
-});
-```
-
-* **When to use TGSL instead of WGSL** --
-Writing the code using TGSL has a few significant advantages.
-It allows defining utils only once and using them both as GPU and CPU functions,
-as well as enables complete syntax highlighting and autocomplete in TypeGPU function definitions, leading to a better developer UX.
-However, it sometimes might be better to choose WGSL for certain functions.
-Since JavaScript doesn't support operator overloading, functions including complex matrix operations can be more readable in WGSL.
-Writing WGSL becomes a necessity whenever TGSL does not support some feature or standard library function quite yet.
-Luckily, you don't have to choose one or the other for the entire project. It is possible to mix and match WGSL and TGSL at every step of the way.
diff --git a/apps/typegpu-docs/src/content/docs/fundamentals/variables.mdx b/apps/typegpu-docs/src/content/docs/fundamentals/variables.mdx
index f170548d9d..842c6852b3 100644
--- a/apps/typegpu-docs/src/content/docs/fundamentals/variables.mdx
+++ b/apps/typegpu-docs/src/content/docs/fundamentals/variables.mdx
@@ -61,10 +61,11 @@ const root = await tgpu.init();
// ---cut---
const threadCounter = tgpu.privateVar(d.u32, 0);
-const getNext = tgpu.fn([], d.i32)(() => {
+const getNext = () => {
+ 'use gpu';
threadCounter.$ += 1;
return [42, 418, 23][threadCounter.$];
-})
+};
const myComputeFn = tgpu['~unstable'].computeFn({
workgroupSize: [64],
@@ -96,13 +97,14 @@ const boid = tgpu.const(Boid, {
vel: d.vec3u(4, 5, 6),
});
-const func = tgpu.fn([])(() => {
+const func = () => {
+ 'use gpu';
const pos = boid.$;
const vel = boid.$.vel;
const velX = boid.$.vel.x;
-});
+};
```
:::caution
-TypeGPU does not support defining `tgpu.const()` variables inside TGSL.
+TypeGPU does not support defining `tgpu.const()` variables inside a TypeGPU function.
:::
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index df4e9bec77..1a96e11566 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -237,17 +237,17 @@ importers:
specifier: ^0.23.2
version: 0.23.2(@astrojs/starlight@0.36.1(astro@5.14.5(@types/node@24.7.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1)))(astro@5.14.5(@types/node@24.7.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1))
starlight-typedoc:
- specifier: ^0.21.4
- version: 0.21.4(@astrojs/starlight@0.36.1(astro@5.14.5(@types/node@24.7.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1)))(typedoc-plugin-markdown@4.4.2(typedoc@0.28.13(typescript@5.8.3)))(typedoc@0.28.13(typescript@5.8.3))
+ specifier: ^0.19.0
+ version: 0.19.0(@astrojs/starlight@0.36.1(astro@5.14.5(@types/node@24.7.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1)))(typedoc-plugin-markdown@4.3.0(typedoc@0.27.9(typescript@5.8.3)))(typedoc@0.27.9(typescript@5.8.3))
tinybench:
specifier: ^3.1.0
version: 3.1.1
typedoc:
- specifier: ^0.28.13
- version: 0.28.13(typescript@5.8.3)
+ specifier: ^0.27.9
+ version: 0.27.9(typescript@5.8.3)
typedoc-plugin-markdown:
- specifier: ^4.3.0
- version: 4.4.2(typedoc@0.28.13(typescript@5.8.3))
+ specifier: 4.3.0
+ version: 4.3.0(typedoc@0.27.9(typescript@5.8.3))
typegpu:
specifier: workspace:*
version: link:../../packages/typegpu
@@ -1343,8 +1343,8 @@ packages:
'@floating-ui/utils@0.2.9':
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
- '@gerrit0/mini-shiki@3.13.0':
- resolution: {integrity: sha512-mCrNvZNYNrwKer5PWLF6cOc0OEe2eKzgy976x+IT2tynwJYl+7UpHTSeXQJGijgTcoOf+f359L946unWlYRnsg==}
+ '@gerrit0/mini-shiki@1.27.2':
+ resolution: {integrity: sha512-GeWyHz8ao2gBiUW4OJnQDxXQnFgZQwwQk05t/CVVgNBN7/rK8XZ7xY6YhLVv9tH3VppWWmr9DCl3MwemB/i+Og==}
'@img/sharp-darwin-arm64@0.34.2':
resolution: {integrity: sha512-OfXHZPppddivUJnqyKoi5YVeHRkkNE2zUFT2gbpKxp/JZCFYEYubnMg+gOp6lWfasPrTS+KPosKqdI+ELYVDtg==}
@@ -2258,6 +2258,9 @@ packages:
'@shikijs/engine-javascript@3.13.0':
resolution: {integrity: sha512-Ty7xv32XCp8u0eQt8rItpMs6rU9Ki6LJ1dQOW3V/56PKDcpvfHPnYFbsx5FFUP2Yim34m/UkazidamMNVR4vKg==}
+ '@shikijs/engine-oniguruma@1.29.2':
+ resolution: {integrity: sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==}
+
'@shikijs/engine-oniguruma@3.12.2':
resolution: {integrity: sha512-hozwnFHsLvujK4/CPVHNo3Bcg2EsnG8krI/ZQ2FlBlCRpPZW4XAEQmEwqegJsypsTAN9ehu2tEYe30lYKSZW/w==}
@@ -2276,6 +2279,9 @@ packages:
'@shikijs/themes@3.13.0':
resolution: {integrity: sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg==}
+ '@shikijs/types@1.29.2':
+ resolution: {integrity: sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==}
+
'@shikijs/types@3.12.2':
resolution: {integrity: sha512-K5UIBzxCyv0YoxN3LMrKB9zuhp1bV+LgewxuVwHdl4Gz5oePoUFrr9EfgJlGlDeXCU1b/yhdnXeuRvAnz8HN8Q==}
@@ -5340,13 +5346,13 @@ packages:
peerDependencies:
'@astrojs/starlight': '>=0.33.0'
- starlight-typedoc@0.21.4:
- resolution: {integrity: sha512-ZnajyNs3CxzaDrKplBY9Lg4R12YheegCvbD2cvczN+8fLET0Rh3sss5mMN206pk8pVXeZgjPY5IATPJoCjjHqA==}
+ starlight-typedoc@0.19.0:
+ resolution: {integrity: sha512-NR1A3XjxEl8hS3xKaw7TutuFMaEYmMstgZNmSNAnAzav1dZtMoZsp3KM+S4Tr+5kYCR1v0DlF+ESouOn8Nl5Dw==}
engines: {node: '>=18.17.1'}
peerDependencies:
'@astrojs/starlight': '>=0.32.0'
- typedoc: '>=0.28.0'
- typedoc-plugin-markdown: '>=4.6.0'
+ typedoc: '>=0.26.5'
+ typedoc-plugin-markdown: '>=4.1.1'
state-local@1.0.7:
resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==}
@@ -5631,18 +5637,18 @@ packages:
typed-binary@4.3.2:
resolution: {integrity: sha512-HT3pIBM2njCZUmeczDaQUUErGiM6GXFCqMsHegE12HCoBtvHCkfR10JJni0TeGOTnLilTd6YFyj+YhflqQDrDQ==}
- typedoc-plugin-markdown@4.4.2:
- resolution: {integrity: sha512-kJVkU2Wd+AXQpyL6DlYXXRrfNrHrEIUgiABWH8Z+2Lz5Sq6an4dQ/hfvP75bbokjNDUskOdFlEEm/0fSVyC7eg==}
+ typedoc-plugin-markdown@4.3.0:
+ resolution: {integrity: sha512-yAR7+JGSwHWNzQhS8AFlGX6GmbWnK7/Q2Y8hHy5pkL/WB9ooJqhsI6B1abldhM3lOQCWqCdkzu9yFMPuvl2HUw==}
engines: {node: '>= 18'}
peerDependencies:
typedoc: 0.27.x
- typedoc@0.28.13:
- resolution: {integrity: sha512-dNWY8msnYB2a+7Audha+aTF1Pu3euiE7ySp53w8kEsXoYw7dMouV5A1UsTUY345aB152RHnmRMDiovuBi7BD+w==}
- engines: {node: '>= 18', pnpm: '>= 10'}
+ typedoc@0.27.9:
+ resolution: {integrity: sha512-/z585740YHURLl9DN2jCWe6OW7zKYm6VoQ93H0sxZ1cwHQEQrUn5BJrEnkWhfzUdyO+BLGjnKUZ9iz9hKloFDw==}
+ engines: {node: '>= 18'}
hasBin: true
peerDependencies:
- typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x
+ typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x
typesafe-path@0.2.2:
resolution: {integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==}
@@ -7019,12 +7025,10 @@ snapshots:
'@floating-ui/utils@0.2.9': {}
- '@gerrit0/mini-shiki@3.13.0':
+ '@gerrit0/mini-shiki@1.27.2':
dependencies:
- '@shikijs/engine-oniguruma': 3.13.0
- '@shikijs/langs': 3.13.0
- '@shikijs/themes': 3.13.0
- '@shikijs/types': 3.13.0
+ '@shikijs/engine-oniguruma': 1.29.2
+ '@shikijs/types': 1.29.2
'@shikijs/vscode-textmate': 10.0.2
'@img/sharp-darwin-arm64@0.34.2':
@@ -7862,6 +7866,11 @@ snapshots:
'@shikijs/vscode-textmate': 10.0.2
oniguruma-to-es: 4.3.3
+ '@shikijs/engine-oniguruma@1.29.2':
+ dependencies:
+ '@shikijs/types': 1.29.2
+ '@shikijs/vscode-textmate': 10.0.2
+
'@shikijs/engine-oniguruma@3.12.2':
dependencies:
'@shikijs/types': 3.12.2
@@ -7888,6 +7897,11 @@ snapshots:
dependencies:
'@shikijs/types': 3.13.0
+ '@shikijs/types@1.29.2':
+ dependencies:
+ '@shikijs/vscode-textmate': 10.0.2
+ '@types/hast': 3.0.4
+
'@shikijs/types@3.12.2':
dependencies:
'@shikijs/vscode-textmate': 10.0.2
@@ -11688,12 +11702,12 @@ snapshots:
- astro
- supports-color
- starlight-typedoc@0.21.4(@astrojs/starlight@0.36.1(astro@5.14.5(@types/node@24.7.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1)))(typedoc-plugin-markdown@4.4.2(typedoc@0.28.13(typescript@5.8.3)))(typedoc@0.28.13(typescript@5.8.3)):
+ starlight-typedoc@0.19.0(@astrojs/starlight@0.36.1(astro@5.14.5(@types/node@24.7.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1)))(typedoc-plugin-markdown@4.3.0(typedoc@0.27.9(typescript@5.8.3)))(typedoc@0.27.9(typescript@5.8.3)):
dependencies:
'@astrojs/starlight': 0.36.1(astro@5.14.5(@types/node@24.7.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1))
github-slugger: 2.0.0
- typedoc: 0.28.13(typescript@5.8.3)
- typedoc-plugin-markdown: 4.4.2(typedoc@0.28.13(typescript@5.8.3))
+ typedoc: 0.27.9(typescript@5.8.3)
+ typedoc-plugin-markdown: 4.3.0(typedoc@0.27.9(typescript@5.8.3))
state-local@1.0.7: {}
@@ -11987,13 +12001,13 @@ snapshots:
typed-binary@4.3.2: {}
- typedoc-plugin-markdown@4.4.2(typedoc@0.28.13(typescript@5.8.3)):
+ typedoc-plugin-markdown@4.3.0(typedoc@0.27.9(typescript@5.8.3)):
dependencies:
- typedoc: 0.28.13(typescript@5.8.3)
+ typedoc: 0.27.9(typescript@5.8.3)
- typedoc@0.28.13(typescript@5.8.3):
+ typedoc@0.27.9(typescript@5.8.3):
dependencies:
- '@gerrit0/mini-shiki': 3.13.0
+ '@gerrit0/mini-shiki': 1.27.2
lunr: 2.3.9
markdown-it: 14.1.0
minimatch: 9.0.5