Signatur
Beschreibung
Mit finish() beendest du die Befehlsaufzeichnung eines GPURenderBundleEncoder. Alle vorher aufgerufenen Befehle wie setPipeline(), setBindGroup(), setVertexBuffer(), setIndexBuffer(), draw() oder drawIndexed() werden in ein unveränderliches GPURenderBundle-Objekt übernommen.
Render-Bundles lohnen sich, wenn du dieselbe Folge von Zeichenbefehlen in jedem Frame oder in mehreren Render-Passes wiederholst, zum Beispiel für statische Szenenteile, UI-Ebenen oder viele gleichartige Objekte. Du zeichnest die Befehle einmal auf und spielst sie danach mit GPURenderPassEncoder.executeBundles() beliebig oft ab. Das spart CPU-Aufwand für JavaScript-Aufrufe und Validierung.
Nach finish() ist der Encoder verbraucht. Du kannst keine weiteren Befehle mehr aufzeichnen, dafür brauchst du einen neuen Encoder über GPUDevice.createRenderBundleEncoder(). Das Bundle ist nur mit Render-Passes kompatibel, deren Anhang-Formate (colorFormats, depthStencilFormat) und sampleCount zum Deskriptor des Encoders passen.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $descriptor | GPURenderBundleDescriptor | {} | Optionales Objekt zur Konfiguration des Bundles. |
| $descriptor.label | string | "" | Bezeichnung des Bundles. Sie taucht in Fehlermeldungen und Debug-Werkzeugen auf und hilft bei der Fehlersuche. |
Rückgabewert
GPURenderPassEncoder.executeBundles().Beispiele
Statisches Dreieck als Bundle aufzeichnen und abspielen
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const canvas = document.querySelector('canvas');
const context = canvas.getContext('webgpu');
const format = navigator.gpu.getPreferredCanvasFormat();
context.configure({ device, format });
const module = device.createShaderModule({
code: `
@vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f {
var p = array<vec2f, 3>(vec2f(0, 0.5), vec2f(-0.5, -0.5), vec2f(0.5, -0.5));
return vec4f(p[i], 0, 1);
}
@fragment fn fs() -> @location(0) vec4f { return vec4f(1, 0.4, 0, 1); }
`
});
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: { module, entryPoint: 'vs' },
fragment: { module, entryPoint: 'fs', targets: [{ format }] }
});
// Bundle einmalig aufzeichnen
const bundleEncoder = device.createRenderBundleEncoder({ colorFormats: [format] });
bundleEncoder.setPipeline(pipeline);
bundleEncoder.draw(3);
const bundle = bundleEncoder.finish({ label: 'Dreieck-Bundle' });
console.log(bundle.label);
// In jedem Frame abspielen
const frame = () => {
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
loadOp: 'clear',
clearValue: { r: 0, g: 0, b: 0, a: 1 },
storeOp: 'store'
}]
});
pass.executeBundles([bundle]);
pass.end();
device.queue.submit([encoder.finish()]);
requestAnimationFrame(frame);
};
requestAnimationFrame(frame);
Mehrere Objekte in einem Bundle zusammenfassen
// Annahme: device, pipeline, format sowie meshes = [{ vertexBuffer, indexBuffer, indexCount, bindGroup }] existieren
const createSceneBundle = (device, pipeline, format, meshes) => {
const enc = device.createRenderBundleEncoder({
label: 'Szene-Encoder',
colorFormats: [format],
depthStencilFormat: 'depth24plus'
});
enc.setPipeline(pipeline);
for (const mesh of meshes) {
enc.setBindGroup(0, mesh.bindGroup);
enc.setVertexBuffer(0, mesh.vertexBuffer);
enc.setIndexBuffer(mesh.indexBuffer, 'uint16');
enc.drawIndexed(mesh.indexCount);
}
return enc.finish({ label: `Szene (${meshes.length} Meshes)` });
};
const sceneBundle = createSceneBundle(device, pipeline, format, meshes);
console.log(sceneBundle.label);
Fehler: Encoder nach finish() weiterverwenden
const enc = device.createRenderBundleEncoder({ colorFormats: [format] });
enc.setPipeline(pipeline);
enc.draw(3);
const bundle = enc.finish();
device.pushErrorScope('validation');
enc.draw(3); // ungültig, der Encoder ist bereits abgeschlossen
const error = await device.popErrorScope();
console.log(error ? 'Validierungsfehler erkannt' : 'kein Fehler');
// Wichtig · Fallstricke
- Einmalig: Du kannst
finish()pro Encoder nur einmal aufrufen. Danach ist der Encoder ungültig und jeder weitere Befehl löst einen Validierungsfehler aus. - Fehler kommen verzögert: Fehler bei der Aufzeichnung, etwa eine fehlende Pipeline oder inkompatible Formate, führen nicht zu einer Exception.
finish()gibt dann ein ungültiges Bundle zurück. Den Fehler selbst meldet WebGPU überpushErrorScope()/popErrorScope()oder das Ereignisuncapturederror. - Kompatibilität:
colorFormats,depthStencilFormatundsampleCountdes Bundles müssen exakt zum Render-Pass passen, in dem duexecuteBundles()aufrufst. - Zustand wird nicht übernommen: Ein Bundle erbt keinen Zustand vom Render-Pass, zum Beispiel Pipeline oder Bind Groups. Nach
executeBundles()wird der Zustand des Passes zurückgesetzt, du musst ihn also bei Bedarf neu setzen. - Nicht erlaubt in Bundles:
setViewport(),setScissorRect(),setBlendConstant()undsetStencilReference()stehen im Bundle-Encoder nicht zur Verfügung. - Browser-Unterstützung: WebGPU gibt es nur in sicheren Kontexten und noch nicht in allen Browsern bzw. auf allen Plattformen. Prüfe vorher
navigator.gpu.