Skip to content

Bind Groups

A bind group is a collection of resources that are bound to a shader. These resources can be buffers, textures, or samplers. It’s a way to define what resources are available to a shader and how they are accessed.

import tgpu, { d } from 'typegpu';
// Defining the layout of resources we want the shader to
// have access to.
const fooLayout = tgpu.bindGroupLayout({
foo: { uniform: d.vec3f },
bar: { texture: d.texture2d(d.f32) },
});
const fooBuffer = ...;
const barTexture = ...;
// Create a bind group that can fulfill the required layout.
const fooBindGroup = root.createBindGroup(fooLayout, {
foo: fooBuffer,
bar: barTexture,
});

In this example, we create a bind group that contains a buffer and a texture. Binding indices are determined based on the order of properties in the layout.

Now, during command encoding, we can assign this bind group to a shader.

// Assuming group index is 0...
pass.setBindGroup(0, root.unwrap(fooBindGroup));

Each property in the layout object represents a resource as seen by a shader. We recommend keeping the names of these properties the same as the corresponding @group(...) @binding(...) ...; statements in WGSL.

const fooLayout = tgpu.bindGroupLayout({
key0: { ... },
key1: { ... },
// ...
});

To interpret a buffer as a uniform, create a property with the value matching:

{
uniform: d.AnyData;
}
main.js
const fooLayout = tgpu.bindGroupLayout({
luckyNumber: { uniform: d.f32 },
// ...
});

Matching WGSL statement:

shader.wgsl
@group(...) @binding(0) var<uniform> luckyNumber: f32;
// ...

To get readonly/mutable access to a buffer, create a property with the value matching:

{
storage: d.AnyData | ((n: number) => d.AnyData);
/** @default 'readonly' */
access?: 'readonly' | 'mutable';
}
const fooLayout = tgpu.bindGroupLayout({
counter: { storage: d.f32, access: 'mutable' },
// ...
});

Matching WGSL statement:

@group(...) @binding(0) var<storage, read_write> counter: f32;
// ...

Apart from being able to specify any data type, we can signal that the shader is generalized to work on arbitrarily sized data by passing a function.

main.ts
const Filter = (n: number) =>
d.struct({
clamp: d.f32,
values: d.arrayOf(d.f32, n),
});
const fooLayout = tgpu.bindGroupLayout({
factors: { storage: (n: number) => d.arrayOf(d.f32, n) },
filter: { storage: Filter },
// ...
});

You don’t have to write arrow functions every time, d.arrayOf(schema) is a convenient shorthand for that.

main.ts
const Filter = (n: number) =>
d.struct({
clamp: d.f32,
// Unfortunately, in structs, you cannot simply write d.arrayOf(d.f32)
values: d.arrayOf(d.f32, n),
});
const fooLayout = tgpu.bindGroupLayout({
factors: { storage: (n: number) => d.arrayOf(d.f32, n) },
factors: { storage: d.arrayOf(d.f32) },
filter: { storage: Filter },
// ...
});

Matching WGSL code:

shader.wgsl
struct Filter {
clamp: f32,
values: array<f32>;
}
@group(...) @binding(0) var<storage, read> factors: array<f32>;
@group(...) @binding(1) var<storage, read> filter: Filter;
// ...

Samplers can be made accessible to shaders with a property that matches the following:

{
sampler: 'filtering' | 'non-filtering' | 'comparison';
}

To be able to sample a texture in a shader, create a property with the corresponding schema:

{
texture: d.WgslTexture;
// for example:
// texture: d.texture2d(d.f32);
// texture: d.textureCubeArray(d.f32);
// texture: d.texture3d(d.i32);
/** @default 'float' */
sampleType?: 'float' | 'unfilterable-float'; // only for float textures
}

To be able to operate on textures more directly in a shader, create a property with the corresponding schema:

{
storageTexture: d.WgslStorageTexture;
// for example:
// storageTexture: d.storageTexture2d('rgba8unorm');
// storageTexture: d.storageTextureCube('r32float', 'read-write');
// storageTexture: d.storageTexture2dArray('rgba16float', 'read-only');
}

You can see the list of supported storage texture formats here.

External textures provide efficient, zero-copy shader access to sources such as video frames. Declare them with d.textureExternal() and bind the GPUExternalTexture returned by WebGPU:

const
const videoLayout: TgpuBindGroupLayout<{
frame: {
externalTexture: d.WgslExternalTexture;
};
sampler: {
sampler: "filtering";
};
}>
videoLayout
=
const tgpu: {
const: typeof import("node_modules/typegpu/src/core/constant/tgpuConstant").constant;
fn: typeof import("node_modules/typegpu/src/core/function/tgpuFn").fn;
comptime: typeof import("node_modules/typegpu/src/core/function/comptime").comptime;
resolve: typeof import("node_modules/typegpu/src/core/resolve/tgpuResolve").resolve;
resolveWithContext: typeof import("node_modules/typegpu/src/core/resolve/tgpuResolve").resolveWithContext;
init: typeof import("node_modules/typegpu/src/core/root/init").init;
initFromDevice: typeof import("node_modules/typegpu/src/core/root/init").initFromDevice;
slot: typeof import("node_modules/typegpu/src/core/slot/slot").slot;
lazy: typeof import("node_modules/typegpu/src/core/slot/lazy").lazy;
... 10 more ...;
'~unstable': typeof import("node_modules/typegpu/src/tgpuUnstable");
}

@module ― typegpu

