Skip to content

Functions

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. Imported functions from external sources are automatically resolved and embedded into the final shader when referenced.

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, objects that represent WGSL data types and assist in generating shader code at runtime. It accepts two arguments:

  • An array of schemas representing argument types,
  • (Optionally) a schema representing the return type.

Then the actual WGSL implementation is passed in to a shell invocation using the tagged template literal.

The following code defines a function that accepts one argument of type f32 and returns a vec4f.

import
const tgpu: {
fn: {
<Args extends d.AnyData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>;
<Args extends d.AnyData[] | [], Return extends d.AnyData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>;
};
... 7 more ...;
'~unstable': {
...;
};
}
tgpu
from 'typegpu';
import * as
import d
d
from 'typegpu/data';
const
const getGradientColor: TgpuFn<(args_0: d.F32) => d.Vec4f>
getGradientColor
=
const tgpu: {
fn: {
<Args extends d.AnyData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>;
<Args extends d.AnyData[] | [], Return extends d.AnyData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>;
};
... 7 more ...;
'~unstable': {
...;
};
}
tgpu
.
fn: <[d.F32], d.Vec4f>(argTypes: [d.F32], returnType: d.Vec4f) => TgpuFnShell<[d.F32], d.Vec4f> (+1 overload)
fn
(
[
import d
d
.
const f32: d.F32
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.

@example const value = f32(); // 0

@example const value = f32(1.23); // 1.23

@example const value = f32(true); // 1

f32
],
import d
d
.
const vec4f: d.Vec4f
export vec4f

Schema representing vec4f - a vector with 4 elements of type f32. Also a constructor function for this vector value.

@example const vector = d.vec4f(); // (0.0, 0.0, 0.0, 0.0) const vector = d.vec4f(1); // (1.0, 1.0, 1.0, 1.0) const vector = d.vec4f(1, 2, 3, 4.5); // (1.0, 2.0, 3.0, 4.5)

@example const 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
) /* 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);
}`;

Since type information is already present in the shell, the WGSL header can be simplified to include only the argument names.

const
const getGradientColor: TgpuFn<(args_0: d.F32) => d.Vec4f>
getGradientColor
=
const tgpu: {
fn: {
<Args extends d.AnyData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>;
<Args extends d.AnyData[] | [], Return extends d.AnyData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>;
};
... 7 more ...;
'~unstable': {
...;
};
}
tgpu
.
fn: <[d.F32], d.Vec4f>(argTypes: [d.F32], returnType: d.Vec4f) => TgpuFnShell<[d.F32], d.Vec4f> (+1 overload)
fn
([
import d
d
.
const f32: d.F32
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.

@example const value = f32(); // 0

@example const value = f32(1.23); // 1.23

@example const value = f32(true); // 1

f32
],
import d
d
.
const vec4f: d.Vec4f
export vec4f

Schema representing vec4f - a vector with 4 elements of type f32. Also a constructor function for this vector value.

@example const vector = d.vec4f(); // (0.0, 0.0, 0.0, 0.0) const vector = d.vec4f(1); // (1.0, 1.0, 1.0, 1.0) const vector = d.vec4f(1, 2, 3, 4.5); // (1.0, 2.0, 3.0, 4.5)

@example const 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
) /* 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);
}`;

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.).

const
const getBlueFunction: TgpuFn<() => d.Vec4f>
getBlueFunction
=
const tgpu: {
fn: {
<Args extends d.AnyData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>;
<Args extends d.AnyData[] | [], Return extends d.AnyData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>;
};
... 7 more ...;
'~unstable': {
...;
};
}
tgpu
.
fn: <[], d.Vec4f>(argTypes: [], returnType: d.Vec4f) => TgpuFnShell<[], d.Vec4f> (+1 overload)
fn
([],
import d
d
.
const vec4f: d.Vec4f
export vec4f

Schema representing vec4f - a vector with 4 elements of type f32. Also a constructor function for this vector value.

@example const vector = d.vec4f(); // (0.0, 0.0, 0.0, 0.0) const vector = d.vec4f(1); // (1.0, 1.0, 1.0, 1.0) const vector = d.vec4f(1, 2, 3, 4.5); // (1.0, 2.0, 3.0, 4.5)

@example const 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
)`() {
return vec4f(0.114, 0.447, 0.941, 1);
}`;
// calling a schema to create a value on the JS side
const
const purple: d.v4f
purple
=
import d
d
.
function vec4f(x: number, y: number, z: number, w: number): d.v4f (+9 overloads)
export vec4f

Schema representing vec4f - a vector with 4 elements of type f32. Also a constructor function for this vector value.

@example const vector = d.vec4f(); // (0.0, 0.0, 0.0, 0.0) const vector = d.vec4f(1); // (1.0, 1.0, 1.0, 1.0) const vector = d.vec4f(1, 2, 3, 4.5); // (1.0, 2.0, 3.0, 4.5)

@example const 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.769, 0.392, 1.0, 1);
const
const getGradientColor: TgpuFn<(args_0: d.F32) => d.Vec4f>
getGradientColor
=
const tgpu: {
fn: {
<Args extends d.AnyData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>;
<Args extends d.AnyData[] | [], Return extends d.AnyData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>;
};
... 7 more ...;
'~unstable': {
...;
};
}
tgpu
.
fn: <[d.F32], d.Vec4f>(argTypes: [d.F32], returnType: d.Vec4f) => TgpuFnShell<[d.F32], d.Vec4f> (+1 overload)
fn
([
import d
d
.
const f32: d.F32
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.

@example const value = f32(); // 0

@example const value = f32(1.23); // 1.23

@example const value = f32(true); // 1

f32
],
import d
d
.
const vec4f: d.Vec4f
export vec4f

Schema representing vec4f - a vector with 4 elements of type f32. Also a constructor function for this vector value.

@example const vector = d.vec4f(); // (0.0, 0.0, 0.0, 0.0) const vector = d.vec4f(1); // (1.0, 1.0, 1.0, 1.0) const vector = d.vec4f(1, 2, 3, 4.5); // (1.0, 2.0, 3.0, 4.5)

@example const 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
)`(ratio) {
return mix(purple, getBlue(), ratio);;
}
`.
TgpuFnBase<(args_0: F32) => Vec4f>.$uses(dependencyMap: Record<string, unknown>): TgpuFn<(args_0: d.F32) => d.Vec4f>
$uses
({
purple: d.v4f
purple
,
getBlue: TgpuFn<() => d.Vec4f>
getBlue
:
const getBlueFunction: TgpuFn<() => d.Vec4f>
getBlueFunction
});

You can check yourself what getGradientColor resolves to by calling tgpu.resolve, all relevant definitions will be automatically included:

// results of calling tgpu.resolve({ externals: { getGradientColor } })
fn getBlueFunction_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);;
}

Instead of annotating a TgpuFn with attributes, entry functions are defined using dedicated shell constructors:

  • tgpu['~unstable'].computeFn,
  • tgpu['~unstable'].vertexFn,
  • tgpu['~unstable'].fragmentFn.

To describe the input and output of an entry point function, we use IORecords, JavaScript objects that map argument names to their types.

const vertexInput = {
idx: d.builtin.vertexIndex,
position: d.vec4f,
color: d.vec4f
}

As you may note, builtin inter-stage inputs and outputs are available on the d.builtin object, 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. 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 IORecords. In WGSL implementation, input and output structs of the given function can be referenced as In and Out respectively. Headers in WGSL implementations must be omitted, all input values are accessible through the struct named in.

TgpuComputeFn accepts an object with two properties:

  • in — an IORecord describing the input of the function,
  • workgroupSize — a JS array of 1-3 numbers that corresponds to the @workgroup_size attribute.
const
const mainCompute: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
mainCompute
=
const tgpu: {
fn: {
<Args extends d.AnyData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>;
<Args extends d.AnyData[] | [], Return extends d.AnyData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>;
};
... 7 more ...;
'~unstable': {
...;
};
}
tgpu
['~unstable'].
computeFn: <{
gid: d.BuiltinGlobalInvocationId;
}>(options: {
in: {
gid: d.BuiltinGlobalInvocationId;
};
workgroupSize: number[];
}) => TgpuComputeFnShell<{
gid: d.BuiltinGlobalInvocationId;
}> (+1 overload)
computeFn
({
in: {
gid: d.BuiltinGlobalInvocationId;
}
in
: {
gid: d.BuiltinGlobalInvocationId
gid
:
import d
d
.
const builtin: {
readonly vertexIndex: d.BuiltinVertexIndex;
readonly instanceIndex: d.BuiltinInstanceIndex;
readonly position: d.BuiltinPosition;
readonly clipDistances: d.BuiltinClipDistances;
... 10 more ...;
readonly subgroupSize: BuiltinSubgroupSize;
}
export builtin
builtin
.
globalInvocationId: d.BuiltinGlobalInvocationId
globalInvocationId
},
workgroupSize: number[]
workgroupSize
: [1],
}) /* wgsl */`{
let index = in.gid.x;
if index == 0 {
time += deltaTime;
}
let phase = (time / 300) + particleData[index].seed;
particleData[index].position += particleData[index].velocity * deltaTime / 20 + vec2f(sin(phase) / 600, cos(phase) / 500);
}`.
TgpuComputeFn<{ gid: BuiltinGlobalInvocationId; }>.$uses(dependencyMap: Record<string, unknown>): TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
$uses
({
particleData: TgpuBufferMutable<d.WgslArray<d.U32>> & TgpuFixedBufferUsage<d.WgslArray<d.U32>>
particleData
:
const particleDataStorage: TgpuBufferMutable<d.WgslArray<d.U32>> & TgpuFixedBufferUsage<d.WgslArray<d.U32>>
particleDataStorage
,
deltaTime: TgpuUniform<d.F32>
deltaTime
,
time: TgpuMutable<d.F32>
time
});

