Start · Sprachen · JavaScript · Referenz · GPUBuffer

GPUBuffer

Schnittstelle

Ein <code>GPUBuffer</code> steht für einen Speicherblock auf der GPU. Er speichert Rohdaten wie Vertices, Indizes, Uniforms oder Rechenergebnisse für WebGPU-Operationen.

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

Signatur

interface GPUBuffer

Beschreibung

Ein GPUBuffer ist ein zusammenhängender Block aus Bytes, den die GPU liest oder beschreibt. Man erzeugt ihn nicht mit new, sondern über GPUDevice.createBuffer(descriptor). Im Deskriptor stehen Größe (size), Verwendungszweck (usage, eine Bitmaske aus GPUBufferUsage-Flags) und optional mappedAtCreation. Die Flags legen fest, wofür der Puffer genutzt werden darf, z. B. als Vertex-, Index-, Uniform- oder Storage-Puffer oder als Quelle bzw. Ziel beim Kopieren.

Die CPU kommt nur über das Mapping an den Pufferinhalt. mapAsync(mode, offset, size) gibt einen Promise zurück. Sobald er erfüllt ist, liefert getMappedRange(offset, size) einen ArrayBuffer, den man liest oder beschreibt. unmap() übergibt den Speicher danach wieder an die GPU. Solange ein Puffer gemappt ist, darf ihn die GPU nicht verwenden.

Instanz-Eigenschaften (nur lesbar):

  • size: Größe in Bytes.
  • usage: die gesetzten Verwendungs-Flags.
  • mapState: "unmapped", "pending" oder "mapped".
  • label: ein Name für Debug-Ausgaben (auch beschreibbar).

Methoden: mapAsync(), getMappedRange(), unmap() und destroy(). destroy() gibt den GPU-Speicher sofort frei.

Einfache Uploads macht man meist ohne Mapping mit GPUQueue.writeBuffer(). Mapping lohnt sich vor allem für die erste Befüllung (mappedAtCreation: true) und zum Zurücklesen von Ergebnissen. Zum Zurücklesen kopiert man das Ergebnis in einen eigenen Staging-Puffer mit MAP_READ | COPY_DST.

Parameter

Name Typ Default Beschreibung
$descriptor.size Pflicht number Größe des Puffers in Bytes. Bei mappedAtCreation: true muss sie ein Vielfaches von 4 sein.
$descriptor.usage Pflicht number Bitmaske aus GPUBufferUsage-Flags, z. B. VERTEX, INDEX, UNIFORM, STORAGE, COPY_SRC, COPY_DST, MAP_READ, MAP_WRITE, INDIRECT oder QUERY_RESOLVE.
$descriptor.mappedAtCreation boolean false Bei true ist der Puffer direkt nach dem Erzeugen gemappt und kann ohne mapAsync() befüllt werden.
$descriptor.label string "" Name, der in Fehlermeldungen und Debug-Werkzeugen erscheint.

Beispiele

Vertex-Puffer mit mappedAtCreation befüllen

const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();

// Dreieck: x, y pro Vertex
const vertices = new Float32Array([0.0, 0.5, -0.5, -0.5, 0.5, -0.5]);

const vertexBuffer = device.createBuffer({
  label: 'Dreieck-Vertices',
  size: vertices.byteLength,
  usage: GPUBufferUsage.VERTEX,
  mappedAtCreation: true,
});

new Float32Array(vertexBuffer.getMappedRange()).set(vertices);
vertexBuffer.unmap();

console.log(vertexBuffer.size, vertexBuffer.mapState);
24 "unmapped"

Uniform-Puffer mit queue.writeBuffer aktualisieren

const uniformBuffer = device.createBuffer({
  label: 'Zeit-Uniform',
  size: 16, // Uniforms werden auf 16 Bytes ausgerichtet
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});

const frame = (time) => {
  device.queue.writeBuffer(uniformBuffer, 0, new Float32Array([time / 1000]));
  // ... Render-Pass mit einer Bind Group, die uniformBuffer enthält ...
  requestAnimationFrame(frame);
};
requestAnimationFrame(frame);

Ergebnis eines Compute-Shaders zurücklesen

const size = 4 * 64;

const storageBuffer = device.createBuffer({
  size,
  usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
});

// Staging-Puffer: nur dieser darf MAP_READ haben
const readBuffer = device.createBuffer({
  size,
  usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
});

const encoder = device.createCommandEncoder();
// ... Compute-Pass, der in storageBuffer schreibt ...
encoder.copyBufferToBuffer(storageBuffer, 0, readBuffer, 0, size);
device.queue.submit([encoder.finish()]);

await readBuffer.mapAsync(GPUMapMode.READ);
// Kopie anlegen, weil der ArrayBuffer bei unmap() ungültig wird
const result = new Float32Array(readBuffer.getMappedRange().slice(0));
readBuffer.unmap();

console.log(result.length);
64

Puffer explizit freigeben

const temp = device.createBuffer({ size: 1024 * 1024, usage: GPUBufferUsage.COPY_DST });
// ... Nutzung ...
temp.destroy(); // GPU-Speicher sofort freigeben, nicht auf die Garbage Collection warten

// Wichtig · Fallstricke

  • Browser-Unterstützung: WebGPU steht nur in sicheren Kontexten (HTTPS) zur Verfügung und noch nicht auf allen Plattformen. Vorher mit if (!navigator.gpu) prüfen und einen Ersatzweg (z. B. WebGL) vorsehen.
  • Einschränkung bei Mapping-Flags: MAP_READ ist nur zusammen mit COPY_DST erlaubt, MAP_WRITE nur zusammen mit COPY_SRC. Ein Storage-Puffer lässt sich also nicht direkt mappen, sondern braucht einen Staging-Puffer.
  • Ausrichtung: offset bei mapAsync() muss ein Vielfaches von 8 sein, size ein Vielfaches von 4. Bei getMappedRange() gelten ein Vielfaches von 8 für offset und ein Vielfaches von 4 für size. writeBuffer() und copyBufferToBuffer() verlangen Vielfache von 4. Uniform-Bindungen erwarten Offsets, die an minUniformBufferOffsetAlignment ausgerichtet sind, meist 256 Bytes.
  • Nach unmap() oder destroy() werden alle ArrayBuffer aus getMappedRange() abgetrennt (Länge 0). Daten, die man behalten will, vorher kopieren.
  • Wird ein gemappter Puffer in einem submit() verwendet, gibt es einen Validierungsfehler. Ein zweiter mapAsync()-Aufruf, während mapState noch "pending" ist, schlägt ebenfalls fehl.
  • Validierungsfehler werfen keine Ausnahme. Sie markieren den Puffer als ungültig und landen in der Konsole bzw. bei device.pushErrorScope()/popErrorScope().
  • Große Puffer sind durch device.limits.maxBufferSize begrenzt. Nicht mehr benötigte Puffer sollte man mit destroy() freigeben, um Speicherdruck auf der GPU zu vermeiden.