Start · Sprachen · JavaScript · Referenz · GPURenderBundle

GPURenderBundle

Schnittstelle

Ein <code>GPURenderBundle</code> ist ein unveränderlicher Container mit vorab aufgezeichneten Render-Befehlen, den man in Render-Passes beliebig oft wiederverwenden kann.

seit JavaScript WebGPU – Chrome/Edge 113+, Safari 26+, Firefox 141 Kategorie: webapi

Signatur

interface GPURenderBundle

Beschreibung

Das Interface GPURenderBundle der WebGPU API steht für ein Bündel von Zeichenbefehlen, das bereits aufgezeichnet wurde. Ein Bundle entsteht nicht über einen Konstruktor. Man zeichnet die Befehle mit einem GPURenderBundleEncoder auf, den GPUDevice.createRenderBundleEncoder() liefert, und ruft dann GPURenderBundleEncoder.finish() auf. Das Ergebnis führt man in einem Render-Pass mit GPURenderPassEncoder.executeBundles() aus.

Render-Bundles lohnen sich vor allem bei statischen oder selten veränderten Szenen, die Bild für Bild mit denselben Befehlen gezeichnet werden, etwa viele Objekte mit festen Pipelines, Bind Groups und Vertex-Buffern. Die Validierung der aufgezeichneten Befehle findet nur einmal beim Aufzeichnen statt. Deshalb sinkt der CPU-Aufwand pro Frame, besonders wenn Hunderte oder Tausende Zeichenaufrufe anfallen.

Ein Bundle kann setPipeline(), setBindGroup(), setVertexBuffer(), setIndexBuffer(), draw(), drawIndexed() und die indirekten Varianten enthalten. Viewport, Scissor-Rechteck, Blend-Konstante und Stencil-Referenz lassen sich im Bundle nicht setzen. Diese Werte übernimmt das Bundle aus dem umgebenden Render-Pass. Die Inhalte der verwendeten Buffer dürfen sich aber ändern. So kann man z. B. Kameramatrizen über queue.writeBuffer() aktualisieren, ohne das Bundle neu aufzuzeichnen.

Das Interface selbst ist schlank. Es hat nur die Eigenschaft label, einen frei wählbaren Namen für Debugging und Fehlermeldungen. Konstruktor und Methoden hat es keine.

Parameter

Name Typ Default Beschreibung
$label string "" Instanz-Eigenschaft (lesen und schreiben): Name des Bundles für Debug-Ausgaben und Validierungsfehler. Den Anfangswert übernimmt das Bundle aus dem label des Deskriptors von finish().

Rückgabewert

Typ
GPURenderBundle
Beschreibung
Wird von GPURenderBundleEncoder.finish(descriptor?) zurückgegeben und an GPURenderPassEncoder.executeBundles(bundles) übergeben.

Beispiele

Bundle aufzeichnen und in jedem Frame ausführen

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(0.0, 0.5), vec2f(-0.5, -0.5), vec2f(0.5, -0.5));
      return vec4f(p[i], 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' },
  fragment: { module, entryPoint: 'fs', targets: [{ format }] }
});

// Einmalig aufzeichnen – colorFormats muss zum Render-Pass passen
const bundleEncoder = device.createRenderBundleEncoder({ colorFormats: [format] });
bundleEncoder.setPipeline(pipeline);
bundleEncoder.draw(3);
const bundle = bundleEncoder.finish({ label: 'Dreieck-Bundle' });

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]); // wiederverwenden statt neu aufzeichnen
  pass.end();
  device.queue.submit([encoder.finish()]);
  requestAnimationFrame(frame);
};
requestAnimationFrame(frame);

console.log(bundle.label);
Dreieck-Bundle

Viele Objekte bündeln, Uniform-Daten trotzdem pro Frame ändern

// Annahme: device, pipeline, format, depthFormat sowie objects
// (Array mit { bindGroup, vertexBuffer, vertexCount }) existieren bereits.
const createSceneBundle = (objects) => {
  const enc = device.createRenderBundleEncoder({
    label: 'Szene',
    colorFormats: [format],
    depthStencilFormat: depthFormat
  });
  enc.setPipeline(pipeline);
  for (const obj of objects) {
    enc.setBindGroup(0, obj.bindGroup);
    enc.setVertexBuffer(0, obj.vertexBuffer);
    enc.draw(obj.vertexCount);
  }
  return enc.finish();
};

const sceneBundle = createSceneBundle(objects);

const render = (cameraMatrix, pass) => {
  // Buffer-Inhalt aktualisieren – das Bundle bleibt gültig
  device.queue.writeBuffer(cameraUniformBuffer, 0, cameraMatrix);
  pass.executeBundles([sceneBundle]);
};

Label nachträglich setzen und Validierungsfehler abfangen

device.pushErrorScope('validation');
const enc = device.createRenderBundleEncoder({ colorFormats: ['rgba8unorm'] });
enc.setPipeline(pipeline); // Pipeline mit abweichendem Zielformat?
enc.draw(3);
const bundle = enc.finish();
bundle.label = 'HUD-Overlay';

const error = await device.popErrorScope();
if (error) {
  console.warn(`Bundle "${bundle.label}" ungültig:`, error.message);
}
Bundle "HUD-Overlay" ungültig: ... (nur wenn das Pipeline-Format nicht passt)

// Wichtig · Fallstricke

  • Kompatible Formate: colorFormats, depthStencilFormat und sampleCount des Bundle-Encoders müssen exakt zu den Attachments des Render-Passes passen, sonst schlägt executeBundles() mit einem Validierungsfehler fehl. Mit depthReadOnly/stencilReadOnly muss der Pass ebenfalls vereinbar sein.
  • Zustand wird zurückgesetzt: Nach executeBundles() sind Pipeline, Bind Groups sowie Vertex- und Index-Buffer des Passes zurückgesetzt. Wer danach im Pass direkt weiterzeichnet, muss sie neu setzen. Umgekehrt erbt ein Bundle keinen Zustand aus dem Pass, mit Ausnahme von Viewport, Scissor, Blend-Konstante und Stencil-Referenz.
  • Unveränderlich: Ändern sich die Befehle selbst, z. B. Objekte kommen hinzu oder fallen weg, muss man das Bundle neu aufzeichnen. Nur Buffer- und Texturinhalte dürfen sich ändern. Zerstört man eine verwendete Ressource mit destroy(), wird die Übermittlung ungültig.
  • Performance: Bei wenigen Zeichenaufrufen bringt ein Bundle kaum etwas. Messbar wird der Vorteil erst bei vielen Draw-Calls pro Frame.
  • Kompatibilität: WebGPU steht nur in sicheren Kontexten zur Verfügung. Vor der Nutzung sollte man mit 'gpu' in navigator prüfen, ob die API vorhanden ist. Auch in Web Workern ist sie nutzbar.