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

GPURenderPassEncoder.drawIndirect()

Methode

Zeichnet Primitive, wobei Vertex-Anzahl, Instanz-Anzahl und Startwerte nicht als Argumente übergeben, sondern aus einem <code>GPUBuffer</code> gelesen werden.

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

Signatur

GPURenderPassEncoder.prototype.drawIndirect(indirectBuffer, indirectOffset)

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 Vertices
  • instanceCount: Anzahl der Instanzen
  • firstVertex: 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

Typ
undefined
Beschreibung
Kein Rückgabewert. Der Befehl wird im Render-Pass aufgezeichnet und erst ausgeführt, wenn der Command Buffer mit 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(', ')}`);
Offsets: 0, 16, 32