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

GPURenderPassEncoder.setIndexBuffer()

Methode

Setzt den <code>GPUBuffer</code> mit Indexdaten, den nachfolgende <code>drawIndexed()</code>- und <code>drawIndexedIndirect()</code>-Aufrufe im Render-Pass verwenden.

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

Signatur

GPURenderPassEncoder.prototype.setIndexBuffer(buffer, indexFormat, offset, size)

Beschreibung

Mit setIndexBuffer() legt man fest, aus welchem GPUBuffer ein Render-Pass die Vertex-Indizes liest. Indizierte Geometrie spart Speicher und Bandbreite: Gemeinsam genutzte Eckpunkte (etwa die vier Ecken eines Rechtecks aus zwei Dreiecken) stehen nur einmal im Vertex-Buffer, und die Dreiecke verweisen per Index auf sie.

Die Einstellung gilt für alle folgenden drawIndexed()- und drawIndexedIndirect()-Aufrufe im selben Pass, bis ein neuer Index-Buffer gesetzt wird. Auf normale draw()-Aufrufe hat sie keinen Einfluss. Der Buffer muss mit dem Nutzungsflag GPUBufferUsage.INDEX angelegt worden sein.

Das indexFormat legt fest, wie breit ein Index ist: "uint16" (2 Byte, bis 65.535 Eckpunkte) oder "uint32" (4 Byte). Mit offset und size lässt sich nur ein Teilbereich des Buffers nutzen. So können mehrere Meshes in einem einzigen großen Buffer liegen.

Die Methode gibt es genauso bei GPURenderBundleEncoder. Sie zeichnet nur einen Befehl auf. Ausgeführt wird erst, wenn der Befehlspuffer über device.queue.submit() abgeschickt wird.

Parameter

Name Typ Default Beschreibung
$buffer Pflicht GPUBuffer Buffer mit den Indexdaten. Muss mit GPUBufferUsage.INDEX erzeugt worden sein.
$indexFormat Pflicht "uint16" | "uint32" Datentyp der Indizes. Muss bei Strip-Topologien (line-strip, triangle-strip) zum stripIndexFormat der Pipeline passen.
$offset number 0 Startposition im Buffer in Byte. Muss ein Vielfaches der Indexgröße sein (2 bei uint16, 4 bei uint32).
$size number buffer.size - offset Länge des genutzten Bereichs in Byte. offset + size darf buffer.size nicht überschreiten.

Rückgabewert

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

Beispiele

Rechteck aus zwei Dreiecken indiziert zeichnen

// Voraussetzung: device, context, pipeline (mit passendem Vertex-Layout) existieren
const vertices = new Float32Array([
  -0.5, -0.5,  0.5, -0.5,  0.5, 0.5,  -0.5, 0.5
]);
const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);

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

const indexBuffer = device.createBuffer({
  size: indices.byteLength, // 12 Byte, Vielfaches von 4
  usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(indexBuffer, 0, indices);

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.setIndexBuffer(indexBuffer, 'uint16');
pass.drawIndexed(indices.length); // 6 Indizes -> 2 Dreiecke
pass.end();
device.queue.submit([encoder.finish()]);

Mehrere Meshes in einem gemeinsamen Index-Buffer

// Zwei Meshes liegen nacheinander in einem uint32-Index-Buffer
const meshA = new Uint32Array([0, 1, 2, 2, 3, 0]);      // 24 Byte
const meshB = new Uint32Array([0, 1, 2]);               // 12 Byte
const all = new Uint32Array([...meshA, ...meshB]);

const indexBuffer = device.createBuffer({
  size: all.byteLength,
  usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(indexBuffer, 0, all);

// im Render-Pass:
pass.setIndexBuffer(indexBuffer, 'uint32', 0, meshA.byteLength);
pass.drawIndexed(meshA.length);

pass.setIndexBuffer(indexBuffer, 'uint32', meshA.byteLength, meshB.byteLength);
pass.drawIndexed(meshB.length);

// Alternative ohne erneutes setIndexBuffer: firstIndex von drawIndexed
// pass.drawIndexed(meshB.length, 1, meshA.length);

Indexformat abhängig von der Vertex-Anzahl wählen

const createIndexBuffer = (device, indexArray) => {
  const format = indexArray instanceof Uint16Array ? 'uint16' : 'uint32';
  // Größe auf 4 Byte aufrunden (writeBuffer verlangt Vielfache von 4)
  const size = Math.ceil(indexArray.byteLength / 4) * 4;
  const buffer = device.createBuffer({
    size,
    usage: GPUBufferUsage.INDEX,
    mappedAtCreation: true
  });
  new indexArray.constructor(buffer.getMappedRange()).set(indexArray);
  buffer.unmap();
  return { buffer, format, count: indexArray.length };
};

const vertexCount = 70000;
const IndexArray = vertexCount > 65535 ? Uint32Array : Uint16Array;
const mesh = createIndexBuffer(device, new IndexArray([0, 1, 2]));
console.log(mesh.format);

pass.setIndexBuffer(mesh.buffer, mesh.format);
pass.drawIndexed(mesh.count);
uint32

// Wichtig · Fallstricke

  • Nutzungsflag: Fehlt GPUBufferUsage.INDEX, erzeugt der Aufruf einen Validierungsfehler. Das Werfen passiert nicht sofort, sondern der Encoder wird ungültig und encoder.finish() bzw. submit() scheitert. Fehler findet man über device.pushErrorScope('validation') oder das Event uncapturederror.
  • Ausrichtung: offset muss ein Vielfaches der Indexgröße sein. queue.writeBuffer() und die Buffergröße bei mappedAtCreation verlangen Vielfache von 4 Byte. Ein Uint16Array mit ungerader Länge muss man also auffüllen.
  • Formatwahl: Das Format muss zum Typed Array passen. Wer ein Uint32Array als "uint16" ausliest, bekommt kaputte Geometrie, aber keine Fehlermeldung.
  • Primitive Restart: Bei Strip-Topologien beenden die Werte 0xFFFF bzw. 0xFFFFFFFF den aktuellen Strip. Das Format muss dann mit primitive.stripIndexFormat der Pipeline übereinstimmen.
  • Außerhalb des Bereichs: drawIndexed() darf nicht mehr Indizes lesen, als im gesetzten Bereich liegen. Sonst gibt es einen Validierungsfehler.
  • Kompatibilität: WebGPU gibt es nur in sicheren Kontexten und noch nicht in allen Browsern. Prüfen Sie vorher navigator.gpu und ob requestAdapter() einen Adapter liefert (Ergebnis ungleich null).