tgpu
.
bindGroupLayout: <{
frame: {
externalTexture: d.WgslExternalTexture;
};
sampler: {
sampler: "filtering";
};
}>(entries: {
frame: {
externalTexture: d.WgslExternalTexture;
};
sampler: {
sampler: "filtering";
};
}) => TgpuBindGroupLayout<{
frame: {
externalTexture: d.WgslExternalTexture;
};
sampler: {
sampler: "filtering";
};
}> (+1 overload)
bindGroupLayout
({
frame: {
externalTexture: d.WgslExternalTexture;
}
frame
: {
externalTexture: d.WgslExternalTexture
externalTexture
:
import d
d
.
function textureExternal(): d.WgslExternalTexture
export textureExternal
textureExternal
() },
sampler: {
sampler: "filtering";
}
sampler
: {
sampler: "filtering"
sampler
: 'filtering' },
});
const
const externalFrame: GPUExternalTexture
externalFrame
=
const root: TgpuRoot
root
.
TgpuRoot.device: GPUDevice

The GPU device associated with this root.

device
.
GPUDevice.importExternalTexture(descriptor: GPUExternalTextureDescriptor): GPUExternalTexture (+1 overload)

Creates a

GPUExternalTexture

wrapping the provided image source.

@param ― descriptor - Provides the external image source object (and any creation options).

importExternalTexture
({
GPUExternalTextureDescriptor.source: HTMLVideoElement | VideoFrame

The video source to import the external texture from. Source size is determined as described by the external source dimensions table.

source
:
const videoElement: HTMLVideoElement
videoElement
,
});
const
const videoGroup: TgpuBindGroup<{
frame: {
externalTexture: d.WgslExternalTexture;
};
sampler: {
sampler: "filtering";
};
}>
videoGroup
=
const root: TgpuRoot
root
.
TgpuRoot.createBindGroup<{
frame: {
externalTexture: d.WgslExternalTexture;
};
sampler: {
sampler: "filtering";
};
}>(layout: TgpuBindGroupLayout<{
frame: {
externalTexture: d.WgslExternalTexture;
};
sampler: {
sampler: "filtering";
};
}>, entries: ExtractBindGroupInputFromLayout<{
frame: {
externalTexture: d.WgslExternalTexture;
};
sampler: {
sampler: "filtering";
};
}>): TgpuBindGroup<{
frame: {
externalTexture: d.WgslExternalTexture;
};
sampler: {
sampler: "filtering";
};
}>

Creates a group of resources that can be bound to a shader based on a specified layout.

@remarks ― Typed wrapper around a GPUBindGroup.

@example

const fooLayout = tgpu.bindGroupLayout({ foo: { uniform: d.vec3f }, bar: { texture: 'float' }, });

const fooBuffer = ...; const barTexture = ...;

const fooBindGroup = root.createBindGroup(fooLayout, { foo: fooBuffer, bar: barTexture, });

@param ― layout Layout describing the bind group to be created.

@param ― entries A record with values being the resources populating the bind group and keys being their associated names, matching the layout keys.

createBindGroup
(
const videoLayout: TgpuBindGroupLayout<{
frame: {
externalTexture: d.WgslExternalTexture;
};
sampler: {
sampler: "filtering";
};
}>
videoLayout
, {
frame: GPUExternalTexture
frame
:
const externalFrame: GPUExternalTexture
externalFrame
,
sampler: TgpuSampler | GPUSampler
sampler
:
const root: TgpuRoot
root
.
TgpuRoot.createSampler(props: WgslSamplerProps): TgpuFixedSampler
createSampler
({
WgslSamplerProps.magFilter?: GPUFilterMode | undefined

Specifies the sampling behavior when the sample footprint is smaller than or equal to one texel.

magFilter
: 'linear',
WgslSamplerProps.minFilter?: GPUFilterMode | undefined

Specifies the sampling behavior when the sample footprint is larger than one texel.

minFilter
: 'linear' }),
});

In shader code, external textures support std.textureLoad and std.textureSampleBaseClampToEdge.

Before execution of a pipeline, any bind group that matches a given layout can be put in its place and used by the shader. To create a bind group, you can call the createBindGroup method on the root object and associate each named key with a proper resource.

const fooLayout = tgpu.bindGroupLayout({
key0: { ... },
key1: { ... },
// ...
});
const fooBindGroup0 = root.createBindGroup(fooLayout, {
key1: ...,
key0: ...,
// ...
});
const fooBindGroup1 = root.createBindGroup(fooLayout, {
key0: ...,
key1: ...,
// ...
});
// ...

If you accidentally pass the wrong type of resource, the TypeScript compiler will catch the error at compile time.

  • Uniform bindings with schema TData accept:
    • TgpuBuffer<TData> & UniformFlag - buffers of type TData with 'uniform' usage,
    • GPUBuffer - raw WebGPU buffers.
  • Storage bindings with schema TData accept:
    • TgpuBuffer<TData> & StorageFlag - buffers of type TData with 'storage' usage,
    • GPUBuffer - raw WebGPU buffers.
  • Texture bindings:
    • GPUTextureView - views of raw WebGPU textures.
  • Storage Texture bindings:
    • GPUTextureView - views of raw WebGPU textures.
  • Sampler bindings:
    • sampler === 'comparison'
      • GPUSampler - raw WebGPU samplers created with a compare function.
    • sampler === 'filtering' or sampler === 'non-filtering'
      • GPUSampler - raw WebGPU samplers created without a compare function.
  • External Texture bindings:
    • GPUExternalTexture - imported with device.importExternalTexture(...).