Signatur
Beschreibung
drawIndirect() funktioniert wie draw(), nur liest die GPU die Zeichenparameter zur Laufzeit direkt aus einem Puffer. Ab indirectOffset erwartet sie vier aufeinanderfolgende vorzeichenlose 32-Bit-Ganzzahlen (insgesamt 16 Byte):
vertexCount: Anzahl der zu zeichnenden VerticesinstanceCount: Anzahl der InstanzenfirstVertex: Offset im Vertex-Puffer (in Vertices)firstInstance: erste Instanz-ID
Der Hauptvorteil: Die Parameter können von der GPU selbst erzeugt werden, zum Beispiel durch einen Compute-Shader beim GPU-Culling, in Partikelsystemen oder bei prozeduraler Geometrie. Die Daten müssen dafür nie zurück zur CPU gelesen werden. Das spart teure Synchronisationen wie mapAsync() und hält die komplette Pipeline auf der GPU.
Vor dem Aufruf müssen wie bei draw() eine Pipeline (setPipeline()) sowie die nötigen Vertex-Puffer und Bind Groups gesetzt sein. Der Puffer braucht das Usage-Flag GPUBufferUsage.INDIRECT. Für indizierte Geometrie gibt es die Variante drawIndexedIndirect(), die fünf Werte (20 Byte) erwartet.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $indirectBuffer Pflicht | GPUBuffer | Puffer mit den Zeichenparametern. Muss mit GPUBufferUsage.INDIRECT erstellt worden sein. |
|
| $indirectOffset Pflicht | number | Byte-Offset im Puffer, an dem die vier u32-Werte beginnen. Muss ein Vielfaches von 4 sein, und indirectOffset + 16 darf die Puffergröße nicht überschreiten. |
Rückgabewert
queue.submit() abgeschickt wird.Beispiele
Einfaches indirektes Zeichnen eines Dreiecks
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
// Parameter: vertexCount, instanceCount, firstVertex, firstInstance
const drawArgs = new Uint32Array([3, 1, 0, 0]);
const indirectBuffer = device.createBuffer({
size: drawArgs.byteLength, // 16 Byte
usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(indirectBuffer, 0, drawArgs);
// ... pipeline und context wurden vorher erstellt
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
clearValue: { r: 0, g: 0, b: 0, a: 1 },
loadOp: 'clear',
storeOp: 'store',
}],
});
pass.setPipeline(pipeline);
pass.drawIndirect(indirectBuffer, 0); // entspricht pass.draw(3, 1, 0, 0)
pass.end();
device.queue.submit([encoder.finish()]);
Compute-Shader bestimmt die Instanz-Anzahl (GPU-Culling)
// Der Compute-Shader zählt sichtbare Objekte und schreibt instanceCount
const cullingShader = device.createShaderModule({
code: /* wgsl */ `
struct DrawArgs {
vertexCount: u32,
instanceCount: atomic<u32>,
firstVertex: u32,
firstInstance: u32,
};
@group(0) @binding(0) var<storage, read_write> args: DrawArgs;
@group(0) @binding(1) var<storage, read> objects: array<vec4f>;
@group(0) @binding(2) var<storage, read_write> visible: array<u32>;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3u) {
if (id.x >= arrayLength(&objects)) { return; }
if (objects[id.x].w > 0.0) { // vereinfachter Sichtbarkeitstest
let slot = atomicAdd(&args.instanceCount, 1u);
visible[slot] = id.x;
}
}`,
});
const indirectBuffer = device.createBuffer({
size: 16,
usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
});
// Pro Frame zurücksetzen: 36 Vertices (Würfel), 0 Instanzen
device.queue.writeBuffer(indirectBuffer, 0, new Uint32Array([36, 0, 0, 0]));
const encoder = device.createCommandEncoder();
const cpass = encoder.beginComputePass();
cpass.setPipeline(cullingPipeline);
cpass.setBindGroup(0, cullingBindGroup);
cpass.dispatchWorkgroups(Math.ceil(objectCount / 64));
cpass.end();
const rpass = encoder.beginRenderPass(renderPassDescriptor);
rpass.setPipeline(renderPipeline);
rpass.setBindGroup(0, renderBindGroup);
rpass.setVertexBuffer(0, cubeVertexBuffer);
rpass.drawIndirect(indirectBuffer, 0); // Kein Zurücklesen auf die CPU nötig
rpass.end();
device.queue.submit([encoder.finish()]);
Mehrere Draw-Aufrufe aus einem gemeinsamen Puffer
// Drei Objekte, je 16 Byte Parameter hintereinander
const args = new Uint32Array([
3, 1, 0, 0, // Dreieck
6, 10, 3, 0, // 10 Quads ab Vertex 3
36, 4, 9, 0, // 4 Würfel ab Vertex 9
]);
const indirectBuffer = device.createBuffer({
size: args.byteLength,
usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(indirectBuffer, 0, args);
const STRIDE = 4 * Uint32Array.BYTES_PER_ELEMENT; // 16 Byte
for (let i = 0; i < 3; i++) {
pass.drawIndirect(indirectBuffer, i * STRIDE);
}
console.log(`Offsets: ${[0, 1, 2].map((i) => i * STRIDE).join(', ')}`);