Start · Sprachen · JavaScript · Referenz · GPUBuffer.mapAsync()

GPUBuffer.mapAsync()

Methode

Mappt einen Bereich eines <code>GPUBuffer</code> asynchron in den CPU-Speicher, damit JavaScript ihn lesen oder beschreiben kann.

seit JavaScript WebGPU – Chrome/Edge 113+, Firefox 141+ (Windows), Kategorie: webapi

Signatur

GPUBuffer.prototype.mapAsync(mode, offset, size)

Beschreibung

mapAsync() fordert an, dass ein Bereich eines GPUBuffer für die CPU zugänglich gemacht wird. Das zurückgegebene Promise wird erfüllt, sobald die GPU alle vorher eingereihten Arbeiten auf diesem Puffer abgeschlossen hat. Danach liefert getMappedRange() einen ArrayBuffer, über den du die Daten liest (GPUMapMode.READ) oder schreibst (GPUMapMode.WRITE).

Typische Anwendungsfälle sind das Zurücklesen von Ergebnissen eines Compute-Shaders und das Hochladen von Daten über einen Staging-Puffer. Dazu muss der Puffer mit GPUBufferUsage.MAP_READ bzw. GPUBufferUsage.MAP_WRITE erzeugt worden sein. MAP_READ lässt sich nur mit COPY_DST kombinieren, MAP_WRITE nur mit COPY_SRC. Deshalb kopiert man Daten meist per copyBufferToBuffer() zwischen einem GPU-Arbeitspuffer und einem mappbaren Staging-Puffer.

Solange ein Puffer gemappt ist oder das Mapping noch läuft, darf die GPU ihn nicht verwenden. Nach der Arbeit rufst du unmap() auf. Dadurch werden alle mit getMappedRange() erhaltenen ArrayBuffer getrennt (detached) und der Puffer steht der GPU wieder zur Verfügung.

Parameter

Name Typ Default Beschreibung
$mode Pflicht number (GPUMapModeFlags) Zugriffsart: GPUMapMode.READ oder GPUMapMode.WRITE. Es muss genau eines von beiden gesetzt sein, passend zu den usage-Flags des Puffers.
$offset number 0 Startposition des Bereichs in Bytes. Muss ein Vielfaches von 8 sein.
$size number buffer.size - offset Länge des Bereichs in Bytes. Muss ein Vielfaches von 4 sein. offset + size darf buffer.size nicht überschreiten.

Rückgabewert

Typ
Promise<undefined>
Beschreibung
Wird erfüllt, sobald der Bereich gemappt ist. Wird mit einer OperationError- bzw. AbortError-DOMException abgelehnt, wenn die Validierung fehlschlägt, das Gerät verloren geht oder vorher unmap()/destroy() aufgerufen wird.

Beispiele

Ergebnis eines Compute-Shaders zurücklesen

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

const SIZE = 16; // 4 Floats

// Arbeitspuffer (z. B. Ausgabe eines Compute-Shaders)
const output = device.createBuffer({
  size: SIZE,
  usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(output, 0, new Float32Array([1.5, 2.5, 3.5, 4.5]));

// Staging-Puffer zum Auslesen
const staging = device.createBuffer({
  size: SIZE,
  usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
});

const encoder = device.createCommandEncoder();
encoder.copyBufferToBuffer(output, 0, staging, 0, SIZE);
device.queue.submit([encoder.finish()]);

await staging.mapAsync(GPUMapMode.READ);
// Kopie anlegen, da der ArrayBuffer nach unmap() getrennt wird
const result = new Float32Array(staging.getMappedRange().slice(0));
staging.unmap();

console.log(result);
Float32Array(4) [1.5, 2.5, 3.5, 4.5]

Daten über einen MAP_WRITE-Puffer hochladen

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

const upload = device.createBuffer({
  size: 16,
  usage: GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC
});

await upload.mapAsync(GPUMapMode.WRITE);
new Uint32Array(upload.getMappedRange()).set([10, 20, 30, 40]);
upload.unmap();

console.log(upload.mapState);
unmapped

Nur einen Teilbereich mappen und Fehler behandeln

const readTail = async (buffer) => {
  try {
    // Bytes 8–15 lesen (offset: Vielfaches von 8, size: Vielfaches von 4)
    await buffer.mapAsync(GPUMapMode.READ, 8, 8);
    const part = new Uint32Array(buffer.getMappedRange(8, 8).slice(0));
    buffer.unmap();
    return part;
  } catch (err) {
    console.error('Mapping fehlgeschlagen:', err.name, err.message);
    return null;
  }
};

// Wichtig · Fallstricke

  • Kein Überlappen: Ein zweiter mapAsync()-Aufruf, solange mapState nicht "unmapped" ist, schlägt fehl. Prüfe vorher buffer.mapState.
  • Getrennte ArrayBuffer: Nach unmap() sind alle Views auf den gemappten Bereich unbrauchbar (Länge 0). Wenn du die Daten länger brauchst, kopiere sie mit .slice(0).
  • Nutzung durch die GPU: Wird ein gemappter Puffer in queue.submit() verwendet, entsteht ein Validierungsfehler.
  • Ausrichtung: offset muss ein Vielfaches von 8 sein, size ein Vielfaches von 4. Sonst wird das Promise abgelehnt.
  • Performance: Warte nicht in jedem Frame mit await auf das Mapping, das bremst die Pipeline aus. Besser ist ein Ring aus mehreren Staging-Puffern. Für einfache Uploads ist device.queue.writeBuffer() meist bequemer.
  • Kompatibilität: WebGPU steht nur in sicheren Kontexten (HTTPS) zur Verfügung und wird noch nicht überall unterstützt. Prüfe deshalb vorher, ob navigator.gpu vorhanden ist.