Start · Sprachen · JavaScript · Referenz · GPUComputePassEncoder.dispatchWorkgroupsIndirect()

GPUComputePassEncoder.dispatchWorkgroupsIndirect()

Methode

Startet die Compute-Pipeline mit einem Workgroup-Raster, dessen Größe (x, y, z) zur Laufzeit aus einem <code>GPUBuffer</code> gelesen wird.

seit JavaScript WebGPU – Chrome/Edge 113+, Safari 26+, Firefox 141 Kategorie: webapi

Signatur

GPUComputePassEncoder.prototype.dispatchWorkgroupsIndirect(indirectBuffer, indirectOffset)

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

Typ
undefined
Beschreibung
Kein Rückgabewert. Der Befehl wird nur im Command-Encoder aufgezeichnet und erst nach 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');
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: indirectOffset wird 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.INDIRECT schlägt die Validierung fehl. Soll ein Shader die Argumente schreiben, braucht der Puffer zusätzlich STORAGE.
  • 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 von 0 sind 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 Ereignis uncapturederror.
  • 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.