Resolved WGSL for the compute function above is equivalent (with respect to some cleanup) to the following:

@group(0) @binding(0) var<storage, read_write> particleData: array<u32, 100>;
@group(0) @binding(1) var<uniform> deltaTime: f32;
@group(0) @binding(2) var<storage, read_write> time: f32;
struct mainCompute_Input {
@builtin(global_invocation_id) gid: vec3u,
}
@compute @workgroup_size(1) fn mainCompute(in: mainCompute_Input) {
let index = in.gid.x;
if index == 0 {
time += deltaTime;
}
let phase = (time / 300) + particleData[index].seed;
particleData[index].position += particleData[index].velocity * deltaTime / 20 + vec2f(sin(phase) / 600, cos(phase) / 500);
}

TgpuVertexFn accepts an object with two properties:

  • in — an IORecord describing the input of the function,
  • out — an IORecord describing the output of the function.

TgpuFragment accepts an object with two properties:

  • in — an IORecord describing the input of the function,
  • outd.vec4f, or an IORecord describing the output of the function.
const
const mainVertex: TgpuVertexFn<{}, {
uv: d.Vec2f;
}>
mainVertex
=
const tgpu: {
fn: {
<Args extends d.AnyData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>;
<Args extends d.AnyData[] | [], Return extends d.AnyData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>;
};
... 7 more ...;
'~unstable': {
...;
};
}
tgpu
['~unstable'].
vertexFn: <{
vertexIndex: d.BuiltinVertexIndex;
}, {
outPos: d.BuiltinPosition;
uv: d.Vec2f;
}>(options: {
in: {
vertexIndex: d.BuiltinVertexIndex;
};
out: {
outPos: d.BuiltinPosition;
uv: d.Vec2f;
};
}) => TgpuVertexFnShell<...> (+1 overload)
vertexFn
({
in: {
vertexIndex: d.BuiltinVertexIndex;
}
in
: {
vertexIndex: d.BuiltinVertexIndex
vertexIndex
:
import d
d
.
const builtin: {
readonly vertexIndex: d.BuiltinVertexIndex;
readonly instanceIndex: d.BuiltinInstanceIndex;
readonly position: d.BuiltinPosition;
readonly clipDistances: d.BuiltinClipDistances;
... 10 more ...;
readonly subgroupSize: BuiltinSubgroupSize;
}
export builtin
builtin
.
vertexIndex: d.BuiltinVertexIndex
vertexIndex
},
out: {
outPos: d.BuiltinPosition;
uv: d.Vec2f;
}
out
: {
outPos: d.BuiltinPosition
outPos
:
import d
d
.
const builtin: {
readonly vertexIndex: d.BuiltinVertexIndex;
readonly instanceIndex: d.BuiltinInstanceIndex;
readonly position: d.BuiltinPosition;
readonly clipDistances: d.BuiltinClipDistances;
... 10 more ...;
readonly subgroupSize: BuiltinSubgroupSize;
}
export builtin
builtin
.
position: d.BuiltinPosition
position
,
uv: d.Vec2f
uv
:
import d
d
.
const vec2f: d.Vec2f
export vec2f

Schema representing vec2f - a vector with 2 elements of type f32. Also a constructor function for this vector value.

@example const vector = d.vec2f(); // (0.0, 0.0) const vector = d.vec2f(1); // (1.0, 1.0) const vector = d.vec2f(0.5, 0.1); // (0.5, 0.1)

@example const buffer = root.createBuffer(d.vec2f, d.vec2f(0, 1)); // buffer holding a d.vec2f value, with an initial value of vec2f(0, 1);

vec2f
},
}) /* wgsl */`{
var pos = array<vec2f, 3>(
vec2(0.0, 0.5),
vec2(-0.5, -0.5),
vec2(0.5, -0.5)
);
var uv = array<vec2f, 3>(
vec2(0.5, 1.0),
vec2(0.0, 0.0),
vec2(1.0, 0.0),
);
return Out(vec4f(pos[in.vertexIndex], 0.0, 1.0), uv[in.vertexIndex]);
}`;
const
const mainFragment: TgpuFragmentFn<{
uv: d.Vec2f;
}, d.Vec4f>
mainFragment
=
const tgpu: {
fn: {
<Args extends d.AnyData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>;
<Args extends d.AnyData[] | [], Return extends d.AnyData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>;
};
... 7 more ...;
'~unstable': {
...;
};
}
tgpu
['~unstable'].
fragmentFn: <{
uv: d.Vec2f;
}, d.Vec4f>(options: {
in: {
uv: d.Vec2f;
};
out: d.Vec4f;
}) => TgpuFragmentFnShell<{
uv: d.Vec2f;
}, d.Vec4f> (+1 overload)
fragmentFn
({
in: {
uv: d.Vec2f;
}
in
: {
uv: d.Vec2f
uv
:
import d
d
.
const vec2f: d.Vec2f
export vec2f

Schema representing vec2f - a vector with 2 elements of type f32. Also a constructor function for this vector value.

@example const vector = d.vec2f(); // (0.0, 0.0) const vector = d.vec2f(1); // (1.0, 1.0) const vector = d.vec2f(0.5, 0.1); // (0.5, 0.1)

@example const buffer = root.createBuffer(d.vec2f, d.vec2f(0, 1)); // buffer holding a d.vec2f value, with an initial value of vec2f(0, 1);

vec2f
},
out: d.Vec4f
out
:
import d
d
.
const vec4f: d.Vec4f
export vec4f

Schema representing vec4f - a vector with 4 elements of type f32. Also a constructor function for this vector value.

@example const vector = d.vec4f(); // (0.0, 0.0, 0.0, 0.0) const vector = d.vec4f(1); // (1.0, 1.0, 1.0, 1.0) const vector = d.vec4f(1, 2, 3, 4.5); // (1.0, 2.0, 3.0, 4.5)

@example const 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
,
}) /* wgsl */`{
return getGradientColor((in.uv[0] + in.uv[1]) / 2);
}`.
TgpuFragmentFn<{ uv: Vec2f; }, Vec4f>.$uses(dependencyMap: Record<string, unknown>): TgpuFragmentFn<{
uv: d.Vec2f;
}, d.Vec4f>
$uses
({
getGradientColor: TgpuFn<(args_0: d.F32) => d.Vec4f>
getGradientColor
});

