Signatur
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
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);
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 beifinish()bzw. überdevice.pushErrorScope()und nicht als JavaScript-Exception. - Alignment:
indirectOffsetmuss durch 4 teilbar sein. Mehrere Draw-Datensätze in einem Buffer liegen jeweils im Abstand von 16 Byte. firstInstanceungleich 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.gpuund obrequestAdapter()einen Adapter liefert.