From e46849110937488121ecab781473f0a46cf5996d Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Wed, 8 Oct 2025 12:36:46 +0200 Subject: [PATCH 1/6] Start wotk on better docs for JS Kernels --- apps/typegpu-docs/astro.config.mjs | 2 +- .../src/content/docs/fundamentals/tgsl.mdx | 62 ++++++++++++++++--- 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/apps/typegpu-docs/astro.config.mjs b/apps/typegpu-docs/astro.config.mjs index 72ae05ec7..8b12632eb 100644 --- a/apps/typegpu-docs/astro.config.mjs +++ b/apps/typegpu-docs/astro.config.mjs @@ -115,7 +115,7 @@ export default defineConfig({ slug: 'fundamentals/functions', }, { - label: 'TGSL', + label: 'JS Kernels', slug: 'fundamentals/tgsl', badge: { text: 'new' }, }, diff --git a/apps/typegpu-docs/src/content/docs/fundamentals/tgsl.mdx b/apps/typegpu-docs/src/content/docs/fundamentals/tgsl.mdx index 49d029b66..209eb4020 100644 --- a/apps/typegpu-docs/src/content/docs/fundamentals/tgsl.mdx +++ b/apps/typegpu-docs/src/content/docs/fundamentals/tgsl.mdx @@ -1,5 +1,5 @@ --- -title: TGSL +title: JS Kernels description: Guide on using JavaScript for WebGPU function definitions. --- @@ -7,13 +7,55 @@ description: Guide on using JavaScript for WebGPU function definitions. 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. +:::note[Requires unplugin-typegpu] +To use JS kernels, you need to install and configure [unplugin-typegpu](/TypeGPU/tooling/unplugin-typegpu). +::: + +**JS Kernels** are functions marked with the `'kernel'` directive, picked up by our dedicated build plugin -- [unplugin-typegpu](/TypeGPU/tooling/unplugin-typegpu) and transformed into a format TypeGPU can understand. + +```ts twoslash +import tgpu, { prepareDispatch } from 'typegpu'; +import * as d from 'typegpu/data'; +import * as std from 'typegpu/std'; + +function manhattanDistance(a: d.v3f, b: d.v3f) { + 'kernel'; + 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)); +} + +// #1) Can be called in JS +const dist = manhattanDistance(d.vec3f(1, 2, 3), d.vec3f(6, 5, 4)); + +// #2) Executed on the GPU +const root = await tgpu.init(); +prepareDispatch(root, () => { + 'kernel'; + manhattanDistance(d.vec3f(1, 2, 3), d.vec3f(6, 5, 4)); +}).dispatch(); + +// #3) Used to generate WGSL +const wgsl = tgpu.resolve({ externals: { manhattanDistance } }); +```` + +Let's answer a few questions right off the bat: +- **Do I need to write in TypeScript?** - TypeScript types themselves don't have any effect on the generated WGSL. + +## Anatomy of a kernel + -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). +:::note +JS kernels are picked up by `unplugin-typegpu` during bundling, their AST is compacted into our custom format called [tinyest](https://www.npmjs.com/package/tinyest) and injected +into the user code. This metadata is then used to generate equivalent WGSL at runtime efficiently. +::: + +:::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). +::: ## Usage @@ -149,10 +191,10 @@ 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; +const fragmentWgsl = tgpu['~unstable'].fragmentFn({ out: d.vec4f })`{ + return bgColor; } `.$uses({ bgColor }); ``` @@ -176,7 +218,7 @@ const fn = tgpu.fn([d.ptrFn(d.vec3f)], d.vec3f)((ptr) => { ptr.x += 1; //^? return ptr; -}); +}); ``` * **When to use TGSL instead of WGSL** -- From 1d6e462b7654c0d8d250bee34474c9ca2af847eb Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Thu, 9 Oct 2025 11:52:50 +0200 Subject: [PATCH 2/6] More work on the docs --- .../docs/fundamentals/functions/index.mdx | 169 ++++++++++++++++-- .../src/content/docs/fundamentals/tgsl.mdx | 47 ++++- .../content/docs/fundamentals/variables.mdx | 12 +- packages/typegpu/src/prepareDispatch.ts | 2 +- 4 files changed, 204 insertions(+), 26 deletions(-) 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 63014e0ed..79873eb93 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 this step. ::: -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,6 +16,122 @@ Imported functions from external sources are automatically resolved and embedded ## Defining a function +:::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, { prepareDispatch } 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--- +// #1) Can be called in JS +// +const range = neighborhood(1, 0.2); +// ^? + +// #2) Executed on the GPU +// +const main = () => { + 'use gpu'; + // Call from another function + neighborhood(1.1, 0.5); +}; +// Dispatching a compute shader! +prepareDispatch(root, main).dispatch(); + +// #3) Used to generate WGSL +// +const wgsl = tgpu.resolve({ externals: { main } }); +```` + +The contents of the `wgsl` variable would resemble the following: + +```wgsl +// Generated WGSL +fn neighborhood(a: f32, r: f32) -> vec2f { + return vec2f(a - r, a + r); +} + +@compute @workgroup_size(1, 1, 1) +fn main() { + 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. + +## 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 + 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. + +## Bruh + In order to construct a TypeGPU function, you need to start by defining its 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: @@ -33,7 +148,7 @@ import tgpu from 'typegpu'; import * as d from 'typegpu/data'; const getGradientColor = tgpu.fn( - [d.f32], + [d.f32], d.vec4f ) /* wgsl */`(ratio: f32) -> vec4f { var purple = vec4f(0.769, 0.392, 1.0, 1); @@ -60,6 +175,34 @@ const getGradientColor = tgpu.fn([d.f32], d.vec4f) /* wgsl */`(ratio) { }`; ``` +## Implementing functions in WGSL + +:::note[Recommended reading] +We assume that you are familiar with the following concepts: +- WebGPU Fundamentals +- WebGPU Shading Language +::: + +```ts +// "shelled" function: +// Same as a "bare" function, but with restricted argument +// and return types. +// +const rangeB = tgpu.fn([d.f32, d.f32], d.vec2f)((a, b) => { + return d.vec2f(a + b, a - b).mul(multiplier); +}); + +// "shelled wgsl" function: +// A shell defining the function's signature, and the +// implementation in WGSL. +// +const rangeC = tgpu.fn([d.f32, d.f32], d.vec2f)`(a, b) { + return vec2f(a + b, a - b) * multiplier; +} +`.$uses({ multiplier }); +// ^ Giving the function access to the 'multiplier' external value +```` + ## External resources Functions can use external resources passed via the `$uses` method. @@ -71,11 +214,11 @@ import * as d from 'typegpu/data'; // ---cut--- const getBlueFunction = tgpu.fn([], d.vec4f)`() { - return vec4f(0.114, 0.447, 0.941, 1); + 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); +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);; @@ -88,7 +231,7 @@ You can check yourself what `getGradientColor` resolves to by calling [`tgpu.res ```wgsl // results of calling tgpu.resolve({ externals: { getGradientColor } }) fn getBlueFunction_1() -> vec4f{ - return vec4f(0.114, 0.447, 0.941, 1); + return vec4f(0.114, 0.447, 0.941, 1); } fn getGradientColor_0(ratio: f32) -> vec4f{ @@ -113,9 +256,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 +267,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 +275,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 index 209eb4020..dd0ea7a52 100644 --- a/apps/typegpu-docs/src/content/docs/fundamentals/tgsl.mdx +++ b/apps/typegpu-docs/src/content/docs/fundamentals/tgsl.mdx @@ -1,5 +1,5 @@ --- -title: JS Kernels +title: TS Kernels description: Guide on using JavaScript for WebGPU function definitions. --- @@ -8,10 +8,29 @@ This feature is under heavy development and is yet to reach stability. ::: :::note[Requires unplugin-typegpu] -To use JS kernels, you need to install and configure [unplugin-typegpu](/TypeGPU/tooling/unplugin-typegpu). +To use TS kernels, you need to install and configure [unplugin-typegpu](/TypeGPU/tooling/unplugin-typegpu). ::: -**JS Kernels** are functions marked with the `'kernel'` directive, picked up by our dedicated build plugin -- [unplugin-typegpu](/TypeGPU/tooling/unplugin-typegpu) and transformed into a format TypeGPU can understand. +**TS Kernels** are functions marked with the `'kernel'` directive, picked up by our dedicated build plugin -- [unplugin-typegpu](/TypeGPU/tooling/unplugin-typegpu) and transformed into a format TypeGPU can understand. + +```ts twoslash +import * as d from 'typegpu/data'; +import * as std from 'typegpu/std'; + +function manhattanDistance(a: d.v3f, b: d.v3f) { + 'tgpu'; + 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)); +} +``` + +TypeGPU already provides a set of standard kernels under `typegpu/std`, which you can use in your own kernels. Our goal is for all kernels to have matching +behavior on the CPU and GPU, which unlocks many possibilities. + +There are three main ways to use kernels. ```ts twoslash import tgpu, { prepareDispatch } from 'typegpu'; @@ -27,6 +46,7 @@ function manhattanDistance(a: d.v3f, b: d.v3f) { return std.max(dx, std.max(dy, dz)); } +// ---cut--- // #1) Can be called in JS const dist = manhattanDistance(d.vec3f(1, 2, 3), d.vec3f(6, 5, 4)); @@ -41,15 +61,28 @@ prepareDispatch(root, () => { const wgsl = tgpu.resolve({ externals: { manhattanDistance } }); ```` -Let's answer a few questions right off the bat: -- **Do I need to write in TypeScript?** - TypeScript types themselves don't have any effect on the generated WGSL. +The contents of the `wgsl` variable will be the following: -## Anatomy of a kernel +```wgsl +// Generated WGSL +fn manhattanDistance(a: vec3f, b: vec3f) -> f32 { + let dx = abs(a.x - b.x); + let dy = abs(a.y - b.y); + let dz = abs(a.z - b.z); + return max(dx, max(dy, dz)); +} +```` +You can already notice a few things about TS Kernels: +- 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. + +## Anatomy of a kernel :::note -JS kernels are picked up by `unplugin-typegpu` during bundling, their AST is compacted into our custom format called [tinyest](https://www.npmjs.com/package/tinyest) and injected +TS kernels are picked up by `unplugin-typegpu` during bundling, their AST is compacted into our custom format called [tinyest](https://www.npmjs.com/package/tinyest) and injected into the user code. This metadata is then used to generate equivalent WGSL at runtime efficiently. ::: diff --git a/apps/typegpu-docs/src/content/docs/fundamentals/variables.mdx b/apps/typegpu-docs/src/content/docs/fundamentals/variables.mdx index f170548d9..e4dbc7692 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 = () => { + 'kernel'; 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 = () => { + 'kernel'; 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/packages/typegpu/src/prepareDispatch.ts b/packages/typegpu/src/prepareDispatch.ts index 76989090a..158de3f9e 100644 --- a/packages/typegpu/src/prepareDispatch.ts +++ b/packages/typegpu/src/prepareDispatch.ts @@ -76,7 +76,7 @@ const workgroupSizeConfigs = [ */ export function prepareDispatch( root: TgpuRoot, - callback: (...args: TArgs) => undefined, + callback: (...args: TArgs) => void, ): PreparedDispatch { if (callback.length >= 4) { throw new Error('Dispatch only supports up to three dimensions.'); From f86dbf3d6b7169f028b1405838743c07c4543ff8 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Thu, 16 Oct 2025 22:15:55 +0200 Subject: [PATCH 3/6] More docs --- .../docs/fundamentals/functions/index.mdx | 87 +++++++++++++++---- pnpm-lock.yaml | 31 +------ 2 files changed, 71 insertions(+), 47 deletions(-) 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 79873eb93..f8a0b7780 100644 --- a/apps/typegpu-docs/src/content/docs/fundamentals/functions/index.mdx +++ b/apps/typegpu-docs/src/content/docs/fundamentals/functions/index.mdx @@ -41,7 +41,7 @@ the same on the CPU and GPU. There are three main ways to use TypeGPU functions. ```ts twoslash -import tgpu, { prepareDispatch } from 'typegpu'; +import tgpu from 'typegpu'; import * as d from 'typegpu/data'; const root = await tgpu.init(); @@ -51,27 +51,27 @@ const neighborhood = (a: number, r: number) => { }; // ---cut--- -// #1) Can be called in JS -// -const range = neighborhood(1, 0.2); -// ^? - -// #2) Executed on the GPU -// const main = () => { 'use gpu'; // Call from another function - neighborhood(1.1, 0.5); + return neighborhood(1.1, 0.5); }; -// Dispatching a compute shader! -prepareDispatch(root, main).dispatch(); -// #3) Used to generate WGSL -// +// #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'] + .prepareDispatch(main) + .dispatchThreads(); ```` -The contents of the `wgsl` variable would resemble the following: +The contents of the `wgsl` variable would contain the following: ```wgsl // Generated WGSL @@ -79,10 +79,11 @@ fn neighborhood(a: f32, r: f32) -> vec2f { return vec2f(a - r, a + r); } -@compute @workgroup_size(1, 1, 1) -fn main() { - neighborhood(1.1, 0.5); +fn main() -> vec2f { + return neighborhood(1.1, 0.5); } + +// ... ```` You can already notice a few things about TypeGPU functions: @@ -123,13 +124,63 @@ const neighborhood = (a: number, r: number) => { const main = () => { 'use gpu'; // A very easy case, just floating point literals, so f32 by default - neighborhood(1.1, 0.5); + 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. + +### Generics + +```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); +}; +``` + ## Bruh In order to construct a TypeGPU function, you need to start by defining its shell, an object holding only the input and output types. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0adb00e2e..2ce36b7bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,7 +57,7 @@ importers: version: link:packages/tgpu-dev-cli '@vitest/browser': specifier: ^3.2.4 - version: 3.2.4(msw@2.10.2(@types/node@24.8.0)(typescript@5.8.3))(vite@6.4.0(@types/node@24.8.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4) + version: 3.2.4(msw@2.10.2(@types/node@24.8.0)(typescript@5.8.3))(vite@6.3.6(@types/node@24.8.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4) '@vitest/coverage-v8': specifier: 3.1.2 version: 3.1.2(@vitest/browser@3.2.4)(vitest@3.2.4) @@ -8455,24 +8455,6 @@ snapshots: - msw - utf-8-validate - vite - optional: true - - '@vitest/browser@3.2.4(msw@2.10.2(@types/node@24.8.0)(typescript@5.8.3))(vite@6.4.0(@types/node@24.8.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)': - dependencies: - '@testing-library/dom': 10.4.0 - '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.0) - '@vitest/mocker': 3.2.4(msw@2.10.2(@types/node@24.8.0)(typescript@5.8.3))(vite@6.4.0(@types/node@24.8.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1)) - '@vitest/utils': 3.2.4 - magic-string: 0.30.17 - sirv: 3.0.1 - tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.8.0)(@vitest/browser@3.2.4)(jiti@2.6.0)(jsdom@27.0.0(canvas@3.2.0)(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.10.2(@types/node@24.8.0)(typescript@5.8.3))(tsx@4.20.6)(yaml@2.8.1) - ws: 8.18.3 - transitivePeerDependencies: - - bufferutil - - msw - - utf-8-validate - - vite '@vitest/coverage-v8@3.1.2(@vitest/browser@3.2.4)(vitest@3.2.4)': dependencies: @@ -8490,7 +8472,7 @@ snapshots: tinyrainbow: 2.0.0 vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.8.0)(@vitest/browser@3.2.4)(jiti@2.6.0)(jsdom@27.0.0(canvas@3.2.0)(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.10.2(@types/node@24.8.0)(typescript@5.8.3))(tsx@4.20.6)(yaml@2.8.1) optionalDependencies: - '@vitest/browser': 3.2.4(msw@2.10.2(@types/node@24.8.0)(typescript@5.8.3))(vite@6.4.0(@types/node@24.8.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4) + '@vitest/browser': 3.2.4(msw@2.10.2(@types/node@24.8.0)(typescript@5.8.3))(vite@6.3.6(@types/node@24.8.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4) transitivePeerDependencies: - supports-color @@ -8511,15 +8493,6 @@ snapshots: msw: 2.10.2(@types/node@24.8.0)(typescript@5.8.3) vite: 6.3.6(@types/node@24.8.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1) - '@vitest/mocker@3.2.4(msw@2.10.2(@types/node@24.8.0)(typescript@5.8.3))(vite@6.4.0(@types/node@24.8.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1))': - dependencies: - '@vitest/spy': 3.2.4 - estree-walker: 3.0.3 - magic-string: 0.30.19 - optionalDependencies: - msw: 2.10.2(@types/node@24.8.0)(typescript@5.8.3) - vite: 6.4.0(@types/node@24.8.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1) - '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 From eafeeab3ede223963d78c650268402df846bc315 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Wed, 29 Oct 2025 11:55:09 +0100 Subject: [PATCH 4/6] More updates --- .../docs/fundamentals/functions/index.mdx | 2 +- .../src/content/docs/fundamentals/tgsl.mdx | 9 +- .../content/docs/fundamentals/variables.mdx | 4 +- packages/typegpu/src/prepareDispatch.ts | 123 ------------------ 4 files changed, 8 insertions(+), 130 deletions(-) delete mode 100644 packages/typegpu/src/prepareDispatch.ts 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 f8a0b7780..494b45985 100644 --- a/apps/typegpu-docs/src/content/docs/fundamentals/functions/index.mdx +++ b/apps/typegpu-docs/src/content/docs/fundamentals/functions/index.mdx @@ -67,7 +67,7 @@ const wgsl = tgpu.resolve({ externals: { main } }); // #3) Executed on the GPU (generates WGSL underneath) root['~unstable'] - .prepareDispatch(main) + .createGuardedComputePipeline(main) .dispatchThreads(); ```` diff --git a/apps/typegpu-docs/src/content/docs/fundamentals/tgsl.mdx b/apps/typegpu-docs/src/content/docs/fundamentals/tgsl.mdx index 8789412d9..8fc72147e 100644 --- a/apps/typegpu-docs/src/content/docs/fundamentals/tgsl.mdx +++ b/apps/typegpu-docs/src/content/docs/fundamentals/tgsl.mdx @@ -33,12 +33,12 @@ behavior on the CPU and GPU, which unlocks many possibilities. There are three main ways to use kernels. ```ts twoslash -import tgpu, { prepareDispatch } from 'typegpu'; +import tgpu from 'typegpu'; import * as d from 'typegpu/data'; import * as std from 'typegpu/std'; function manhattanDistance(a: d.v3f, b: d.v3f) { - 'kernel'; + '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); @@ -52,8 +52,9 @@ const dist = manhattanDistance(d.vec3f(1, 2, 3), d.vec3f(6, 5, 4)); // #2) Executed on the GPU const root = await tgpu.init(); -prepareDispatch(root, () => { - 'kernel'; + +(root, () => { + 'use gpu'; manhattanDistance(d.vec3f(1, 2, 3), d.vec3f(6, 5, 4)); }).dispatch(); diff --git a/apps/typegpu-docs/src/content/docs/fundamentals/variables.mdx b/apps/typegpu-docs/src/content/docs/fundamentals/variables.mdx index e4dbc7692..842c6852b 100644 --- a/apps/typegpu-docs/src/content/docs/fundamentals/variables.mdx +++ b/apps/typegpu-docs/src/content/docs/fundamentals/variables.mdx @@ -62,7 +62,7 @@ const root = await tgpu.init(); const threadCounter = tgpu.privateVar(d.u32, 0); const getNext = () => { - 'kernel'; + 'use gpu'; threadCounter.$ += 1; return [42, 418, 23][threadCounter.$]; }; @@ -98,7 +98,7 @@ const boid = tgpu.const(Boid, { }); const func = () => { - 'kernel'; + 'use gpu'; const pos = boid.$; const vel = boid.$.vel; const velX = boid.$.vel.x; diff --git a/packages/typegpu/src/prepareDispatch.ts b/packages/typegpu/src/prepareDispatch.ts deleted file mode 100644 index 158de3f9e..000000000 --- a/packages/typegpu/src/prepareDispatch.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { builtin } from './builtin.ts'; -import { computeFn } from './core/function/tgpuComputeFn.ts'; -import { fn } from './core/function/tgpuFn.ts'; -import type { TgpuComputePipeline } from './core/pipeline/computePipeline.ts'; -import type { TgpuRoot } from './core/root/rootTypes.ts'; -import { u32 } from './data/numeric.ts'; -import { vec3f, vec3u } from './data/vector.ts'; -import type { v3u } from './data/wgslTypes.ts'; -import { ceil } from './std/numeric.ts'; -import type { TgpuBindGroup } from './tgpuBindGroupLayout.ts'; - -/** - * Changes the given array to a vec of 3 numbers, filling missing values with 1. - */ -function toVec3(arr: readonly (number | undefined)[]): v3u { - if (arr.includes(0)) { - throw new Error('Size and workgroupSize cannot contain zeroes.'); - } - return vec3u(arr[0] ?? 1, arr[1] ?? 1, arr[2] ?? 1); -} - -type DispatchForArgs = TArgs extends { length: infer TLength } - ? TLength extends 0 ? (() => void) - : TLength extends 1 ? ((x: number) => void) - : TLength extends 2 ? ((x: number, y: number) => void) - : TLength extends 3 ? ((x: number, y: number, z: number) => void) - : never - : never; - -class PreparedDispatch { - #pipeline: TgpuComputePipeline; - #createDispatch: (pipeline: TgpuComputePipeline) => DispatchForArgs; - constructor( - createDispatch: (pipeline: TgpuComputePipeline) => DispatchForArgs, - pipeline: TgpuComputePipeline, - ) { - this.#createDispatch = createDispatch; - this.#pipeline = pipeline; - } - - /** - * Returns a new PreparedDispatch with the specified bind group bound. - * Analogous to `TgpuComputePipeline.with(bindGroup)`. - */ - with(bindGroup: TgpuBindGroup): PreparedDispatch { - return new PreparedDispatch( - this.#createDispatch, - this.#pipeline.with(bindGroup), - ); - } - - /** - * Run the prepared dispatch. - * Unlike `TgpuComputePipeline.dispatchWorkgroups()`, - * this method takes in the number of threads to run in each dimension. - */ - get dispatch(): DispatchForArgs { - return this.#createDispatch(this.#pipeline); - } -} - -const workgroupSizeConfigs = [ - vec3u(1, 1, 1), - vec3u(256, 1, 1), - vec3u(16, 16, 1), - vec3u(8, 8, 4), -] as const; - -/** - * Creates a dispatch function for a compute pipeline. - * - * The returned function can be called multiple times to run GPU computations. - * - * @param root A TgpuRoot instance. - * @param callback A function converted to WGSL and executed on the GPU. Its arguments correspond to the global invocation IDs. - */ -export function prepareDispatch( - root: TgpuRoot, - callback: (...args: TArgs) => void, -): PreparedDispatch { - if (callback.length >= 4) { - throw new Error('Dispatch only supports up to three dimensions.'); - } - const workgroupSize = workgroupSizeConfigs[callback.length] as v3u; - const wrappedCallback = fn([u32, u32, u32])( - callback as (...args: number[]) => void, - ); - - const sizeUniform = root.createUniform(vec3u); - - // raw WGSL instead of TGSL - // because we do not run unplugin before shipping typegpu package - const mainCompute = computeFn({ - workgroupSize: workgroupSize, - in: { id: builtin.globalInvocationId }, - })`{ - if (any(in.id >= sizeUniform)) { - return; - } - wrappedCallback(in.id.x, in.id.y, in.id.z); -}`.$uses({ sizeUniform, wrappedCallback }); - - const pipeline = root['~unstable'] - .withCompute(mainCompute) - .createPipeline(); - - const createDispatch = (pipeline: TgpuComputePipeline) => - ((...size: (number | undefined)[]) => { - const sanitizedSize = toVec3(size); - const workgroupCount = ceil( - vec3f(sanitizedSize).div(vec3f(workgroupSize)), - ); - sizeUniform.write(sanitizedSize); - pipeline.dispatchWorkgroups( - workgroupCount.x, - workgroupCount.y, - workgroupCount.z, - ); - root['~unstable'].flush(); - }) as DispatchForArgs; - - return new PreparedDispatch(createDispatch, pipeline); -} From ad890a966f4f3fa63cc701346bf6f976bc64b8b1 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Wed, 29 Oct 2025 15:49:19 +0100 Subject: [PATCH 5/6] More updates --- apps/typegpu-docs/package.json | 6 +- .../src/content/docs/fundamentals/buffers.mdx | 50 ++-- .../docs/fundamentals/functions/index.mdx | 241 ++++++++++++++---- .../src/content/docs/fundamentals/tgsl.mdx | 5 +- pnpm-lock.yaml | 74 +++--- 5 files changed, 255 insertions(+), 121 deletions(-) diff --git a/apps/typegpu-docs/package.json b/apps/typegpu-docs/package.json index 692643bc9..30e144561 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 a6de95d76..9448b4608 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 494b45985..3a054c675 100644 --- a/apps/typegpu-docs/src/content/docs/fundamentals/functions/index.mdx +++ b/apps/typegpu-docs/src/content/docs/fundamentals/functions/index.mdx @@ -5,7 +5,7 @@ description: A guide on how to create and use the TypeGPU typed functions. :::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 this step. +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. @@ -91,7 +91,16 @@ You can already notice a few things about TypeGPU functions: - TS types are properly inferred, feel free to hover over the variables to see their types. - The generated code closely matches your source code. -## Type inference +### 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 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. @@ -166,7 +175,7 @@ fn main() { } ``` -You can limit the types that a function can accept by using wrapping it in a shell. +You can limit the types that a function can accept by using [wrapping it in a shell](#function-shells). ### Generics @@ -181,49 +190,148 @@ const double = (a: T): T => { }; ``` -## Bruh +### Standard library -In order to construct a TypeGPU function, you need to start by defining its 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: +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...). -- An array of schemas representing argument types, -- (Optionally) a schema representing the return type. +```ts twoslash +import * as d from 'typegpu/data'; +import * as std from 'typegpu/std'; -Then the actual WGSL implementation is passed in to a shell invocation using the tagged template literal. +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); -The following code defines a function that accepts one argument of type `f32` and returns a `vec4f`. + 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 tgpu from 'typegpu'; import * as d from 'typegpu/data'; +import * as std from 'typegpu/std'; -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 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 -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. +To avoid inlining, use [tgpu.const](/TypeGPU/fundamentals/variables#const-variables). ::: -Since type information is already present in the shell, the WGSL header can be simplified to include only the argument names. +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 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 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. +::: + +`Math.*` functions can usually be swapped with functions exported from `typegpu/std`. Calling JavaScript functions that are not marked with `'use gpu'` is not allowed. An exception to that rule is [`console.log`](/TypeGPU/fundamentals/utils#consolelog), which allows for tracking runtime behavior +of shaders in a familiar way. + +## 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. + +```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); +``` + +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'; +// ---cut--- +const neighborhood = tgpu.fn([d.f32, d.f32], d.vec2f)((a, r) => { + 'use gpu'; + return d.vec2f(a - r, a + r); +}); ``` ## Implementing functions in WGSL @@ -234,62 +342,83 @@ We assume that you are familiar with the following concepts: - WebGPU Shading Language ::: -```ts -// "shelled" function: -// Same as a "bare" function, but with restricted argument -// and return types. -// -const rangeB = tgpu.fn([d.f32, d.f32], d.vec2f)((a, b) => { - return d.vec2f(a + b, a - b).mul(multiplier); -}); +Instead of passing JavaScript functions to shells, you can pass WGSL code directly: -// "shelled wgsl" function: -// A shell defining the function's signature, and the -// implementation in WGSL. -// -const rangeC = tgpu.fn([d.f32, d.f32], d.vec2f)`(a, b) { - return vec2f(a + b, a - b) * multiplier; -} -`.$uses({ multiplier }); -// ^ Giving the function access to the 'multiplier' external value -```` +```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 +import tgpu from 'typegpu'; +import * as d from 'typegpu/data'; + +// ---cut--- +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)`() { +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 +// 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{ + +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`. + ## Entry functions :::caution[Experimental] diff --git a/apps/typegpu-docs/src/content/docs/fundamentals/tgsl.mdx b/apps/typegpu-docs/src/content/docs/fundamentals/tgsl.mdx index 8fc72147e..6842b1fcc 100644 --- a/apps/typegpu-docs/src/content/docs/fundamentals/tgsl.mdx +++ b/apps/typegpu-docs/src/content/docs/fundamentals/tgsl.mdx @@ -53,10 +53,11 @@ const dist = manhattanDistance(d.vec3f(1, 2, 3), d.vec3f(6, 5, 4)); // #2) Executed on the GPU const root = await tgpu.init(); -(root, () => { +const pipeline = root['~unstable'].createGuardedComputePipeline(() => { 'use gpu'; manhattanDistance(d.vec3f(1, 2, 3), d.vec3f(6, 5, 4)); -}).dispatch(); +}); +pipeline.dispatchThreads(); // #3) Used to generate WGSL const wgsl = tgpu.resolve({ externals: { manhattanDistance } }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index df4e9bec7..1a96e1156 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 From d7a2af31fc3153badf9c4c5d652e514f958c3747 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Wed, 29 Oct 2025 16:06:48 +0100 Subject: [PATCH 6/6] MORE --- apps/typegpu-docs/astro.config.mjs | 5 - .../docs/fundamentals/functions/index.mdx | 29 +- .../src/content/docs/fundamentals/tgsl.mdx | 266 ------------------ 3 files changed, 26 insertions(+), 274 deletions(-) delete mode 100644 apps/typegpu-docs/src/content/docs/fundamentals/tgsl.mdx diff --git a/apps/typegpu-docs/astro.config.mjs b/apps/typegpu-docs/astro.config.mjs index 0915e4ab5..f9bfb1415 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: 'JS Kernels', - slug: 'fundamentals/tgsl', - badge: { text: 'new' }, - }, { label: 'Pipelines', slug: 'fundamentals/pipelines', 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 3a054c675..dc7748dc0 100644 --- a/apps/typegpu-docs/src/content/docs/fundamentals/functions/index.mdx +++ b/apps/typegpu-docs/src/content/docs/fundamentals/functions/index.mdx @@ -93,7 +93,7 @@ You can already notice a few things about TypeGPU functions: ### 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 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. +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). @@ -179,6 +179,9 @@ You can limit the types that a function can accept by using [wrapping it in a sh ### 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'; @@ -293,12 +296,21 @@ You can generally assume that all JavaScript syntax is supported, and in the occ 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. +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. ::: -`Math.*` functions can usually be swapped with functions exported from `typegpu/std`. Calling JavaScript functions that are not marked with `'use gpu'` is not allowed. An exception to that rule is [`console.log`](/TypeGPU/fundamentals/utils#consolelog), which allows for tracking runtime behavior +* **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. @@ -419,6 +431,17 @@ fn getGradientColor_0(ratio: f32) -> vec4f { 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] 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 6842b1fcc..000000000 --- a/apps/typegpu-docs/src/content/docs/fundamentals/tgsl.mdx +++ /dev/null @@ -1,266 +0,0 @@ ---- -title: TS Kernels -description: Guide on using JavaScript for WebGPU function definitions. ---- - -:::caution[Experimental] -This feature is under heavy development and is yet to reach stability. -::: - -:::note[Requires unplugin-typegpu] -To use TS kernels, you need to install and configure [unplugin-typegpu](/TypeGPU/tooling/unplugin-typegpu). -::: - -**TS Kernels** are functions marked with the `'kernel'` directive, picked up by our dedicated build plugin -- [unplugin-typegpu](/TypeGPU/tooling/unplugin-typegpu) and transformed into a format TypeGPU can understand. - -```ts twoslash -import * as d from 'typegpu/data'; -import * as std from 'typegpu/std'; - -function manhattanDistance(a: d.v3f, b: d.v3f) { - 'tgpu'; - 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)); -} -``` - -TypeGPU already provides a set of standard kernels under `typegpu/std`, which you can use in your own kernels. Our goal is for all kernels to have matching -behavior on the CPU and GPU, which unlocks many possibilities. - -There are three main ways to use kernels. - -```ts twoslash -import tgpu from 'typegpu'; -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)); -} - -// ---cut--- -// #1) Can be called in JS -const dist = manhattanDistance(d.vec3f(1, 2, 3), d.vec3f(6, 5, 4)); - -// #2) Executed on the GPU -const root = await tgpu.init(); - -const pipeline = root['~unstable'].createGuardedComputePipeline(() => { - 'use gpu'; - manhattanDistance(d.vec3f(1, 2, 3), d.vec3f(6, 5, 4)); -}); -pipeline.dispatchThreads(); - -// #3) Used to generate WGSL -const wgsl = tgpu.resolve({ externals: { manhattanDistance } }); -```` - -The contents of the `wgsl` variable will be the following: - -```wgsl -// Generated WGSL -fn manhattanDistance(a: vec3f, b: vec3f) -> f32 { - let dx = abs(a.x - b.x); - let dy = abs(a.y - b.y); - let dz = abs(a.z - b.z); - - return max(dx, max(dy, dz)); -} -```` - -You can already notice a few things about TS Kernels: -- 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. - -## Anatomy of a kernel - -:::note -TS kernels are picked up by `unplugin-typegpu` during bundling, their AST is compacted into our custom format called [tinyest](https://www.npmjs.com/package/tinyest) and injected -into the user code. This metadata is then used to generate equivalent WGSL at runtime efficiently. -::: - -:::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). -::: - -## 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.