Signatur
Beschreibung
dispatchWorkgroupsIndirect() macht dasselbe wie dispatchWorkgroups(). Der Unterschied: Die Anzahl der Workgroups in x-, y- und z-Richtung übergibt man nicht als JavaScript-Zahlen. Die GPU liest sie direkt aus einem Puffer. Dort müssen an der Stelle indirectOffset drei aufeinanderfolgende u32-Werte stehen (12 Byte): workgroupCountX, workgroupCountY und workgroupCountZ.
Das ist vor allem dann sinnvoll, wenn die Arbeitsmenge erst auf der GPU feststeht. Beispiele sind Culling, Stream-Compaction, Partikelsysteme oder mehrstufige Algorithmen, bei denen ein vorheriger Compute-Pass bestimmt, wie viele Elemente weiterverarbeitet werden. Die Daten müssen dafür nicht zur CPU zurückgelesen werden. So entfällt ein teurer Synchronisationspunkt mit mapAsync().
Vor dem Aufruf müssen mit setPipeline() eine Compute-Pipeline und mit setBindGroup() alle nötigen Bind-Groups gesetzt sein. Der Puffer muss mit dem Verwendungs-Flag GPUBufferUsage.INDIRECT erzeugt worden sein.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $indirectBuffer Pflicht | GPUBuffer | Puffer mit den drei Workgroup-Anzahlen als u32. Er muss mit GPUBufferUsage.INDIRECT erzeugt worden sein. |
|
| $indirectOffset Pflicht | number | Byte-Offset im Puffer, an dem die Parameter beginnen. Er muss ein Vielfaches von 4 sein, und indirectOffset + 12 darf die Puffergröße nicht überschreiten. |
Rückgabewert
device.queue.submit() ausgeführt.Beispiele
Einfacher indirekter Dispatch mit Werten aus JavaScript
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const module = device.createShaderModule({
code: `
@group(0) @binding(0) var<storage, read_write> data: array<f32>;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3u) {
if (id.x < arrayLength(&data)) { data[id.x] *= 2.0; }
}`
});
const pipeline = device.createComputePipeline({
layout: 'auto',
compute: { module, entryPoint: 'main' }
});
const dataBuffer = device.createBuffer({
size: 1024 * 4,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
});
// Indirekt-Puffer: [x, y, z] als u32
const indirectBuffer = device.createBuffer({
size: 12,
usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(indirectBuffer, 0, new Uint32Array([16, 1, 1])); // 16 * 64 = 1024
const bindGroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [{ binding: 0, resource: { buffer: dataBuffer } }]
});
const encoder = device.createCommandEncoder();
const pass = encoder.beginComputePass();
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.dispatchWorkgroupsIndirect(indirectBuffer, 0);
pass.end();
device.queue.submit([encoder.finish()]);
console.log('Indirekter Dispatch eingereiht');
GPU berechnet die Workgroup-Anzahl selbst (ohne Rücklesen zur CPU)
// Annahme: device, countBuffer (STORAGE, enthält atomare Trefferzahl),
// processPipeline und processBindGroup existieren bereits.
const prepModule = device.createShaderModule({
code: `
@group(0) @binding(0) var<storage, read> count: u32;
@group(0) @binding(1) var<storage, read_write> args: array<u32, 3>;
@compute @workgroup_size(1)
fn main() {
args[0] = (count + 63u) / 64u; // aufrunden auf 64er-Workgroups
args[1] = 1u;
args[2] = 1u;
}`
});
const prepPipeline = device.createComputePipeline({
layout: 'auto',
compute: { module: prepModule, entryPoint: 'main' }
});
const argsBuffer = device.createBuffer({
size: 12,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.INDIRECT
});
const prepBindGroup = device.createBindGroup({
layout: prepPipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: { buffer: countBuffer } },
{ binding: 1, resource: { buffer: argsBuffer } }
]
});
const encoder = device.createCommandEncoder();
// Pass 1: Argumente schreiben
const prep = encoder.beginComputePass();
prep.setPipeline(prepPipeline);
prep.setBindGroup(0, prepBindGroup);
prep.dispatchWorkgroups(1);
prep.end();
// Pass 2: mit den berechneten Argumenten starten
const work = encoder.beginComputePass();
work.setPipeline(processPipeline);
work.setBindGroup(0, processBindGroup);
work.dispatchWorkgroupsIndirect(argsBuffer, 0);
work.end();
device.queue.submit([encoder.finish()]);
Mehrere Argument-Sätze in einem Puffer über den Offset
// Zwei Dispatch-Argumentsätze hintereinander (je 12 Byte)
const multiArgs = device.createBuffer({
size: 24,
usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(multiArgs, 0, new Uint32Array([
8, 8, 1, // Satz 0: 8×8 Workgroups
4, 1, 1 // Satz 1: 4 Workgroups
]));
const encoder = device.createCommandEncoder();
const pass = encoder.beginComputePass();
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.dispatchWorkgroupsIndirect(multiArgs, 0); // Satz 0
pass.dispatchWorkgroupsIndirect(multiArgs, 12); // Satz 1 (Offset in Byte!)
pass.end();
device.queue.submit([encoder.finish()]);
// Wichtig · Fallstricke
- Offset in Byte:
indirectOffsetwird in Byte angegeben, nicht in Elementen, und muss ein Vielfaches von 4 sein. Sonst entsteht ein Validierungsfehler, und der Command-Buffer wird ungültig. - Verwendungs-Flag: Ohne
GPUBufferUsage.INDIRECTschlägt die Validierung fehl. Soll ein Shader die Argumente schreiben, braucht der Puffer zusätzlichSTORAGE. - Limits: Überschreitet ein Wert
device.limits.maxComputeWorkgroupsPerDimension(Standard 65535), wird der Dispatch stillschweigend verworfen. Es gibt dann keinen Fehler, aber auch keine Ausführung. Werte von0sind erlaubt und führen dazu, dass nichts ausgeführt wird. - Fehler sind asynchron: Validierungsfehler erscheinen nicht als Exception. Man findet sie über
device.pushErrorScope()/popErrorScope()oder das Ereignisuncapturederror. - Kompatibilität: WebGPU gibt es nur in sicheren Kontexten (HTTPS). Chromium-Browser unterstützen es ab Version 113, Safari ab 26, Firefox ab 141 unter Windows. Auf anderen Plattformen ist die Unterstützung teils noch experimentell. Prüfe vorher mit
if (!navigator.gpu), ob WebGPU verfügbar ist.