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.
Defining a function
Section titled “Defining a function”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.F32export 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.
f32], import d
d.const vec4f: d.Vec4fexport vec4f
Schema representing vec4f - a vector with 4 elements of type f32.
Also a constructor function for this vector value.
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.F32export 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.
f32], import d
d.const vec4f: d.Vec4fexport vec4f
Schema representing vec4f - a vector with 4 elements of type f32.
Also a constructor function for this vector value.
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);}`;
External resources
Section titled “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.).
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.Vec4fexport vec4f
Schema representing vec4f - a vector with 4 elements of type f32.
Also a constructor function for this vector value.
vec4f)`() { return vec4f(0.114, 0.447, 0.941, 1);}`;
// calling a schema to create a value on the JS sideconst 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.
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.F32export 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.
f32], import d
d.const vec4f: d.Vec4fexport vec4f
Schema representing vec4f - a vector with 4 elements of type f32.
Also a constructor function for this vector value.
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);;}
Entry functions
Section titled “Entry functions”Instead of annotating a TgpuFn
with attributes, entry functions are defined using dedicated shell constructors:
tgpu['~unstable'].computeFn
,tgpu['~unstable'].vertexFn
,tgpu['~unstable'].fragmentFn
.
Entry point function I/O
Section titled “Entry point function I/O”To describe the input and output of an entry point function, we use IORecord
s, 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 IORecord
s.
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
.
Compute
Section titled “Compute”TgpuComputeFn
accepts an object with two properties:
in
— anIORecord
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);}
Vertex and fragment
Section titled “Vertex and fragment”TgpuVertexFn
accepts an object with two properties:
in
— anIORecord
describing the input of the function,out
— anIORecord
describing the output of the function.
TgpuFragment
accepts an object with two properties:
in
— anIORecord
describing the input of the function,out
—d.vec4f
, or anIORecord
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.Vec2fexport vec2f
Schema representing vec2f - a vector with 2 elements of type f32.
Also a constructor function for this vector value.
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.Vec2fexport vec2f
Schema representing vec2f - a vector with 2 elements of type f32.
Also a constructor function for this vector value.
vec2f }, out: d.Vec4f
out: import d
d.const vec4f: d.Vec4fexport vec4f
Schema representing vec4f - a vector with 4 elements of type f32.
Also a constructor function for this vector value.
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);}
Usage in pipelines
Section titled “Usage in pipelines”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:
You can check out the full example on our examples page.