Skip to content

Data Schemas

Writing a GPU program usually involves sharing data between the host (CPU) and the device (GPU), in this case between JavaScript and WGSL. Any misalignments or misinterpretations of data can lead to bugs that are hard to debug (No console.log on the GPU, I am afraid). While data is strongly typed in WGSL shaders, we give up that type safety completely when writing and reading data in JavaScript/TypeScript. This is precisely what TypeGPU data types help with.

Examples

Let’s look at some examples of defining custom data types using the typegpu/data module. If you’re familiar with Zod, then this style of schema definitions may already seem familiar.

import { struct, f32, vec3i, Parsed } from 'typegpu/data';
const Circle = struct({
centerPos: vec3i,
radius: f32,
});
// type Circle = {
// centerPos: vec3i;
// radius: number;
// }
type Circle = Parsed<typeof Circle>;
const redCircle: Circle = {
centerPos: vec3i(2, 4, 0),
radius: 0.2,
};

By defining the Circle struct in TypeScript via TypeGPU, in a similar way to how we would in WGSL, we gain access to its TypeScript type definition, which we can use to validate our data values. When reading from or writing data to the GPU, the type of the JavaScript value is inferred automatically, and it’s enforced by TypeScript. Thanks to that, whenever we mistakenly set or assume a wrong value for an object, we get a type error, avoiding unnecessary debugging afterwards. That’s a big improvement to the development process.

const redCircle: Circle = {
// Error: Property 'z' is missing in type 'vec2i' but required in type 'vec3i'.
centerPos: vec2i(2, 4),
radius: 0.2,
};
const redCircle: Circle = {
centerPos: vec3i(2, 4, 0),
// Error: Type 'string' is not assignable to type 'number'.
radius: "0.2",
};
// Error: Property 'radius' is missing in type '{ centerPos: vec3i; }'
// but required in type '{ centerPos: vec3i; radius: number; }'.
const redCircle: Circle = {
centerPos: vec3i(2, 4, 0),
};
// Error: Property 'rad' does not exist on type '{ centerPos: vec3i; radius: number; }'.
const diam = redCircle.rad * 2;

Defined data structures automatically measure and hold information about their memory layout parameters, which is useful for writing to and reading data from the GPU.

Circle.size // 16
Circle.byteAlignment // 16

TypeGPU data types are essential for the library’s automated data marshalling capabilities. You can read more about it in the chapter dedicated to tgpu.write.

Built-in primitive data types

SchemaJavaScriptWGSL
import { f32 } from 'typegpu/data';
numberf32
import { i32 } from 'typegpu/data';
numberi32
import { u32 } from 'typegpu/data';
numberu32
import { bool } from 'typegpu/data';
booleanbool

Vector and matrix types

SchemaValue constructorsWGSL equivalents
vec2u
  • vec2u(x: number, y: number)
  • vec2u(xy: number)
  • vec2u()
vec2u, vec2<u32>
vec2f
  • vec2f(x: number, y: number)
  • vec2f(xy: number)
  • vec2f()
vec2f, vec2<f32>
vec2i
  • vec2i(x: number, y: number)
  • vec2i(xy: number)
  • vec2i()
vec2i, vec2<i32>
vec3u
  • vec3u(x: number, y: number, z: number)
  • vec3u(xyz: number)
  • vec3u()
vec3u, vec3<u32>
vec3f
  • vec3f(x: number, y: number, z: number)
  • vec3f(xyz: number)
  • vec3f()
vec3f, vec3<f32>
vec3i
  • vec3i(x: number, y: number, z: number)
  • vec3i(xyz: number)
  • vec3i()
vec3i, vec3<i32>
vec4u
  • vec4u(x: number, y: number, z: number, w: number)
  • vec4u(xyzw: number)
  • vec4u()
vec4u, vec4<u32>
vec4f
  • vec4f(x: number, y: number, z: number, w: number)
  • vec4f(xyzw: number)
  • vec4f()
vec4f, vec4<f32>
vec4i
  • vec4i(x: number, y: number, z: number, w: number)
  • vec4i(xyzw: number)
  • vec4i()
vec4i, vec4<i32>
mat2x2f
  • mat2x2f(...elements: number[])
  • mat2x2f(...columns: vec2f[])
  • mat2x2f()
mat2x2f, mat2x2<f32>
mat3x3f
  • mat3x3f(...elements: number[])
  • mat3x3f(...columns: vec3f[])
  • mat3x3f()
mat3x3f, mat3x3<f32>
mat4x4f
  • mat4x4f(...elements: number[])
  • mat4x4f(...columns: vec4f[])
  • mat4x4f()
mat4x4f, mat4x4<f32>

Complex data structures

struct

import { struct, vec3u, vec3f, vec4f, bool, Parsed } from 'typegpu/data';
const Boid = struct({
position: vec3u,
velocity: vec3f,
color: vec4f,
isActive: bool,
});
const boid: Parsed<typeof Boid> = {
position: vec3u(0, 0, 0),
velocity: vec3f(1, 0.5, 0.5),
color: vec4f(1.0, 0.2, 0.3, 1.0),
isActive: true,
};

Each struct has its byteAlignment equal to the biggest byteAlignment of its properties. It is also possible to override default byte alignment and size for particular fields via the align and size functions.

import { struct, vec3u, vec3f, vec4f, bool, align, size } from 'typegpu/data';
const Boid = struct({
position: align(32, vec3u),
velocity: vec3f,
color: vec4f,
isActive: size(8, bool),
});

arrayOf

To define arrays of known constant length, use the arrayOf function. It accepts as arguments the array’s elements data type constructor and the length of the array.

import { arrayOf, f32, Parsed } from 'typegpu/data';
const RecentResultsArray = arrayOf(f32, 4);
// evaluated to number[];
type RecentResultsArray = Parsed<typeof RecentResultsArray>;
const recentResults: RecentResultsArray = [
1, 0, 0.5, 20
];
recentResults.shift();
recentResults[3] = -1;