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 from 'typegpu';
import * as d from 'typegpu/data';
// Defining the layout of resources we want the shader to
// have access to.
const fooLayout = tgpu.bindGroupLayout({
foo: { uniform: d.vec3f },
bar: { texture: 'float' },
});
const fooBuffer = ...;
const barTexture = ...;
// Create a bind group that can fulfill the required layout.
const fooBindGroup = fooLayout.populate({
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));

Available resource types

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: { ... },
// ...
});

Uniforms

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

{
uniform: d.AnyData;
}

Simple example

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

Matching WGSL statement:

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

Storage

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';
}

Simple example

const fooLayout = tgpu.bindGroupLayout({
counter: { storage: d.f32, access: 'mutable' },
// ...
});

Matching WGSL statement:

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

Runtime-sized example

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) => d.arrayOf(d.f32, n) },
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

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

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

Textures

To be able to sample a texture in a shader, create a property with the value matching:

{
texture: 'float' | 'unfilterable-float' | 'depth' | 'sint' | 'uint';
/** @default '2d' */
viewDimension?: '1d' | '2d' | '2d-array' | 'cube' | 'cube-array' | '3d';
/** @default false */
multisampled?: boolean;
}

Storage Textures

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

{
storageTexture: StorageTextureTexelFormat;
/** @default 'writeonly' */
access?: 'readonly' | 'writeonly' | 'mutable';
/** @default '2d' */
viewDimension?: '1d' | '2d' | '2d-array' | '3d';
}

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

Bind Groups

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 populate method on the bind group layout and associate each named key with a proper resource.

const fooLayout = tgpu.bindGroupLayout({
key0: { ... },
key1: { ... },
// ...
});
const fooBindGroup0 = fooLayout.populate({
key1: ...,
key0: ...,
// ...
});
const fooBindGroup1 = fooLayout.populate({
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> & Uniform - buffers of type TData with 'uniform' usage,
    • GPUBuffer - raw WebGPU buffers.
  • Storage bindings with schema TData accept:
    • TgpuBuffer<TData> & Storage - 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.