Resolved WGSL for the pipeline including the two entry point functions above is equivalent (with respect to some cleanup) to the following:

struct mainVertex_Input {
@builtin(vertex_index) vertexIndex: u32,
}
struct mainVertex_Output {
@builtin(position) outPos: vec4f,
@location(0) uv: vec2f,
}
@vertex fn mainVertex(in: mainVertex_Input) -> mainVertex_Output {
var pos = array<vec2f, 3>(
vec2(0.0, 0.5),
vec2(-0.5, -0.5),
vec2(0.5, -0.5)
);
var uv = array<vec2f, 3>(
vec2(0.5, 1.0),
vec2(0.0, 0.0),
vec2(1.0, 0.0),
);
return mainVertex_Output(vec4f(pos[in.vertexIndex], 0.0, 1.0), uv[in.vertexIndex]);
}
fn getGradientColor(ratio: f32) -> vec4f{
return mix(vec4f(0.769, 0.392, 1, 1), vec4f(0.114, 0.447, 0.941, 1), ratio);
}
struct mainFragment_Input {
@location(0) uv: vec2f,
}
@fragment fn mainFragment(in: mainFragment_Input) -> @location(0) vec4f {
return getGradientColor((in.uv[0] + in.uv[1]) / 2);
}

Typed functions are crucial for simplified pipeline creation offered by TypeGPU. You can define and run pipelines as follows:

