Start · Sprachen · JavaScript · Referenz · GPURenderBundleEncoder.finish()

GPURenderBundleEncoder.finish()

Methode

Schließt die Aufzeichnung eines Render-Bundles ab und gibt ein wiederverwendbares <code>GPURenderBundle</code> zurück, das mit <code>executeBundles()</code> in Render-Passes abgespielt werden kann.

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

Signatur

GPURenderBundleEncoder.prototype.finish(descriptor)

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

Typ
GPURenderBundle
Beschreibung
Ein unveränderliches Render-Bundle mit den aufgezeichneten Befehlen. Du übergibst es an 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);
Dreieck-Bundle

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);
Szene (3 Meshes)

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');
Validierungsfehler erkannt

// 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 über pushErrorScope()/popErrorScope() oder das Ereignis uncapturederror.
  • Kompatibilität: colorFormats, depthStencilFormat und sampleCount des Bundles müssen exakt zum Render-Pass passen, in dem du executeBundles() 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() und setStencilReference() 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.