Start · Sprachen · JavaScript · Referenz · GPURenderPassEncoder.drawIndexedIndirect()

GPURenderPassEncoder.drawIndexedIndirect()

Methode

Zeichnet indizierte Primitive. Die Zeichenparameter (Indexanzahl, Instanzen, Offsets) liest die Methode dabei direkt aus einem <code>GPUBuffer</code> auf der GPU.

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

Signatur

GPURenderPassEncoder.prototype.drawIndexedIndirect(indirectBuffer, indirectOffset)

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 Indizes
  • instanceCount (Uint32): Anzahl der Instanzen
  • firstIndex (Uint32): Startposition im Indexpuffer
  • baseVertex (Int32, vorzeichenbehaftet): Wert, der vor dem Zugriff auf den Vertexpuffer zu jedem Index addiert wird
  • firstInstance (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

Typ
undefined
Beschreibung
Kein Rückgabewert. Der Befehl wird im Encoder aufgezeichnet und erst beim Absenden des Command-Buffers über 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`);
3 indirekte Draw-Aufrufe aufgezeichnet