const
const pipeline: TgpuRenderPipeline<d.Vec4f>
pipeline
=
const root: TgpuRoot
root
['~unstable']
.
withVertex<{}, {
uv: d.Vec2f;
}>(entryFn: TgpuVertexFn<{}, {
uv: d.Vec2f;
}>, attribs: {}): WithVertex<{
uv: d.Vec2f;
}>
withVertex
(
const mainVertex: TgpuVertexFn<{}, {
uv: d.Vec2f;
}>
mainVertex
, {})
.
WithVertex<{ uv: Vec2f; }>.withFragment<{
uv: d.Vec2f;
}, d.Vec4f>(entryFn: TgpuFragmentFn<{
uv: d.Vec2f;
}, d.Vec4f>, targets: GPUColorTargetState): WithFragment<d.Vec4f>
withFragment
(
const mainFragment: TgpuFragmentFn<{
uv: d.Vec2f;
}, d.Vec4f>
mainFragment
, {
format: string
format
:
const presentationFormat: "rgba8unorm"
presentationFormat
})
.
WithFragment<Vec4f>.createPipeline(): TgpuRenderPipeline<d.Vec4f>
createPipeline
();
const pipeline: TgpuRenderPipeline<d.Vec4f>
pipeline
.
TgpuRenderPipeline<Vec4f>.withColorAttachment(attachment: ColorAttachment): TgpuRenderPipeline<d.Vec4f>
withColorAttachment
({
ColorAttachment.view: any

A

GPUTextureView

describing the texture subresource that will be output to for this color attachment.

view
:
const context: any
context
.
any
getCurrentTexture
().
any
createView
(),
ColorAttachment.clearValue?: any

Indicates the value to clear

GPURenderPassColorAttachment#view

to prior to executing the render pass. If not map/exist|provided, defaults to {r: 0, g: 0, b: 0, a: 0}. Ignored if

GPURenderPassColorAttachment#loadOp

is not

GPULoadOp# "clear"

. The components of

GPURenderPassColorAttachment#clearValue

are all double values. They are converted to a texel value of texture format matching the render attachment. If conversion fails, a validation error is generated.

clearValue
: [0, 0, 0, 0],
ColorAttachment.loadOp: GPULoadOp

Indicates the load operation to perform on

GPURenderPassColorAttachment#view

prior to executing the render pass. Note: It is recommended to prefer clearing; see

GPULoadOp# "clear"

for details.

loadOp
: 'clear',
ColorAttachment.storeOp: GPUStoreOp

The store operation to perform on

GPURenderPassColorAttachment#view

after executing the render pass.

storeOp
: 'store',
})
.
TgpuRenderPipeline<Vec4f>.draw(vertexCount: number, instanceCount?: number, firstVertex?: number, firstInstance?: number): void
draw
(3);

The rendering result looks like this: rendering result - gradient triangle

You can check out the full example on our examples page.