Skip to content

WESL Interoperability

We are working with WESL, a community standard for enhanced WGSL, to enable hybrid programs that mix-and-match between shader-centric and host-centric approaches. Shaders written in WGSL or WESL can be reflected into JS/TS as TypeGPU definitions, with proper types generated on the fly.

  • ✨ Take advantage of type-safe buffers while keeping your shaders in WESL
  • ⚔️ Eliminate manual byte alignment and padding

Setting up

This functionality is provided as an extension to wesl-plugin. Consult their documentation on what to install, and how to use it with your bundler of choice.

Our official wesl-ext-typepgu package extends the capabilities of wesl-plugin. Install it before proceeding.

npm add --save-dev wesl-ext-typegpu

Next up, reference the extension in your bundler’s configuration. Below is an example using Vite.

import { defineConfig } from "vite";
import weslPlugin from "wesl-plugin/vite";
import { linkBuildExtension } from "wesl-plugin";
import { typegpuExtension } from "wesl-ext-typegpu";
export default defineConfig({
plugins: [weslPlugin({ extensions: [linkBuildExtension, typegpuExtension] })],
});

And finally, to let the TypeScript language server know where to look for typing of the .wgsl/.wesl you’re importing, change the following in your tsconfig.json:

{
// ...
"include": [/* all other files you're including */, ".wesl/**/*"]
// ...
}

Reflection

Let’s say we have to following shader program, split across two files.

shaders/shared.wesl
struct BoidState {
position: vec3f,
velocity: vec3f,
}
struct Fish {
kind: u32,
state: BoidState,
}
shaders/main.wesl
use package::shared::Fish;
@group(0) @binding(0) var<storage, read_write> fish: array<Fish>;
@compute @workgroup_size(32)
fn main() {
// ...
}

Given a shader written in WGSL/WESL, we can use the ?typegpu query parameter to import reified references to any struct definition.

main.ts
// Importing a WGSL struct into JS
import {
import Fish
Fish
} from './shaders/shared.wesl?typegpu';
const
const FishArray: (n: number) => d.WgslArray<d.WgslStruct<{
kind: d.U32;
state: d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>;
}>>
FishArray
= (
n: number
n
: number) =>
import d
d
.
arrayOf<d.WgslStruct<{
kind: d.U32;
state: d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>;
}>>(elementType: d.WgslStruct<{
kind: d.U32;
state: d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>;
}>, elementCount: number): d.WgslArray<...>
export arrayOf

Creates an array schema that can be used to construct gpu buffers. Describes arrays with fixed-size length, storing elements of the same type.

@example const LENGTH = 3; const array = d.arrayOf(d.u32, LENGTH);

@paramelementType The type of elements in the array.

@paramelementCount The number of elements in the array.

arrayOf
(
const Fish: d.WgslStruct<{
kind: d.U32;
state: d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>;
}>
Fish
,
n: number
n
);
const buffer =
const root: TgpuRoot
root
.
TgpuRoot.createBuffer<d.WgslArray<d.WgslStruct<{
kind: d.U32;
state: d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>;
}>>>(typeSchema: d.WgslArray<d.WgslStruct<{
kind: d.U32;
state: d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>;
}>>, initial?: {
...;
}[] | undefined): TgpuBuffer<...> (+1 overload)

Allocates memory on the GPU, allows passing data between host and shader.

@paramtypeSchema The type of data that this buffer will hold.

@paraminitial The initial value of the buffer. (optional)

createBuffer
(
const FishArray: (n: number) => d.WgslArray<d.WgslStruct<{
kind: d.U32;
state: d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>;
}>>
FishArray
(512)).
TgpuBuffer<WgslArray<WgslStruct<{ kind: U32; state: WgslStruct<{ position: Vec3f; velocity: Vec3f; }>; }>>>.$usage<["storage"]>(usages_0: "storage"): TgpuBuffer<d.WgslArray<d.WgslStruct<{
kind: d.U32;
state: d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>;
}>>> & StorageFlag
$usage
('storage');
const buffer: TgpuBuffer<d.WgslArray<d.WgslStruct<{
kind: d.U32;
state: d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>;
}>>> & StorageFlag
// Updating the 123rd fish's position
const buffer: TgpuBuffer<d.WgslArray<d.WgslStruct<{
kind: d.U32;
state: d.WgslStruct<{
position: d.Vec3f;
velocity: d.Vec3f;
}>;
}>>> & StorageFlag
buffer
.
TgpuBuffer<WgslArray<WgslStruct<{ kind: U32; state: WgslStruct<{ position: Vec3f; velocity: Vec3f; }>; }>>>.writePartial(data: {
idx: number;
value: {
kind?: number | undefined;
state?: {
position?: d.v3f | undefined;
velocity?: d.v3f | undefined;
};
};
}[]): void
writePartial
([
{
idx: number
idx
: 123,
value: {
kind?: number | undefined;
state?: {
position?: d.v3f | undefined;
velocity?: d.v3f | undefined;
};
}
value
: {
state?: {
position?: d.v3f | undefined;
velocity?: d.v3f | undefined;
}
state
: {
posit
posit: any
position
},
}
}
]);