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

GPURenderBundleEncoder.draw()

Methode

Nimmt einen nicht-indizierten Zeichenbefehl in ein Render-Bundle auf, der Primitive aus den per <code>setVertexBuffer()</code> gebundenen Vertex-Puffern zeichnet.

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

Signatur

GPURenderBundleEncoder.prototype.draw(vertexCount, instanceCount, firstVertex, firstInstance)

Beschreibung

draw() nimmt einen Zeichenbefehl in einen GPURenderBundleEncoder auf. Die GPU führt dabei nichts sofort aus. Der Befehl wird im Bundle gespeichert und erst ausgeführt, wenn das fertige GPURenderBundle über GPURenderPassEncoder.executeBundles() in einem Render-Pass abgespielt wird. Pro Instanz verarbeitet der Vertex-Shader vertexCount Vertices, beginnend bei firstVertex.

Render-Bundles eignen sich für statische Szenenteile, die in jedem Frame gleich gezeichnet werden, etwa Gelände, Hintergrundobjekte oder UI-Geometrie. Die Befehlsfolge wird einmal aufgezeichnet und dann beliebig oft wiederverwendet. Das spart pro Frame Aufwand in JavaScript und bei der Validierung.

Vor draw() müssen im Bundle-Encoder eine passende Pipeline (setPipeline()), alle nötigen Bind-Groups (setBindGroup()) und die Vertex-Puffer (setVertexBuffer()) gesetzt sein. Zustand aus dem Render-Pass wird nicht übernommen: Jedes Bundle beginnt mit leerem Zustand.

Die Methode entspricht GPURenderPassEncoder.draw(). Für indizierte Geometrie gibt es drawIndexed(), für Parameter aus einem GPU-Puffer drawIndirect().

Parameter

Name Typ Default Beschreibung
$vertexCount Pflicht number Anzahl der zu zeichnenden Vertices (vorzeichenlose 32-Bit-Ganzzahl).
$instanceCount number 1 Anzahl der zu zeichnenden Instanzen. Im Shader über @builtin(instance_index) abfragbar.
$firstVertex number 0 Vertex-Offset in den Vertex-Puffern, ab dem gezeichnet wird. Entspricht dem Startwert von @builtin(vertex_index).
$firstInstance number 0 Index der ersten Instanz. Wirkt auf Vertex-Puffer mit stepMode: "instance".

Rückgabewert

Typ
undefined
Beschreibung
Kein Rückgabewert. Der Befehl wird nur im Bundle aufgezeichnet.

Beispiele

Dreieck in ein Render-Bundle aufzeichnen und abspielen

const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const format = navigator.gpu.getPreferredCanvasFormat();
const context = document.querySelector('canvas').getContext('webgpu');
context.configure({ device, format });

const module = device.createShaderModule({ code: `
  @vertex fn vs(@location(0) pos: vec2f) -> @builtin(position) vec4f {
    return vec4f(pos, 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',
    buffers: [{ arrayStride: 8, attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x2' }] }]
  },
  fragment: { module, entryPoint: 'fs', targets: [{ format }] }
});

const vertices = new Float32Array([0, 0.5, -0.5, -0.5, 0.5, -0.5]);
const vertexBuffer = device.createBuffer({
  size: vertices.byteLength,
  usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(vertexBuffer, 0, vertices);

// Bundle einmalig aufzeichnen
const bundleEncoder = device.createRenderBundleEncoder({ colorFormats: [format] });
bundleEncoder.setPipeline(pipeline);
bundleEncoder.setVertexBuffer(0, vertexBuffer);
bundleEncoder.draw(3);
const bundle = bundleEncoder.finish();

// In jedem Frame wiederverwenden
const frame = () => {
  const encoder = device.createCommandEncoder();
  const pass = encoder.beginRenderPass({
    colorAttachments: [{
      view: context.getCurrentTexture().createView(),
      loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 1]
    }]
  });
  pass.executeBundles([bundle]);
  pass.end();
  device.queue.submit([encoder.finish()]);
  requestAnimationFrame(frame);
};
requestAnimationFrame(frame);

Instanziertes Zeichnen: 100 Instanzen eines Quads

// Voraussetzung: device, format, pipeline mit zwei Vertex-Puffern
// (Puffer 0: stepMode 'vertex', Puffer 1: stepMode 'instance')
const bundleEncoder = device.createRenderBundleEncoder({ colorFormats: [format] });
bundleEncoder.setPipeline(pipeline);
bundleEncoder.setVertexBuffer(0, quadBuffer);      // 6 Vertices (2 Dreiecke)
bundleEncoder.setVertexBuffer(1, offsetBuffer);    // 100 Offsets pro Instanz
bundleEncoder.draw(6, 100);                         // 6 Vertices × 100 Instanzen
const particlesBundle = bundleEncoder.finish();

Teilbereiche eines Puffers mit firstVertex zeichnen

// Ein Puffer enthält mehrere Formen hintereinander:
// Vertices 0–2: Dreieck, Vertices 3–8: Quadrat
const bundleEncoder = device.createRenderBundleEncoder({ colorFormats: [format] });
bundleEncoder.setPipeline(pipeline);
bundleEncoder.setVertexBuffer(0, sharedBuffer);
bundleEncoder.draw(3, 1, 0);   // nur das Dreieck
bundleEncoder.draw(6, 1, 3);   // nur das Quadrat
const shapesBundle = bundleEncoder.finish();

// Wichtig · Fallstricke

  • Kein geerbter Zustand: Ein Render-Bundle übernimmt Pipeline, Bind-Groups und Vertex-Puffer nicht aus dem Render-Pass. Umgekehrt ist der Zustand des Passes nach executeBundles() zurückgesetzt, und alles muss neu gesetzt werden.
  • Fehler erscheinen verzögert: Ungültige Aufrufe werfen keine Ausnahme. Das gilt etwa für fehlende Pipeline, fehlende Vertex-Puffer oder Zugriffe über die Puffergrenzen hinaus. Stattdessen wird der Encoder ungültig, und der Fehler zeigt sich bei finish() als Validierungsfehler. Solche Fehler lassen sich mit device.pushErrorScope('validation') abfangen.
  • Formate müssen passen: colorFormats, depthStencilFormat und sampleCount des Bundle-Encoders müssen zur Pipeline und später zum Render-Pass passen, sonst schlägt executeBundles() fehl.
  • firstInstance ungleich 0 bei drawIndirect() erfordert das Feature "indirect-first-instance". Beim direkten draw() ist der Parameter immer erlaubt.
  • Kompatibilität: WebGPU steht nur in sicheren Kontexten zur Verfügung, und die Browser-Unterstützung ist noch lückenhaft. Prüfe vorher, ob navigator.gpu existiert, und sieh einen Fallback wie WebGL vor.