Signatur
Beschreibung
draw() nimmt einen Zeichenbefehl in einen GPURenderBundleEncoder auf. Die GPU führt dabei nichts sofort aus. Der Befehl wird im Bundle gespeichert und erst ausgeführt, wenn das fertige GPURenderBundle über GPURenderPassEncoder.executeBundles() in einem Render-Pass abgespielt wird. Pro Instanz verarbeitet der Vertex-Shader vertexCount Vertices, beginnend bei firstVertex.
Render-Bundles eignen sich für statische Szenenteile, die in jedem Frame gleich gezeichnet werden, etwa Gelände, Hintergrundobjekte oder UI-Geometrie. Die Befehlsfolge wird einmal aufgezeichnet und dann beliebig oft wiederverwendet. Das spart pro Frame Aufwand in JavaScript und bei der Validierung.
Vor draw() müssen im Bundle-Encoder eine passende Pipeline (setPipeline()), alle nötigen Bind-Groups (setBindGroup()) und die Vertex-Puffer (setVertexBuffer()) gesetzt sein. Zustand aus dem Render-Pass wird nicht übernommen: Jedes Bundle beginnt mit leerem Zustand.
Die Methode entspricht GPURenderPassEncoder.draw(). Für indizierte Geometrie gibt es drawIndexed(), für Parameter aus einem GPU-Puffer drawIndirect().
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $vertexCount Pflicht | number | Anzahl der zu zeichnenden Vertices (vorzeichenlose 32-Bit-Ganzzahl). | |
| $instanceCount | number | 1 | Anzahl der zu zeichnenden Instanzen. Im Shader über @builtin(instance_index) abfragbar. |
| $firstVertex | number | 0 | Vertex-Offset in den Vertex-Puffern, ab dem gezeichnet wird. Entspricht dem Startwert von @builtin(vertex_index). |
| $firstInstance | number | 0 | Index der ersten Instanz. Wirkt auf Vertex-Puffer mit stepMode: "instance". |
Rückgabewert
Beispiele
Dreieck in ein Render-Bundle aufzeichnen und abspielen
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const format = navigator.gpu.getPreferredCanvasFormat();
const context = document.querySelector('canvas').getContext('webgpu');
context.configure({ device, format });
const module = device.createShaderModule({ code: `
@vertex fn vs(@location(0) pos: vec2f) -> @builtin(position) vec4f {
return vec4f(pos, 0.0, 1.0);
}
@fragment fn fs() -> @location(0) vec4f {
return vec4f(1.0, 0.4, 0.0, 1.0);
}` });
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: {
module, entryPoint: 'vs',
buffers: [{ arrayStride: 8, attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x2' }] }]
},
fragment: { module, entryPoint: 'fs', targets: [{ format }] }
});
const vertices = new Float32Array([0, 0.5, -0.5, -0.5, 0.5, -0.5]);
const vertexBuffer = device.createBuffer({
size: vertices.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(vertexBuffer, 0, vertices);
// Bundle einmalig aufzeichnen
const bundleEncoder = device.createRenderBundleEncoder({ colorFormats: [format] });
bundleEncoder.setPipeline(pipeline);
bundleEncoder.setVertexBuffer(0, vertexBuffer);
bundleEncoder.draw(3);
const bundle = bundleEncoder.finish();
// In jedem Frame wiederverwenden
const frame = () => {
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 1]
}]
});
pass.executeBundles([bundle]);
pass.end();
device.queue.submit([encoder.finish()]);
requestAnimationFrame(frame);
};
requestAnimationFrame(frame);
Instanziertes Zeichnen: 100 Instanzen eines Quads
// Voraussetzung: device, format, pipeline mit zwei Vertex-Puffern
// (Puffer 0: stepMode 'vertex', Puffer 1: stepMode 'instance')
const bundleEncoder = device.createRenderBundleEncoder({ colorFormats: [format] });
bundleEncoder.setPipeline(pipeline);
bundleEncoder.setVertexBuffer(0, quadBuffer); // 6 Vertices (2 Dreiecke)
bundleEncoder.setVertexBuffer(1, offsetBuffer); // 100 Offsets pro Instanz
bundleEncoder.draw(6, 100); // 6 Vertices × 100 Instanzen
const particlesBundle = bundleEncoder.finish();
Teilbereiche eines Puffers mit firstVertex zeichnen
// Ein Puffer enthält mehrere Formen hintereinander:
// Vertices 0–2: Dreieck, Vertices 3–8: Quadrat
const bundleEncoder = device.createRenderBundleEncoder({ colorFormats: [format] });
bundleEncoder.setPipeline(pipeline);
bundleEncoder.setVertexBuffer(0, sharedBuffer);
bundleEncoder.draw(3, 1, 0); // nur das Dreieck
bundleEncoder.draw(6, 1, 3); // nur das Quadrat
const shapesBundle = bundleEncoder.finish();
// Wichtig · Fallstricke
- Kein geerbter Zustand: Ein Render-Bundle übernimmt Pipeline, Bind-Groups und Vertex-Puffer nicht aus dem Render-Pass. Umgekehrt ist der Zustand des Passes nach
executeBundles()zurückgesetzt, und alles muss neu gesetzt werden. - Fehler erscheinen verzögert: Ungültige Aufrufe werfen keine Ausnahme. Das gilt etwa für fehlende Pipeline, fehlende Vertex-Puffer oder Zugriffe über die Puffergrenzen hinaus. Stattdessen wird der Encoder ungültig, und der Fehler zeigt sich bei
finish()als Validierungsfehler. Solche Fehler lassen sich mitdevice.pushErrorScope('validation')abfangen. - Formate müssen passen:
colorFormats,depthStencilFormatundsampleCountdes Bundle-Encoders müssen zur Pipeline und später zum Render-Pass passen, sonst schlägtexecuteBundles()fehl. firstInstanceungleich 0 beidrawIndirect()erfordert das Feature"indirect-first-instance". Beim direktendraw()ist der Parameter immer erlaubt.- Kompatibilität: WebGPU steht nur in sicheren Kontexten zur Verfügung, und die Browser-Unterstützung ist noch lückenhaft. Prüfe vorher, ob
navigator.gpuexistiert, und sieh einen Fallback wie WebGL vor.