Signatur
Beschreibung
drawIndexedIndirect() ist die indirekte Variante von drawIndexed(). Die Zeichenparameter übergeben Sie nicht als JavaScript-Argumente. Die GPU liest sie zur Ausführungszeit aus einem Puffer. Ab der Byte-Position indirectOffset müssen dort fünf aufeinanderfolgende 32-Bit-Werte liegen:
indexCount(Uint32): Anzahl der zu zeichnenden IndizesinstanceCount(Uint32): Anzahl der InstanzenfirstIndex(Uint32): Startposition im IndexpufferbaseVertex(Int32, vorzeichenbehaftet): Wert, der vor dem Zugriff auf den Vertexpuffer zu jedem Index addiert wirdfirstInstance(Uint32): erste Instanz-ID. Werte ungleich 0 setzen das Feature"indirect-first-instance"voraus.
Wirklich nützlich wird die Methode im Zusammenspiel mit Compute-Shadern. Ein Compute-Pass kann zum Beispiel Frustum- oder Occlusion-Culling ausführen, LOD-Stufen auswählen oder Partikel zählen. Das Ergebnis schreibt er direkt in den Indirect-Puffer. Die CPU muss diese Daten nie zurücklesen. So entfällt eine teure Synchronisierung zwischen GPU und CPU (GPU-driven Rendering).
Vor dem Aufruf müssen im Render-Pass eine Pipeline (setPipeline()), ein Indexpuffer (setIndexBuffer()) und alle benötigten Vertexpuffer und Bind Groups gesetzt sein. Der Indirect-Puffer braucht beim Erstellen das Usage-Flag GPUBufferUsage.INDIRECT.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $indirectBuffer Pflicht | GPUBuffer | Puffer mit den fünf Zeichenparametern (insgesamt 20 Bytes). Er muss mit GPUBufferUsage.INDIRECT erstellt worden sein. |
|
| $indirectOffset Pflicht | number | Byte-Offset im Puffer, an dem die Parameter beginnen. Er muss ein Vielfaches von 4 sein, und indirectOffset + 20 darf die Puffergröße nicht überschreiten. |
Rückgabewert
queue.submit() ausgeführt.Beispiele
Indirect-Puffer von der CPU befüllen und zeichnen
// Voraussetzung: device, context, pipeline, vertexBuffer und indexBuffer existieren bereits
const indexCount = 36; // z. B. ein Würfel
// 5 Werte à 4 Bytes; baseVertex (Index 3) ist ein Int32
const args = new ArrayBuffer(20);
const u32 = new Uint32Array(args);
const i32 = new Int32Array(args);
u32[0] = indexCount; // indexCount
u32[1] = 100; // instanceCount
u32[2] = 0; // firstIndex
i32[3] = 0; // baseVertex
u32[4] = 0; // firstInstance
const indirectBuffer = device.createBuffer({
size: 20,
usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(indirectBuffer, 0, args);
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.setVertexBuffer(0, vertexBuffer);
pass.setIndexBuffer(indexBuffer, 'uint16');
pass.drawIndexedIndirect(indirectBuffer, 0);
pass.end();
device.queue.submit([encoder.finish()]);
GPU-driven Culling: Compute-Shader setzt instanceCount
// Der Compute-Shader zählt sichtbare Instanzen atomar in den Indirect-Puffer
const cullShader = device.createShaderModule({ code: `
struct DrawArgs {
indexCount: u32,
instanceCount: atomic<u32>,
firstIndex: u32,
baseVertex: i32,
firstInstance: u32,
};
@group(0) @binding(0) var<storage, read> spheres: array<vec4f>;
@group(0) @binding(1) var<storage, read_write> drawArgs: DrawArgs;
@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(&spheres)) { return; }
let s = spheres[id.x];
if (s.z - s.w < 100.0) { // stark vereinfachter Sichtbarkeitstest
let slot = atomicAdd(&drawArgs.instanceCount, 1u);
visible[slot] = id.x;
}
}
`});
const drawArgsBuffer = device.createBuffer({
size: 20,
usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
});
// Pro Frame zurücksetzen: indexCount fest, instanceCount = 0
device.queue.writeBuffer(drawArgsBuffer, 0, new Uint32Array([indexCount, 0, 0, 0, 0]));
const encoder = device.createCommandEncoder();
const cPass = encoder.beginComputePass();
cPass.setPipeline(cullPipeline); // aus cullShader erstellt
cPass.setBindGroup(0, cullBindGroup);
cPass.dispatchWorkgroups(Math.ceil(objectCount / 64));
cPass.end();
const rPass = encoder.beginRenderPass(renderPassDescriptor);
rPass.setPipeline(renderPipeline);
rPass.setBindGroup(0, renderBindGroup); // enthält den Puffer 'visible'
rPass.setVertexBuffer(0, vertexBuffer);
rPass.setIndexBuffer(indexBuffer, 'uint32');
rPass.drawIndexedIndirect(drawArgsBuffer, 0); // die CPU kennt die Instanzzahl nicht
rPass.end();
device.queue.submit([encoder.finish()]);
Mehrere Draw-Aufrufe aus einem gemeinsamen Puffer
// Drei Meshes liegen im selben Vertex- und Indexpuffer; je 20 Bytes Argumente pro Mesh
const meshes = [
{ indexCount: 36, firstIndex: 0, baseVertex: 0 },
{ indexCount: 960, firstIndex: 36, baseVertex: 24 },
{ indexCount: 6, firstIndex: 996, baseVertex: 600 },
];
const STRIDE = 20;
const data = new ArrayBuffer(meshes.length * STRIDE);
const view = new DataView(data);
meshes.forEach((m, i) => {
const o = i * STRIDE;
view.setUint32(o, m.indexCount, true);
view.setUint32(o + 4, 1, true); // instanceCount
view.setUint32(o + 8, m.firstIndex, true);
view.setInt32(o + 12, m.baseVertex, true);
view.setUint32(o + 16, 0, true); // firstInstance
});
const indirect = device.createBuffer({
size: data.byteLength,
usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(indirect, 0, data);
// innerhalb des Render-Pass:
meshes.forEach((_, i) => pass.drawIndexedIndirect(indirect, i * STRIDE));
console.log(`${meshes.length} indirekte Draw-Aufrufe aufgezeichnet`);