Signatur
Beschreibung
drawIndexedIndirect() zeichnet indizierte Primitive, wobei die Zeichenparameter nicht als JavaScript-Argumente übergeben werden, sondern erst bei der Ausführung auf der GPU aus einem Puffer gelesen werden. Ab indirectOffset müssen dort fünf 32-Bit-Werte liegen: indexCount, instanceCount, firstIndex, baseVertex (vorzeichenbehaftet, Int32) und firstInstance – zusammen 20 Byte.
Die Methode ist dann nützlich, wenn die GPU selbst entscheidet, wie viel gezeichnet wird, z. B. nach Frustum- oder Occlusion-Culling in einem Compute-Shader, bei GPU-gesteuerter LOD-Auswahl oder bei Partikelsystemen. So bleiben die Daten vollständig auf der GPU, und es ist kein Rücklesen zur CPU nötig.
Auf einem GPURenderBundleEncoder wird der Befehl nur aufgezeichnet. Das mit finish() erzeugte GPURenderBundle kann danach in beliebig vielen Render-Passes mit GPURenderPassEncoder.executeBundles() wiederverwendet werden. Da der Puffer erst beim Ausführen gelesen wird, übernimmt jede Ausführung des Bundles automatisch die aktuellen Pufferwerte. Das passt gut zu Render-Bundles, deren Befehle ja unveränderlich sind.
Vorher müssen eine Render-Pipeline (setPipeline()), ein Indexpuffer (setIndexBuffer()) sowie die benötigten Vertex-Puffer und Bind-Groups gesetzt sein.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $indirectBuffer Pflicht | GPUBuffer | Puffer mit den Zeichenparametern. Er muss mit dem Nutzungsflag GPUBufferUsage.INDIRECT erstellt worden sein. |
|
| $indirectOffset Pflicht | number | Byte-Offset im Puffer, an dem die fünf 32-Bit-Werte beginnen. Muss ein Vielfaches von 4 sein, und indirectOffset + 20 darf die Puffergröße nicht überschreiten. |
Rückgabewert
finish() als ungültiges Bundle bzw. als Fehler auf dem Gerät.Beispiele
Indirekten Puffer anlegen und Bundle aufzeichnen
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const format = navigator.gpu.getPreferredCanvasFormat();
// Parameter: indexCount, instanceCount, firstIndex, baseVertex, firstInstance
const indirectBuffer = device.createBuffer({
size: 20,
usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(indirectBuffer, 0, new Uint32Array([6, 1, 0, 0, 0]));
// pipeline, vertexBuffer und indexBuffer wurden vorher erstellt
const bundleEncoder = device.createRenderBundleEncoder({
colorFormats: [format],
});
bundleEncoder.setPipeline(pipeline);
bundleEncoder.setVertexBuffer(0, vertexBuffer);
bundleEncoder.setIndexBuffer(indexBuffer, 'uint16');
bundleEncoder.drawIndexedIndirect(indirectBuffer, 0);
const bundle = bundleEncoder.finish();
Bundle in jedem Frame ausführen
const frame = () => {
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
loadOp: 'clear',
clearValue: { r: 0, g: 0, b: 0, a: 1 },
storeOp: 'store',
}],
});
pass.executeBundles([bundle]); // liest indirectBuffer bei jeder Ausführung neu
pass.end();
device.queue.submit([encoder.finish()]);
requestAnimationFrame(frame);
};
requestAnimationFrame(frame);
Instanzanzahl per Compute-Shader bestimmen (GPU-Culling)
// Der Puffer darf zusätzlich als Storage-Puffer beschrieben werden
const drawArgs = device.createBuffer({
size: 20,
usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
});
// indexCount = 36 (Würfel), instanceCount wird vom Compute-Shader gesetzt
device.queue.writeBuffer(drawArgs, 0, new Uint32Array([36, 0, 0, 0, 0]));
// WGSL-Ausschnitt im Culling-Shader:
// @group(0) @binding(0) var<storage, read_write> args: array<atomic<u32>, 5>;
// if (visible) { atomicAdd(&args[1], 1u); }
const enc = device.createRenderBundleEncoder({ colorFormats: [format] });
enc.setPipeline(pipeline);
enc.setVertexBuffer(0, cubeVertices);
enc.setIndexBuffer(cubeIndices, 'uint16');
enc.drawIndexedIndirect(drawArgs, 0);
const cullBundle = enc.finish();
// Pro Frame: erst Compute-Pass (Culling), dann executeBundles([cullBundle])
// Wichtig · Fallstricke
- Ohne
GPUBufferUsage.INDIRECTist der Aufruf ungültig. Dann wird das gesamte Bundle ungültig, und es gibt einen Validierungsfehler auf demGPUDevice(abfangbar mitpushErrorScope('validation')). indirectOffsetmuss durch 4 teilbar sein. Wenn du mehrere Zeichenbefehle in einem Puffer ablegst, nutze einen Abstand von 20 Byte pro Eintrag.baseVertexist vorzeichenbehaftet (i32). Negative Werte schreibst du am besten über eineInt32Array-Sicht auf denselbenArrayBuffer.firstInstanceungleich 0 erfordert das Feature"indirect-first-instance"beim Anfordern des Geräts. Ohne dieses Feature wird der Zeichenbefehl stillschweigend verworfen.- Liegen die gelesenen Indizes außerhalb des gebundenen Indexpuffers, wird dieser Zeichenbefehl nicht ausgeführt. Es gibt dafür keinen JavaScript-Fehler.
- Ein Bundle übernimmt keinen Zustand aus dem Render-Pass. Pipeline, Puffer und Bind-Groups müssen deshalb im Bundle selbst gesetzt werden.
- Browser-Unterstützung: WebGPU gibt es nur in sicheren Kontexten. In Firefox und Safari ist die Unterstützung noch neu bzw. plattformabhängig. Prüfe vorher, ob
navigator.gpuvorhanden ist.