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));Available resource types
Section titled β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
Section titled βUniformsβTo interpret a buffer as a uniform, create a property with the value matching:
{ uniform: d.AnyData;}Simple example
Section titled βSimple exampleβconst fooLayout = tgpu.bindGroupLayout({ luckyNumber: { uniform: d.f32 }, // ...});Matching WGSL statement:
@group(...) @binding(0) var<uniform> luckyNumber: f32;// ...Storage
Section titled β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
Section titled β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
Section titled β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.
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.
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:
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
Section titled βSamplersβSamplers can be made accessible to shaders with a property that matches the following:
{ sampler: 'filtering' | 'non-filtering' | 'comparison';}Textures
Section titled βTexturesβ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}Storage Textures
Section titled βStorage 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
Section titled βExternal Texturesβ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");}
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.WgslExternalTextureexport 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.
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.
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.
Bind Groups
Section titled β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 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
TDataaccept:TgpuBuffer<TData> & UniformFlag- buffers of typeTDatawith'uniform'usage,GPUBuffer- raw WebGPU buffers.
- Storage bindings with schema
TDataaccept:TgpuBuffer<TData> & StorageFlag- buffers of typeTDatawith'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 acomparefunction.
sampler === 'filtering'orsampler === 'non-filtering'GPUSampler- raw WebGPU samplers created without acomparefunction.
- External Texture bindings:
GPUExternalTexture- imported withdevice.importExternalTexture(...).