Signatur
Beschreibung
createBindGroup() verbindet echte GPU-Ressourcen mit den @group/@binding-Deklarationen eines WGSL-Shaders. Das zugrunde liegende GPUBindGroupLayout legt fest, welche Art von Ressource an welchem Binding-Index erwartet wird. Die Bind Group liefert dann die konkreten Objekte, also etwa einen bestimmten Uniform-Puffer oder eine bestimmte Textur-Ansicht.
Gebraucht wird die Methode immer dann, wenn ein Render- oder Compute-Pipeline Daten lesen oder schreiben soll: Uniforms (Kameramatrizen, Zeit), Storage-Puffer (Partikel, Rechenergebnisse), Texturen und Sampler. Die fertige Bind Group wird während eines Durchlaufs mit passEncoder.setBindGroup(index, bindGroup) gesetzt.
Das Layout kann explizit mit device.createBindGroupLayout() erstellt werden oder bei Pipelines mit layout: "auto" über pipeline.getBindGroupLayout(index) abgefragt werden. Bind Groups sind unveränderlich. Den Inhalt eines gebundenen Puffers darfst du jederzeit ändern (z. B. mit queue.writeBuffer()). Willst du aber eine andere Ressource binden, brauchst du eine neue Bind Group.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $descriptor Pflicht | GPUBindGroupDescriptor | Konfigurationsobjekt mit folgenden Eigenschaften:
|
|
| $descriptor.entries[].binding Pflicht | number | Binding-Index. Er muss zu einem Eintrag im Layout und zu @binding(n) im WGSL-Code passen. |
|
| $descriptor.entries[].resource Pflicht | GPUBufferBinding | GPUTextureView | GPUSampler | GPUExternalTexture | GPUBuffer | Die zu bindende Ressource. Puffer werden als { buffer, offset?, size? } übergeben (offset ist standardmäßig 0, size standardmäßig der Rest des Puffers). In neueren Implementierungen darfst du auch direkt einen GPUBuffer übergeben. |
Rückgabewert
GPUBindGroup. Ist der Deskriptor ungültig, bekommst du trotzdem ein Objekt zurück. Es ist dann aber ungültig, und es wird ein Validierungsfehler gemeldet (keine Ausnahme!).Beispiele
Uniform-Puffer an eine Render-Pipeline binden
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const shader = device.createShaderModule({
code: `
struct U { color: vec4f };
@group(0) @binding(0) var<uniform> u: U;
@vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f {
let p = array(vec2f(0, 0.5), vec2f(-0.5, -0.5), vec2f(0.5, -0.5));
return vec4f(p[i], 0, 1);
}
@fragment fn fs() -> @location(0) vec4f { return u.color; }
`
});
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: { module: shader, entryPoint: 'vs' },
fragment: { module: shader, entryPoint: 'fs', targets: [{ format: navigator.gpu.getPreferredCanvasFormat() }] }
});
const uniformBuffer = device.createBuffer({
size: 16,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(uniformBuffer, 0, new Float32Array([1, 0.4, 0, 1]));
const bindGroup = device.createBindGroup({
label: 'Farb-Uniforms',
layout: pipeline.getBindGroupLayout(0),
entries: [{ binding: 0, resource: { buffer: uniformBuffer } }]
});
console.log(bindGroup.label);
// Später im Render-Pass: pass.setBindGroup(0, bindGroup);
Explizites Layout mit Storage-Puffern für einen Compute-Shader
const device = await (await navigator.gpu.requestAdapter()).requestDevice();
const layout = device.createBindGroupLayout({
entries: [
{ binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } },
{ binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } }
]
});
const input = device.createBuffer({ size: 1024, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST });
const output = device.createBuffer({ size: 1024, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC });
const bindGroup = device.createBindGroup({
layout,
entries: [
{ binding: 0, resource: { buffer: input } },
// Nur die zweite Hälfte des Puffers binden (Offset muss 256-Byte-ausgerichtet sein)
{ binding: 1, resource: { buffer: output, offset: 256, size: 768 } }
]
});
const pipeline = device.createComputePipeline({
layout: device.createPipelineLayout({ bindGroupLayouts: [layout] }),
compute: {
module: device.createShaderModule({ code: `
@group(0) @binding(0) var<storage, read> a: array<f32>;
@group(0) @binding(1) var<storage, read_write> b: array<f32>;
@compute @workgroup_size(64) fn main(@builtin(global_invocation_id) id: vec3u) {
if (id.x < arrayLength(&b)) { b[id.x] = a[id.x] * 2.0; }
}` }),
entryPoint: 'main'
}
});
const encoder = device.createCommandEncoder();
const pass = encoder.beginComputePass();
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.dispatchWorkgroups(4);
pass.end();
device.queue.submit([encoder.finish()]);
Textur und Sampler binden, Validierungsfehler abfangen
const device = await (await navigator.gpu.requestAdapter()).requestDevice();
const layout = device.createBindGroupLayout({
entries: [
{ binding: 0, visibility: GPUShaderStage.FRAGMENT, sampler: {} },
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: {} }
]
});
const texture = device.createTexture({
size: [256, 256],
format: 'rgba8unorm',
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
});
const sampler = device.createSampler({ magFilter: 'linear', minFilter: 'linear' });
device.pushErrorScope('validation');
const bindGroup = device.createBindGroup({
label: 'Material',
layout,
entries: [
{ binding: 0, resource: sampler },
{ binding: 1, resource: texture.createView() }
]
});
const error = await device.popErrorScope();
console.log(error ? `Fehler: ${error.message}` : 'Bind Group gültig');