Start · Sprachen · JavaScript · Referenz · GPURenderBundleEncoder

GPURenderBundleEncoder

Schnittstelle

Zeichnet Render-Befehle in ein wiederverwendbares <code>GPURenderBundle</code> auf, das später in beliebig vielen Render-Passes per <code>executeBundles()</code> abgespielt werden kann.

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

Signatur

interface GPURenderBundleEncoder

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

Typ
GPURenderBundleEncoder
Beschreibung
Wird von 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);
Dreieck

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. Liefert requestAdapter() null, gibt es keinen passenden Grafikadapter.
  • Formate müssen passen: colorFormats, depthStencilFormat und sampleCount müssen exakt mit den Attachments des Render-Passes übereinstimmen. Sonst schlägt executeBundles() mit einem Validierungsfehler fehl. Solche Fehler erscheinen nur als Warnung in der Konsole oder über device.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 über device.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.