GPU programs usually involve sharing data between the host (CPU) and the device (GPU).
In the case of WebGPU, even though data is strongly typed in WGSL shaders, the communication between JavaScript and WGSL involves reading raw bytes from buffers.
Any misalignments or misinterpretations of data can lead to faulty code that is difficult to debug.
This is precisely what TypeGPU data schemas, like d.u32 or d.vec3f, help with.
The primary usage for data schemas is to define buffer and function signatures.
Schemas can also be called in JS or TGSL to create instances of the types they represent.
TypeGPU provides data schemas for scalar, vector and matrix types, as well as constructors for struct and array schemas.
For scalar WGSL types f32, u32, i32, f16 and bool, TypeGPU provides d.f32, d.u32, d.i32, d.f16 and d.bool schemas respectively.
Schemas can be called to cast a value to a given type.
This works both in TGSL, and in plain JavaScript.
import d
d.
functionu32(v?:number|boolean):number
export u32
A schema that represents an unsigned 32-bit integer value. (equivalent to u32 in WGSL)
Can also be called to cast a value to an u32 in accordance with WGSL casting rules.
@exampleconst value = u32(); // 0
@exampleconst value = u32(7); // 7
@exampleconst value = u32(3.14); // 3
@exampleconst value = u32(-1); // 4294967295
@exampleconst value = u32(-3.1); // 0
u32(31.1); // 31
import d
d.
functionu32(v?:number|boolean):number
export u32
A schema that represents an unsigned 32-bit integer value. (equivalent to u32 in WGSL)
Can also be called to cast a value to an u32 in accordance with WGSL casting rules.
@exampleconst value = u32(); // 0
@exampleconst value = u32(7); // 7
@exampleconst value = u32(3.14); // 3
@exampleconst value = u32(-1); // 4294967295
@exampleconst value = u32(-3.1); // 0
u32(-1); // 4294967295
import d
d.
functionf32(v?:number|boolean):number
export f32
A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)
Can also be called to cast a value to an f32.
@exampleconst value = f32(); // 0
@exampleconst value = f32(1.23); // 1.23
@exampleconst value = f32(true); // 1
f32(true); // 1
import d
d.
functioni32(v?:number|boolean):number
export i32
A schema that represents a signed 32-bit integer value. (equivalent to i32 in WGSL)
Can also be called to cast a value to an i32 in accordance with WGSL casting rules.
@exampleconst value = i32(); // 0
@exampleconst value = i32(3.14); // 3
@exampleconst value = i32(-3.9); // -3
@exampleconst value = i32(10000000000) // 1410065408
i32(); // 0 (default value)
import d
d.
functionbool(v?:number|boolean):boolean
export bool
A schema that represents a boolean value. (equivalent to bool in WGSL)
Can also be called to cast a value to a bool in accordance with WGSL casting rules.
@exampleconst value = bool(); // false
@exampleconst value = bool(0); // false
@exampleconst value = bool(-0); // false
@exampleconst value = bool(21.37); // true
bool(0.01); // true
For each of the scalar types, there is a vector schema of 2, 3 and 4 elements of that type.
For vector WGSL types vec2f, vec2u, vec2i, vec2h and vec2<bool>, TypeGPU provides d.vec2f, d.vec2u, d.vec2i, d.vec2h and d.vec2b schemas respectively. Analogously for 3 and 4 components.
Just like scalar schemas, vector schemas can be called to create values of corresponding types.
@exampleconst buffer = root.createBuffer(d.vec4f, d.vec4f(0, 1, 2, 3)); // buffer holding a d.vec4f value, with an initial value of vec4f(0, 1, 2, 3);
vec4f(0,
const v3: d.v2f
v3, 3); // a vector of four elements [0, 1, 2, 3]
Matrices work similarly.
For matrix WGSL types mat2x2f, mat3x3f and mat4x4f, TypeGPU provides d.mat2x2f, d.mat3x3f and d.mat4x4f schemas respectively.
Matrices of other sizes, as well as matrices of f16, are currently not supported by TypeGPU.
const mat1 =
import d
d.
functionmat2x2f(): d.m2x2f (+2overloads)
export mat2x2f
Schema representing mat2x2f - a matrix with 2 rows and 2 columns, with elements of type f32.
Also a constructor function for this matrix type.
@exampleconst zero2x2 = mat2x2f(); // filled with zeros
@exampleconst buffer = root.createBuffer(d.mat2x2f, d.mat2x2f(0, 1, 2, 3)); // buffer holding a d.mat2x2f value, with an initial value of ((0, 1), (2, 3))
@exampleconst buffer = root.createBuffer(d.mat3x3f, d.mat3x3f()); // buffer holding a d.mat3x3f value, with an initial value of mat3x3f filled with zeros
@exampleconst buffer = root.createBuffer(d.mat3x3f, d.mat3x3f()); // buffer holding a d.mat3x3f value, with an initial value of mat3x3f filled with zeros
Here is an example of defining a custom compound data type using the d.struct function.
import*as
import d
dfrom'typegpu/data';
const
const Circle: d.WgslStruct<{
centerPos:d.Vec3i;
radius:d.F32;
}>
Circle =
import d
d.
struct<{
centerPos:d.Vec3i;
radius:d.F32;
}>(props: {
centerPos: d.Vec3i;
radius: d.F32;
}): d.WgslStruct<{
centerPos:d.Vec3i;
radius:d.F32;
}>
export struct
Creates a struct schema that can be used to construct GPU buffers.
Ensures proper alignment and padding of properties (as opposed to a d.unstruct schema).
The order of members matches the passed in properties object.
@exampleconst buffer = root.createBuffer(d.vec3i, d.vec3i(0, 1, 2)); // buffer holding a d.vec3i value, with an initial value of vec3i(0, 1, 2);
vec3i(2, 4, 0),
radius: number
radius: 0.2,
});
If you’re familiar with Zod, then this style of schema definitions may already seem familiar.
We define the Circle struct, similarly to how we would in WGSL, and then we can use it to validate and cast our data values.
When reading from or writing data to the GPU, the type of the JavaScript value is inferred automatically.
@exampleconst buffer = root.createBuffer(d.vec3i, d.vec3i(0, 1, 2)); // buffer holding a d.vec3i value, with an initial value of vec3i(0, 1, 2);
vec3i(2, 4, 0),
radius: "0.2",
Error ts(2322) ― Type 'string' is not assignable to type 'number'.
});
const
const redCircle3: {
centerPos: d.v3i;
radius:number;
}
redCircle3 =
const Circle: d.WgslStruct
(props: {
centerPos: d.v3i;
radius:number;
})=> {
centerPos: d.v3i;
radius: number;
} (+1 overload)
Circle({
Error ts(2345) ― Argument of type '{ centerPos: d.v3i; }' is not assignable to parameter of type '{ centerPos: v3i; radius: number; }'.
Property 'radius' is missing in type '{ centerPos: d.v3i; }' but required in type '{ centerPos: v3i; radius: number; }'.
@exampleconst buffer = root.createBuffer(d.vec4f, d.vec4f(0, 1, 2, 3)); // buffer holding a d.vec4f value, with an initial value of vec4f(0, 1, 2, 3);
vec4f(1.0, 0.2, 0.3, 1.0),
isAct
isAct: any
isActive
});
Structs can also be nested.
const
const FishState: d.WgslStruct<{
pos:d.Vec3f;
vel:d.Vec3f;
}>
FishState =
import d
d.
struct<{
pos:d.Vec3f;
vel:d.Vec3f;
}>(props: {
pos: d.Vec3f;
vel: d.Vec3f;
}): d.WgslStruct<{
pos:d.Vec3f;
vel:d.Vec3f;
}>
export struct
Creates a struct schema that can be used to construct GPU buffers.
Ensures proper alignment and padding of properties (as opposed to a d.unstruct schema).
The order of members matches the passed in properties object.
@exampleconst buffer = root.createBuffer(d.vec3f, d.vec3f(0, 1, 2)); // buffer holding a d.vec3f value, with an initial value of vec3f(0, 1, 2);
vec3f,
});
const
const Fish: d.WgslStruct<{
kind:d.U32;
state:d.WgslStruct<{
pos:d.Vec3f;
vel:d.Vec3f;
}>;
}>
Fish =
import d
d.
struct<{
kind:d.U32;
state:d.WgslStruct<{
pos:d.Vec3f;
vel:d.Vec3f;
}>;
}>(props: {
kind: d.U32;
state: d.WgslStruct<{
pos:d.Vec3f;
vel:d.Vec3f;
}>;
}): d.WgslStruct<{
kind:d.U32;
state:d.WgslStruct<{
pos:d.Vec3f;
vel:d.Vec3f;
}>;
}>
export struct
Creates a struct schema that can be used to construct GPU buffers.
Ensures proper alignment and padding of properties (as opposed to a d.unstruct schema).
The order of members matches the passed in properties object.
@exampleconst buffer = root.createBuffer(d.vec3f, d.vec3f(0, 1, 2)); // buffer holding a d.vec3f value, with an initial value of vec3f(0, 1, 2);
vec3f() } });
const instance: {
kind:number;
state: {
pos: d.v3f;
vel: d.v3f;
};
}
If you wish to extract the JS type equivalent of a TypeGPU schema, you can do so with d.Infer:
type u32 =
import d
d.
type Infer<T> =Textends {
readonly [$repr]:inferTRepr;
} ?TRepr:T
export Infer
Extracts the inferred representation of a resource.
For inferring types as seen by the GPU, see
InferGPU
@exampletype A = Infer // => number
type B = Infer<WgslArray> // => number[]
type C = Infer<Atomic> // => number
Infer<typeof
import d
d.
const u32: d.U32
export u32
A schema that represents an unsigned 32-bit integer value. (equivalent to u32 in WGSL)
Can also be called to cast a value to an u32 in accordance with WGSL casting rules.
@exampleconst value = u32(); // 0
@exampleconst value = u32(7); // 7
@exampleconst value = u32(3.14); // 3
@exampleconst value = u32(-1); // 4294967295
@exampleconst value = u32(-3.1); // 0
u32>;
type u32 =number
type Circle =
import d
d.
type Infer<T> =Textends {
readonly [$repr]:inferTRepr;
} ?TRepr:T
export Infer
Extracts the inferred representation of a resource.
For inferring types as seen by the GPU, see
InferGPU
@exampletype A = Infer // => number
type B = Infer<WgslArray> // => number[]
type C = Infer<Atomic> // => number
Infer<typeof
const Circle: d.WgslStruct<{
centerPos:d.Vec3i;
radius:d.F32;
}>
Circle>;
type Circle = {
centerPos: d.v3i;
radius:number;
}
Defined data structures automatically measure and hold information about their memory layout parameters.
import d
d.
functionsizeOf(schema: d.AnyData):number
export sizeOf
Returns the size (in bytes) of data represented by the schema.
sizeOf(
const Circle: d.WgslStruct<{
centerPos:d.Vec3i;
radius:d.F32;
}>
Circle) // 16
import d
d.
functionalignmentOf(schema: d.AnyData):number
export alignmentOf
Returns the alignment (in bytes) of data represented by the schema.
alignmentOf(
const Circle: d.WgslStruct<{
centerPos:d.Vec3i;
radius:d.F32;
}>
Circle) // 16
Struct schemas adjust the padding and alignment automatically, so that they comply with WebGPU’s memory alignment rules.
It is also possible to override default byte alignment and size for particular fields via the d.align and d.size functions.
const
const Boid: d.WgslStruct<{
position:d.Decorated<d.Vec3u, [d.Align<32>]>;
velocity:d.Vec3f;
color:d.Vec4f;
isActive:d.Decorated<d.Bool, [d.Size<8>]>;
}>
Boid =
import d
d.
struct<{
position:d.Decorated<d.Vec3u, [d.Align<32>]>;
velocity:d.Vec3f;
color:d.Vec4f;
isActive:d.Decorated<d.Bool, [d.Size<8>]>;
}>(props: {
position: d.Decorated<d.Vec3u, [d.Align<32>]>;
velocity: d.Vec3f;
color: d.Vec4f;
isActive: d.Decorated<d.Bool, [d.Size<8>]>;
}): d.WgslStruct<...>
export struct
Creates a struct schema that can be used to construct GPU buffers.
Ensures proper alignment and padding of properties (as opposed to a d.unstruct schema).
The order of members matches the passed in properties object.
Gives the wrapped data-type a custom byte alignment. Useful in order to
fulfill uniform alignment requirements.
@exampleconst Data = d.struct({
a: u32, // takes up 4 bytes
// 12 bytes of padding, because b is custom aligned to multiples of 16 bytes
b: d.align(16, u32),
});
@param ― alignment The multiple of bytes this data should align itself to.
@param ― data The data-type to align.
align(32,
import d
d.
const vec3u: d.Vec3u
export vec3u
Schema representing vec3u - a vector with 3 elements of type u32.
Also a constructor function for this vector value.
You can also call d.arrayOf without specifying the array length. This returns a partially applied schema function, allowing you to provide the length later or use the schema to declare WGSL runtime-sized arrays.
One of the biggest differences between JavaScript and WGSL is the existence of value/reference types.
Let’s take a look at a simple TGSL function and the WGSL code it resolves to.
// TGSL
const
const MyStruct: d.WgslStruct<{
n:d.U32;
}>
MyStruct =
import d
d.
struct<{
n:d.U32;
}>(props: {
n: d.U32;
}): d.WgslStruct<{
n:d.U32;
}>
export struct
Creates a struct schema that can be used to construct GPU buffers.
Ensures proper alignment and padding of properties (as opposed to a d.unstruct schema).
The order of members matches the passed in properties object.
On the JS side, s2 is a reference to s1 and therefore s1 is modified when s2 is modified.
On the WGSL side, the assignment operator copies the value of s1, and thus s1 is not modified later on.
To help with this issue, schema calls can be used as “copy constructors”, that copy the value on the JS side, and disappear on the WGSL side:
// TGSL
const
const MyStruct: d.WgslStruct<{
n:d.U32;
}>
MyStruct =
import d
d.
struct<{
n:d.U32;
}>(props: {
n: d.U32;
}): d.WgslStruct<{
n:d.U32;
}>
export struct
Creates a struct schema that can be used to construct GPU buffers.
Ensures proper alignment and padding of properties (as opposed to a d.unstruct schema).
The order of members matches the passed in properties object.