Signatur
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);
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);
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_READist nur zusammen mitCOPY_DSTerlaubt,MAP_WRITEnur zusammen mitCOPY_SRC. Ein Storage-Puffer lässt sich also nicht direkt mappen, sondern braucht einen Staging-Puffer. - Ausrichtung:
offsetbeimapAsync()muss ein Vielfaches von 8 sein,sizeein Vielfaches von 4. BeigetMappedRange()gelten ein Vielfaches von 8 füroffsetund ein Vielfaches von 4 fürsize.writeBuffer()undcopyBufferToBuffer()verlangen Vielfache von 4. Uniform-Bindungen erwarten Offsets, die anminUniformBufferOffsetAlignmentausgerichtet sind, meist 256 Bytes. - Nach
unmap()oderdestroy()werden alleArrayBufferausgetMappedRange()abgetrennt (Länge 0). Daten, die man behalten will, vorher kopieren. - Wird ein gemappter Puffer in einem
submit()verwendet, gibt es einen Validierungsfehler. Ein zweitermapAsync()-Aufruf, währendmapStatenoch"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.maxBufferSizebegrenzt. Nicht mehr benötigte Puffer sollte man mitdestroy()freigeben, um Speicherdruck auf der GPU zu vermeiden.