Start · Sprachen · JavaScript · Referenz · GPURenderBundleEncoder.drawIndirect()

GPURenderBundleEncoder.drawIndirect()

Methode

Nimmt in ein Render-Bundle einen Zeichenbefehl auf, dessen Vertex- und Instanzanzahl zur Laufzeit aus einem <code>GPUBuffer</code> gelesen wird.

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

Signatur

GPURenderBundleEncoder.prototype.drawIndirect(indirectBuffer, indirectOffset)

Beschreibung

drawIndirect() ist die indirekte Variante von draw(). Die Parameter vertexCount, instanceCount, firstVertex und firstInstance werden nicht direkt aus JavaScript übergeben. Die GPU liest sie erst beim Ausführen aus einem GPUBuffer. Dort stehen sie als vier aufeinanderfolgende u32-Werte (16 Byte) ab indirectOffset.

Das ist vor allem bei GPU-gesteuertem Rendering nützlich: Ein Compute-Shader kann zum Beispiel Frustum-Culling ausführen oder Partikel erzeugen und schreibt die Anzahl der sichtbaren Instanzen selbst in den Indirect-Buffer. Die CPU muss das Ergebnis nicht zurücklesen, also gibt es keinen teuren Abgleich zwischen GPU und CPU.

Bei einem GPURenderBundleEncoder wird der Befehl nur aufgezeichnet. Das fertige Bundle (finish()) lässt sich danach in beliebig vielen Render-Passes mit GPURenderPassEncoder.executeBundles() abspielen. Der Inhalt des Indirect-Buffers wird bei jeder Ausführung neu gelesen. Das Bundle bleibt also gleich, die gezeichnete Menge kann sich aber von Frame zu Frame ändern.

Vor dem Aufruf müssen im Encoder eine Pipeline (setPipeline()), die nötigen Vertex-Buffer (setVertexBuffer()) und die Bind-Groups (setBindGroup()) gesetzt sein.

Parameter

Name Typ Default Beschreibung
$indirectBuffer Pflicht GPUBuffer Buffer mit den Zeichenparametern. Er muss mit GPUBufferUsage.INDIRECT erstellt worden sein.
$indirectOffset Pflicht number Byte-Offset im Buffer, ab dem die vier u32-Werte [vertexCount, instanceCount, firstVertex, firstInstance] stehen. Er muss ein Vielfaches von 4 sein, und indirectOffset + 16 darf die Buffergröße nicht überschreiten.

Rückgabewert

Typ
undefined
Beschreibung
Kein Rückgabewert. Der Befehl wird nur im Bundle aufgezeichnet.

Beispiele

Indirect-Buffer anlegen und Bundle aufzeichnen

// Voraussetzung: device, pipeline, vertexBuffer und context existieren bereits
const indirectBuffer = device.createBuffer({
  size: 16,
  usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_DST,
});

// vertexCount=3, instanceCount=1, firstVertex=0, firstInstance=0
device.queue.writeBuffer(indirectBuffer, 0, new Uint32Array([3, 1, 0, 0]));

const bundleEncoder = device.createRenderBundleEncoder({
  colorFormats: [navigator.gpu.getPreferredCanvasFormat()],
});
bundleEncoder.setPipeline(pipeline);
bundleEncoder.setVertexBuffer(0, vertexBuffer);
bundleEncoder.drawIndirect(indirectBuffer, 0);
const bundle = bundleEncoder.finish();

console.log(bundle instanceof GPURenderBundle);
true

Bundle abspielen, Instanzanzahl pro Frame ändern

const frame = (instanceCount) => {
  // Nur der Buffer-Inhalt ändert sich, das Bundle bleibt gleich
  device.queue.writeBuffer(indirectBuffer, 4, new Uint32Array([instanceCount]));

  const encoder = device.createCommandEncoder();
  const pass = encoder.beginRenderPass({
    colorAttachments: [{
      view: context.getCurrentTexture().createView(),
      loadOp: 'clear',
      storeOp: 'store',
      clearValue: { r: 0, g: 0, b: 0, a: 1 },
    }],
  });
  pass.executeBundles([bundle]);
  pass.end();
  device.queue.submit([encoder.finish()]);
};

frame(100); // zeichnet 100 Instanzen
frame(250); // gleiches Bundle, 250 Instanzen

GPU-Culling: Compute-Shader füllt die Draw-Parameter

// Ein Compute-Shader schreibt instanceCount per atomicAdd in den Buffer
const drawArgs = device.createBuffer({
  size: 16,
  usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
});

// Pro Frame: instanceCount auf 0 setzen, vertexCount = 36 (Würfel)
device.queue.writeBuffer(drawArgs, 0, new Uint32Array([36, 0, 0, 0]));

// ... Compute-Pass mit cullingPipeline, der drawArgs als storage-Binding nutzt ...

const enc = device.createRenderBundleEncoder({ colorFormats: ['bgra8unorm'] });
enc.setPipeline(renderPipeline);
enc.setVertexBuffer(0, cubeVertices);
enc.setBindGroup(0, instanceBindGroup);
enc.drawIndirect(drawArgs, 0);
const cullBundle = enc.finish();

// Wichtig · Fallstricke

  • Usage-Flag: Fehlt beim Buffer GPUBufferUsage.INDIRECT, meldet WebGPU einen Validierungsfehler. Er erscheint erst bei finish() bzw. über device.pushErrorScope() und nicht als JavaScript-Exception.
  • Alignment: indirectOffset muss durch 4 teilbar sein. Mehrere Draw-Datensätze in einem Buffer liegen jeweils im Abstand von 16 Byte.
  • firstInstance ungleich 0 ist nur erlaubt, wenn das Gerät mit dem Feature "indirect-first-instance" angefordert wurde. Sonst wird der Draw-Aufruf stillschweigend übersprungen.
  • Zu große Werte (etwa mehr Vertices, als der Vertex-Buffer hergibt) führen nicht zum Absturz. Die Implementierung prüft oder beschränkt sie, und der Aufruf zeichnet dann unter Umständen nichts.
  • Bei indizierter Geometrie verwendest du stattdessen drawIndexedIndirect() (fünf Werte, 20 Byte).
  • Kompatibilität: WebGPU ist in Chromium-Browsern stabil verfügbar. In Safari und Firefox ist die Unterstützung neuer und hängt noch teilweise von der Plattform ab. Prüfe daher immer zuerst navigator.gpu und ob requestAdapter() einen Adapter liefert.