Signatur
Beschreibung
drawIndexed() schreibt einen indizierten Zeichenbefehl in einen GPURenderBundleEncoder. Die Methode zeichnet noch nichts. Sie zeichnet den Befehl nur auf. Mit finish() wird daraus ein GPURenderBundle, das man mit GPURenderPassEncoder.executeBundles() in einem oder mehreren Render-Passes beliebig oft abspielen kann.
Beim indizierten Zeichnen liest die GPU Indizes aus dem Indexpuffer, der mit setIndexBuffer() gesetzt wurde. Über diese Indizes greift sie auf die Vertizes in den Vertexpuffern zu. So lassen sich gemeinsame Eckpunkte wiederverwenden. Ein Quadrat braucht dann zum Beispiel nur 4 Vertizes und 6 Indizes statt 6 Vertizes. Das spart Speicher und Arbeit im Vertex-Shader.
Render-Bundles eignen sich für statische Szenenteile, die jedes Frame gleich gezeichnet werden. Dazu gehören etwa Gelände, Gebäude oder UI-Geometrie. Der Aufwand für die Befehlsaufzeichnung in JavaScript fällt dann nur einmal an. Vor drawIndexed() müssen im Encoder eine Pipeline (setPipeline()), ein Indexpuffer, die benötigten Vertexpuffer und die Bind-Groups gesetzt sein.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $indexCount Pflicht | number | Anzahl der Indizes, die aus dem Indexpuffer gelesen und gezeichnet werden. | |
| $instanceCount | number | 1 | Anzahl der Instanzen, die gezeichnet werden (Instancing). |
| $firstIndex | number | 0 | Versatz in Indizes (nicht in Bytes) im Indexpuffer, ab dem gelesen wird. |
| $baseVertex | number | 0 | Wert, der zu jedem Index addiert wird, bevor der Vertexpuffer gelesen wird. Er darf negativ sein. |
| $firstInstance | number | 0 | Erste Instanz-ID (@builtin(instance_index)). Ein Wert ungleich 0 erfordert das Feature "indirect-first-instance" nur bei indirekten Aufrufen, hier ist er immer erlaubt. |
Rückgabewert
Beispiele
Quadrat aus zwei Dreiecken in ein Render-Bundle aufnehmen
// Voraussetzung: device, pipeline, vertexBuffer (4 Vertizes) und bindGroup existieren bereits
const indices = new Uint16Array([0, 1, 2, 2, 1, 3]);
const indexBuffer = device.createBuffer({
size: indices.byteLength,
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(indexBuffer, 0, indices);
const bundleEncoder = device.createRenderBundleEncoder({
colorFormats: [navigator.gpu.getPreferredCanvasFormat()],
});
bundleEncoder.setPipeline(pipeline);
bundleEncoder.setBindGroup(0, bindGroup);
bundleEncoder.setVertexBuffer(0, vertexBuffer);
bundleEncoder.setIndexBuffer(indexBuffer, 'uint16');
bundleEncoder.drawIndexed(indices.length); // 6 Indizes, 1 Instanz
const quadBundle = bundleEncoder.finish();
console.log(quadBundle instanceof GPURenderBundle);
Bundle in jedem Frame wiederverwenden
const frame = () => {
const commandEncoder = device.createCommandEncoder();
const pass = commandEncoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
loadOp: 'clear',
clearValue: { r: 0.1, g: 0.1, b: 0.1, a: 1 },
storeOp: 'store',
}],
});
// Aufgezeichnete drawIndexed()-Befehle abspielen
pass.executeBundles([quadBundle]);
pass.end();
device.queue.submit([commandEncoder.finish()]);
requestAnimationFrame(frame);
};
requestAnimationFrame(frame);
Mehrere Meshes aus einem gemeinsamen Puffer mit Instancing
// Ein gemeinsamer Vertex-/Indexpuffer enthält zwei Meshes:
// Mesh A: 36 Indizes ab Index 0, Vertizes ab 0
// Mesh B: 24 Indizes ab Index 36, Vertizes ab 24
bundleEncoder.setPipeline(pipeline);
bundleEncoder.setBindGroup(0, sceneBindGroup);
bundleEncoder.setVertexBuffer(0, sharedVertexBuffer);
bundleEncoder.setVertexBuffer(1, instanceBuffer); // stepMode: 'instance'
bundleEncoder.setIndexBuffer(sharedIndexBuffer, 'uint32');
// 100 Würfel (Mesh A)
bundleEncoder.drawIndexed(36, 100, 0, 0, 0);
// 50 Instanzen von Mesh B, Instanzdaten ab Eintrag 100
bundleEncoder.drawIndexed(24, 50, 36, 24, 100);
const sceneBundle = bundleEncoder.finish();
// Wichtig · Fallstricke
- Validierung ist verzögert: Fehlt ein Index- oder Vertexpuffer, ist keine Pipeline gesetzt oder liegt
firstIndex + indexCountaußerhalb des Indexpuffers, wirftdrawIndexed()keine Exception. Der Encoder wird ungültig, und der Fehler erscheint erst beifinish()als Validierungsfehler. Man fängt ihn mitdevice.pushErrorScope('validation')oder über das Ereignisuncapturederrorab. firstIndexwird in Indizes gezählt, nicht in Bytes. Der Byte-Versatz aussetIndexBuffer(buffer, format, offset)kommt noch dazu.- Das Indexformat (
'uint16'oder'uint32') muss zu den Daten passen. Bei Topologien mit Strips ('triangle-strip','line-strip') mussprimitive.stripIndexFormatder Pipeline mit dem Format des Indexpuffers übereinstimmen. - Die Formate im Deskriptor von
createRenderBundleEncoder()(colorFormats,depthStencilFormat,sampleCount) müssen zum Render-Pass passen, in dem das Bundle ausgeführt wird. - Nach
executeBundles()ist der Zustand des Render-Passes (Pipeline, Puffer, Bind-Groups) zurückgesetzt und muss für weitere direkte Zeichenbefehle neu gesetzt werden. - Kompatibilität: WebGPU gibt es nur in sicheren Kontexten und nicht in allen Browsern bzw. auf allen Plattformen. Prüfe vorher mit
if (!navigator.gpu)und halte eine Ausweichlösung bereit, etwa WebGL.