Signatur
Beschreibung
drawIndexed() zeichnet innerhalb eines Render-Passes Primitiven, deren Eckpunkte über einen Indexpuffer referenziert werden. Statt jeden Vertex für jedes Dreieck erneut abzulegen, speichert man jeden Eckpunkt nur einmal im Vertexpuffer. Der Indexpuffer legt dann fest, in welcher Reihenfolge die Eckpunkte zu Dreiecken, Linien oder Punkten zusammengesetzt werden. Das spart Speicher und Bandbreite, zum Beispiel bei Meshes, in denen sich viele Dreiecke Eckpunkte teilen.
Vor dem Aufruf müssen gesetzt sein: eine Render-Pipeline über setPipeline(), alle von der Pipeline erwarteten Vertexpuffer über setVertexBuffer(), ein Indexpuffer über setIndexBuffer(buffer, "uint16" | "uint32") und die nötigen Bind-Groups über setBindGroup(). Der Aufruf wird nur in den Befehlspuffer aufgezeichnet. Die GPU führt ihn erst aus, wenn der Puffer über device.queue.submit() übermittelt wird.
Mit instanceCount und firstInstance lässt sich Instancing umsetzen: Dasselbe Mesh wird mehrfach gezeichnet, und der Shader kann die Instanzen über @builtin(instance_index) unterscheiden. baseVertex wird zu jedem gelesenen Indexwert addiert. So können mehrere Meshes in einem gemeinsamen Vertexpuffer liegen und trotzdem Indizes verwenden, die jeweils bei 0 beginnen.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $indexCount Pflicht | number | Anzahl der Indizes, die aus dem Indexpuffer gelesen und gezeichnet werden. Bei Dreiecken meist ein Vielfaches von 3. | |
| $instanceCount | number | 1 | Anzahl der zu zeichnenden Instanzen. |
| $firstIndex | number | 0 | Position im Indexpuffer (in Indizes gezählt, nicht in Bytes), ab der gelesen wird. Sie wird zum Offset aus setIndexBuffer() addiert. |
| $baseVertex | number | 0 | Wert, der vor dem Zugriff auf den Vertexpuffer zu jedem Indexwert addiert wird. Er darf negativ sein (vorzeichenbehaftete 32-Bit-Ganzzahl). |
| $firstInstance | number | 0 | Instanz-Index, mit dem gezählt wird. Er wirkt auf instance_index und auf Vertexpuffer mit stepMode: "instance". |
Rückgabewert
Beispiele
Quadrat aus zwei Dreiecken mit Indexpuffer zeichnen
// Voraussetzung: device, context, pipeline existieren bereits
const vertices = new Float32Array([
// x, y
-0.5, -0.5,
0.5, -0.5,
0.5, 0.5,
-0.5, 0.5,
]);
const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
const vertexBuffer = device.createBuffer({
size: vertices.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(vertexBuffer, 0, vertices);
const indexBuffer = device.createBuffer({
size: indices.byteLength,
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(indexBuffer, 0, indices);
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
clearValue: { r: 0.1, g: 0.1, b: 0.1, a: 1 },
loadOp: 'clear',
storeOp: 'store',
}],
});
pass.setPipeline(pipeline);
pass.setVertexBuffer(0, vertexBuffer);
pass.setIndexBuffer(indexBuffer, 'uint16');
pass.drawIndexed(indices.length); // 6 Indizes = 2 Dreiecke
pass.end();
device.queue.submit([encoder.finish()]);
Instancing: dasselbe Mesh 100-mal zeichnen
// Im WGSL-Shader: @builtin(instance_index) idx : u32
// liefert pro Instanz den Wert 0..99, z. B. als Index in ein Offset-Array
pass.setPipeline(pipeline);
pass.setBindGroup(0, offsetsBindGroup);
pass.setVertexBuffer(0, cubeVertexBuffer);
pass.setIndexBuffer(cubeIndexBuffer, 'uint16');
pass.drawIndexed(36, 100); // 36 Indizes pro Würfel, 100 Instanzen
Mehrere Meshes in gemeinsamen Puffern mit firstIndex und baseVertex
// Alle Meshes liegen hintereinander in einem Vertex- und einem Indexpuffer
const meshes = [
{ name: 'Würfel', indexCount: 36, firstIndex: 0, baseVertex: 0 },
{ name: 'Pyramide', indexCount: 18, firstIndex: 36, baseVertex: 24 },
];
pass.setPipeline(pipeline);
pass.setVertexBuffer(0, sharedVertexBuffer);
pass.setIndexBuffer(sharedIndexBuffer, 'uint32');
for (const m of meshes) {
// Die Indizes jedes Meshes beginnen bei 0 - baseVertex verschiebt sie
pass.drawIndexed(m.indexCount, 1, m.firstIndex, m.baseVertex, 0);
}
// Wichtig · Fallstricke
- Kein Exception-Wurf: Ungültige Aufrufe werfen keine JavaScript-Ausnahme. Das gilt zum Beispiel, wenn der Indexpuffer fehlt, ein Vertexpuffer fehlt oder
firstIndex + indexCountüber das Ende des Indexpuffers hinausgeht. Stattdessen wird der Encoder ungültig, und der Fehler erscheint erst beifinish()bzw.submit()als Validierungsfehler. Solche Fehler fängt man mitdevice.pushErrorScope('validation')/popErrorScope()oder über das Ereignisuncapturederrorab. - Indexformat: Das Format in
setIndexBuffer()("uint16"oder"uint32") muss zu den Daten passen. Bei Strip-Topologien ("triangle-strip","line-strip") muss zusätzlichprimitive.stripIndexFormatin der Pipeline übereinstimmen. Der Wert0xFFFFbzw.0xFFFFFFFFdient dort zum Neustart des Strips (primitive restart). - Ausrichtung:
firstIndexwird in Indizes gezählt, nicht in Bytes. Der Offset insetIndexBuffer()wird dagegen in Bytes angegeben und muss ein Vielfaches der Indexgröße (2 oder 4 Byte) sein. - Für
firstInstanceungleich 0 kann das Feature"indirect-first-instance"nötig sein, allerdings nur beidrawIndexedIndirect(). Bei direktemdrawIndexed()ist es immer erlaubt. - Browser-Kompatibilität: WebGPU steht nur in sicheren Kontexten (HTTPS) zur Verfügung und ist noch nicht in allen Browsern bzw. auf allen Plattformen aktiv. Vor der Nutzung sollte man
navigator.gpuprüfen und einen Fallback (z. B. WebGL) bereithalten.