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

GPUBuffer.destroy()

Methode

Zerstört einen <code>GPUBuffer</code> und gibt den zugehörigen GPU-Speicher sofort frei, statt auf die Garbage Collection zu warten.

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

Signatur

GPUBuffer.prototype.destroy()

Beschreibung

Die Methode destroy() der Schnittstelle GPUBuffer zerstört den Puffer. Danach ist er ungültig. Den Speicher auf der Grafikkarte kann der Browser dann sofort freigeben. Ohne diesen Aufruf wird der Speicher erst frei, wenn das JavaScript-Objekt vom Garbage Collector eingesammelt wird. Wann das passiert, lässt sich nicht vorhersagen, und der Garbage Collector weiß nichts vom tatsächlichen Speicherverbrauch auf der GPU.

Sinnvoll ist destroy() vor allem bei großen oder kurzlebigen Puffern, zum Beispiel:

  • Staging-Puffer, die nur für ein einmaliges Hochladen oder Zurücklesen von Daten gebraucht werden
  • Vertex- oder Index-Puffer, die beim Nachladen einer Szene ersetzt werden
  • Hilfspuffer in Compute-Pipelines, deren Größe sich ändert

Ist der Puffer gerade gemappt (mapState ist "mapped" oder "pending"), hebt destroy() das Mapping automatisch auf. Ein noch offenes Promise von mapAsync() wird mit einem AbortError abgelehnt. Befehle, die schon mit queue.submit() abgeschickt wurden und den Puffer nutzen, laufen noch korrekt zu Ende. Die Implementierung gibt den Speicher erst frei, wenn diese Befehle fertig sind.

Nach destroy() darf der Puffer in neuen Befehlen nicht mehr vorkommen. Wird ein Befehlspuffer abgeschickt, der einen zerstörten Puffer nutzt, meldet WebGPU einen Validierungsfehler, und dieser Befehlspuffer wird nicht ausgeführt.

Rückgabewert

Typ
undefined
Beschreibung
Kein Rückgabewert. Die Methode gibt undefined zurück.

Beispiele

Staging-Puffer nach dem Zurücklesen freigeben

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

// Quellpuffer mit Daten füllen
const data = new Float32Array([1, 2, 3, 4]);
const storage = device.createBuffer({
  size: data.byteLength,
  usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(storage, 0, data);

// Staging-Puffer nur zum Zurücklesen
const staging = device.createBuffer({
  size: data.byteLength,
  usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
});

const encoder = device.createCommandEncoder();
encoder.copyBufferToBuffer(storage, 0, staging, 0, data.byteLength);
device.queue.submit([encoder.finish()]);

await staging.mapAsync(GPUMapMode.READ);
const result = new Float32Array(staging.getMappedRange()).slice(); // Kopie anlegen!
staging.unmap();

// Staging-Puffer wird nicht mehr gebraucht -> GPU-Speicher sofort freigeben
staging.destroy();
console.log(result);
Float32Array(4) [1, 2, 3, 4]

Vertex-Puffer beim Szenenwechsel ersetzen

let vertexBuffer = null;

const loadMesh = (device, vertices) => {
  // Alten Puffer explizit zerstören, statt auf die GC zu warten
  vertexBuffer?.destroy();

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

const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
loadMesh(device, new Float32Array([0, 0.5, -0.5, -0.5, 0.5, -0.5]));
loadMesh(device, new Float32Array(3000)); // erster Puffer wird freigegeben
console.log(vertexBuffer.size);
12000

Laufendes mapAsync() durch destroy() abbrechen

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

const buffer = device.createBuffer({
  size: 256,
  usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
});

const mapping = buffer.mapAsync(GPUMapMode.READ);
buffer.destroy(); // bricht das Mapping ab

try {
  await mapping;
} catch (err) {
  console.log(err.name, buffer.mapState);
}
AbortError unmapped

Validierungsfehler bei Nutzung nach destroy() abfangen

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

const src = device.createBuffer({ size: 16, usage: GPUBufferUsage.COPY_SRC });
const dst = device.createBuffer({ size: 16, usage: GPUBufferUsage.COPY_DST });
src.destroy();

device.pushErrorScope('validation');
const enc = device.createCommandEncoder();
enc.copyBufferToBuffer(src, 0, dst, 0, 16);
device.queue.submit([enc.finish()]); // ungültig: src ist zerstört
const error = await device.popErrorScope();
console.log(error instanceof GPUValidationError);
true

// Wichtig · Fallstricke

  • Nicht umkehrbar: Ein zerstörter Puffer lässt sich nicht wiederherstellen. Braucht man ihn wieder, muss man ihn mit device.createBuffer() neu anlegen. Mehrfaches Aufrufen von destroy() ist erlaubt und bewirkt nichts weiter.
  • Keine Ausnahme bei Fehlnutzung: Wer einen zerstörten Puffer weiter benutzt, bekommt keinen JavaScript-Fehler an der Aufrufstelle. Stattdessen gibt es einen GPUValidationError beim queue.submit(), und der ganze Befehlspuffer wird verworfen. Solche Fehler fängt man mit pushErrorScope()/popErrorScope() oder über das Ereignis uncapturederror ab.
  • Gemappte Bereiche: ArrayBuffer-Objekte aus getMappedRange() werden durch destroy() (wie durch unmap()) abgetrennt und haben danach die Länge 0. Daten, die man behalten will, vorher mit slice() kopieren.
  • Bereits abgeschickte Arbeit: Man kann destroy() direkt nach queue.submit() aufrufen, ohne auf onSubmittedWorkDone() zu warten. Der Browser gibt den Speicher erst frei, wenn die GPU-Befehle fertig sind.
  • Kompatibilität: WebGPU gibt es nur in sicheren Kontexten. Unterstützt wird es in Chromium-Browsern ab Version 113 (Android ab 121), in Safari ab 26 und in Firefox ab 141 (zunächst nur unter Windows). Vorher mit if (!navigator.gpu) prüfen. In Web Workern ist die API ebenfalls verfügbar.
  • Auch GPUTexture, GPUQuerySet und GPUDevice haben eine eigene destroy()-Methode. Zerstört man das Gerät, werden damit auch alle seine Puffer ungültig.