Signatur
Beschreibung
GPURenderBundleEncoder ist Teil der WebGPU API. Mit ihm lassen sich Zeichenbefehle wie setPipeline(), setBindGroup(), setVertexBuffer(), setIndexBuffer(), draw() und drawIndexed() einmal vorab aufzeichnen. Mit finish() entsteht daraus ein unveränderliches GPURenderBundle, das in jedem Frame mit GPURenderPassEncoder.executeBundles() erneut ausgeführt werden kann.
Der Hauptnutzen ist die Entlastung der CPU: Bei statischen Szenen mit vielen Draw-Calls (Gelände, Gebäude, UI-Elemente) muss JavaScript die Befehle nicht in jedem Frame neu einreihen und prüfen. Die Validierung übernimmt der Browser schon beim Aufzeichnen. Instanzen legt man mit GPUDevice.createRenderBundleEncoder(descriptor) an. Der Deskriptor gibt die Formate der Render-Attachments (colorFormats, depthStencilFormat, sampleCount) vor. Diese müssen zum späteren Render-Pass passen.
Zur Verfügung stehen die gemeinsamen Methoden aus GPURenderCommandsMixin, GPUBindingCommandsMixin und GPUDebugCommandsMixin (pushDebugGroup(), popDebugGroup(), insertDebugMarker()), dazu finish() und die Eigenschaft label. Pass-bezogene Befehle fehlen dagegen: setViewport(), setScissorRect(), setBlendConstant(), setStencilReference() und Occlusion Queries gibt es in einem Bundle nicht. Sie gehören in den umgebenden Render-Pass.
Rückgabewert
GPUDevice.createRenderBundleEncoder() zurückgegeben. finish() liefert anschließend ein GPURenderBundle.Beispiele
Encoder erzeugen und ein Bundle aufzeichnen
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const format = navigator.gpu.getPreferredCanvasFormat();
const module = device.createShaderModule({
code: `
@vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f {
var p = array(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({
label: 'Dreieck-Bundle',
colorFormats: [format]
});
bundleEncoder.setPipeline(pipeline);
bundleEncoder.draw(3);
const bundle = bundleEncoder.finish({ label: 'Dreieck' });
console.log(bundle.label);
Bundle in jedem Frame abspielen
const canvas = document.querySelector('canvas');
const context = canvas.getContext('webgpu');
context.configure({ device, format });
const frame = () => {
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
loadOp: 'clear',
clearValue: { r: 0.1, g: 0.1, b: 0.1, a: 1 },
storeOp: 'store'
}]
});
// Viewport u. Ä. gehören in den Pass, nicht ins Bundle
pass.setViewport(0, 0, canvas.width, canvas.height, 0, 1);
pass.executeBundles([bundle]);
pass.end();
device.queue.submit([encoder.finish()]);
requestAnimationFrame(frame);
};
requestAnimationFrame(frame);
Viele statische Objekte mit Vertex-/Index-Buffern und Bind Groups
// meshes: [{ vertexBuffer, indexBuffer, indexCount, bindGroup }]
const recordScene = (device, pipeline, meshes, format) => {
const enc = device.createRenderBundleEncoder({
label: 'Statische Szene',
colorFormats: [format],
depthStencilFormat: 'depth24plus'
});
enc.setPipeline(pipeline);
enc.pushDebugGroup('Gebäude');
for (const mesh of meshes) {
enc.setBindGroup(0, mesh.bindGroup);
enc.setVertexBuffer(0, mesh.vertexBuffer);
enc.setIndexBuffer(mesh.indexBuffer, 'uint16');
enc.drawIndexed(mesh.indexCount);
}
enc.popDebugGroup();
return enc.finish();
};
const sceneBundle = recordScene(device, pipeline, meshes, format);
// Später: pass.executeBundles([sceneBundle]);
// Wichtig · Fallstricke
- Kompatibilität: WebGPU steht nur in sicheren Kontexten (HTTPS) zur Verfügung und ist noch nicht überall freigeschaltet. Prüfen Sie vorher mit
if (!navigator.gpu) { … }, ob die API vorhanden ist. LiefertrequestAdapter()null, gibt es keinen passenden Grafikadapter. - Formate müssen passen:
colorFormats,depthStencilFormatundsampleCountmüssen exakt mit den Attachments des Render-Passes übereinstimmen. Sonst schlägtexecuteBundles()mit einem Validierungsfehler fehl. Solche Fehler erscheinen nur als Warnung in der Konsole oder überdevice.pushErrorScope(), es wird keine Exception geworfen. - Zustand wird zurückgesetzt: Nach
executeBundles()sind Pipeline, Bind Groups sowie Vertex- und Index-Buffer des Passes zurückgesetzt. Folgen danach weitere Draw-Calls, müssen Sie diese Zustände neu setzen. Umgekehrt übernimmt ein Bundle keinen Zustand aus dem Pass. - Nach
finish()ist der Encoder ungültig: Weitere Befehle führen zu Validierungsfehlern. Ein fertiges Bundle lässt sich nicht mehr ändern. Bei geänderter Geometrie muss es neu aufgezeichnet werden. Dynamische Daten (Matrizen, Farben) aktualisieren Sie stattdessen überdevice.queue.writeBuffer()in Uniform- oder Storage-Buffern, die das Bundle referenziert. - Nicht verfügbar:
setViewport,setScissorRect,setBlendConstant,setStencilReference,beginOcclusionQuery. - Performance: Bundles lohnen sich vor allem bei vielen kleinen Draw-Calls. Bei wenigen Objekten ist der Vorteil gering.