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

GPURenderPassEncoder.draw()

Methode

Zeichnet nicht-indizierte Primitive anhand der mit <code>setVertexBuffer()</code> gebundenen Vertex-Puffer und der aktuell gesetzten Render-Pipeline.

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

Signatur

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

Beschreibung

draw() ist der grundlegende Zeichenbefehl in WebGPU. Der Aufruf zeichnet nicht sofort, sondern zeichnet einen Befehl im Render-Pass auf. Ausgeführt wird er erst, wenn der zugehörige GPUCommandBuffer über device.queue.submit() an die GPU übergeben wird. Welche Primitive dabei entstehen (Dreiecke, Linien, Punkte), legt primitive.topology der mit setPipeline() gesetzten GPURenderPipeline fest.

Vorher müssen im Pass eine Render-Pipeline, alle Vertex-Puffer aus dem vertex.buffers-Layout der Pipeline (setVertexBuffer()) und alle nötigen Bind-Groups (setBindGroup()) gesetzt sein. Die Vertices werden der Reihe nach gelesen, beginnend bei firstVertex. Im Vertex-Shader stehen die Werte als @builtin(vertex_index) und @builtin(instance_index) zur Verfügung.

Mit instanceCount gibt es Instancing: Dieselbe Geometrie wird mehrfach gezeichnet, etwa für viele Bäume, Partikel oder Kacheln. Dafür braucht es nur einen einzigen Zeichenaufruf. Daten pro Instanz kommen aus Vertex-Puffern mit stepMode: "instance" oder aus einem Storage-Puffer, auf den man über instance_index zugreift. Für Geometrie mit Indexpuffer nimmt man drawIndexed(). Sollen die Zeichenparameter aus einem GPU-Puffer kommen, nimmt man drawIndirect().

Parameter

Name Typ Default Beschreibung
$vertexCount Pflicht number Anzahl der zu zeichnenden Vertices (ganzzahlig, GPUSize32).
$instanceCount number 1 Anzahl der Instanzen. Bei 0 wird nichts gezeichnet.
$firstVertex number 0 Versatz in Vertices vom Anfang der Vertex-Puffer. Ab hier wird gelesen. Das ist auch der Startwert von vertex_index.
$firstInstance number 0 Erste zu zeichnende Instanz. Das ist auch der Startwert von instance_index.

Rückgabewert

Typ
undefined
Beschreibung
Kein Rückgabewert. Der Zeichenbefehl wird im Encoder aufgezeichnet.

Beispiele

Einfaches Dreieck aus einem Vertex-Puffer

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 shader = 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 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);

const pipeline = device.createRenderPipeline({
  layout: 'auto',
  vertex: {
    module: shader,
    entryPoint: 'vs',
    buffers: [{ arrayStride: 8, attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x2' }] }],
  },
  fragment: { module: shader, entryPoint: 'fs', targets: [{ format }] },
  primitive: { topology: 'triangle-list' },
});

const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
  colorAttachments: [{
    view: context.getCurrentTexture().createView(),
    clearValue: { r: 0, g: 0, b: 0, a: 1 },
    loadOp: 'clear',
    storeOp: 'store',
  }],
});
pass.setPipeline(pipeline);
pass.setVertexBuffer(0, vertexBuffer);
pass.draw(3); // 3 Vertices = 1 Dreieck
pass.end();
device.queue.submit([encoder.finish()]);
Ein orangefarbenes Dreieck auf schwarzem Hintergrund.

Instancing: 100 Kopien in einem Aufruf

// WGSL-Ausschnitt: Position je Instanz aus instance_index berechnen
const code = `
  @vertex fn vs(@location(0) pos: vec2f,
                @builtin(instance_index) i: u32) -> @builtin(position) vec4f {
    let col = f32(i % 10u);
    let row = f32(i / 10u);
    let offset = vec2f(col * 0.2 - 0.9, row * 0.2 - 0.9);
    return vec4f(pos * 0.08 + offset, 0.0, 1.0);
  }`;

// ... Pipeline und Vertex-Puffer wie im ersten Beispiel ...
pass.setPipeline(pipeline);
pass.setVertexBuffer(0, vertexBuffer);
pass.draw(3, 100); // 3 Vertices x 100 Instanzen
pass.end();
Ein 10×10-Raster aus kleinen Dreiecken.

Vollbild-Dreieck ohne Vertex-Puffer

// Vertices werden im Shader aus vertex_index erzeugt – kein setVertexBuffer() nötig
const shader = device.createShaderModule({ code: `
  @vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f {
    var p = array<vec2f, 3>(vec2f(-1, -1), vec2f(3, -1), vec2f(-1, 3));
    return vec4f(p[i], 0.0, 1.0);
  }
  @fragment fn fs(@builtin(position) c: vec4f) -> @location(0) vec4f {
    return vec4f(c.x / 800.0, c.y / 600.0, 0.5, 1.0);
  }` });

// Pipeline ohne vertex.buffers anlegen, dann:
pass.setPipeline(fullscreenPipeline);
pass.draw(3); // deckt den gesamten Viewport ab (Post-Processing)
Farbverlauf über die gesamte Canvas.

// Wichtig · Fallstricke

  • Validierungsfehler statt Exceptions: Fehlt die Pipeline, ein Vertex-Puffer oder eine Bind-Group, oder reicht der Vertex-Puffer für firstVertex + vertexCount nicht aus, dann wirft draw() nicht. Stattdessen wird der Encoder ungültig, und der Fehler erscheint erst bei finish() bzw. submit(). Er landet in der Konsole oder über device.pushErrorScope() / das Ereignis uncapturederror.
  • Topologie beachten: Bei triangle-list sollte vertexCount ein Vielfaches von 3 sein, bei line-list ein Vielfaches von 2. Überzählige Vertices werden verworfen.
  • vertexCount gibt die Anzahl der Vertices an, nicht die Anzahl der Primitive und auch keine Bytes.
  • firstInstance ungleich 0 verlangt keine eigene Geräte-Funktion. Bei drawIndirect() dagegen braucht ein firstInstance ungleich 0 das Feature "indirect-first-instance".
  • Performance: Viele kleine draw()-Aufrufe kosten CPU-Zeit. Wo möglich, fasst man sie per Instancing zusammen oder zeichnet wiederkehrende Befehle in GPURenderBundles auf.
  • Kompatibilität: WebGPU gibt es nur in sicheren Kontexten. Die Unterstützung ist noch nicht in allen Browsern bzw. auf allen Plattformen vollständig. Deshalb vorher mit if (!navigator.gpu) prüfen und eine Alternative vorsehen, zum Beispiel WebGL.