Signatur
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
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);
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);
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);
}
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);
// 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 vondestroy()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
GPUValidationErrorbeimqueue.submit(), und der ganze Befehlspuffer wird verworfen. Solche Fehler fängt man mitpushErrorScope()/popErrorScope()oder über das Ereignisuncapturederrorab. - Gemappte Bereiche:
ArrayBuffer-Objekte ausgetMappedRange()werden durchdestroy()(wie durchunmap()) abgetrennt und haben danach die Länge 0. Daten, die man behalten will, vorher mitslice()kopieren. - Bereits abgeschickte Arbeit: Man kann
destroy()direkt nachqueue.submit()aufrufen, ohne aufonSubmittedWorkDone()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,GPUQuerySetundGPUDevicehaben eine eigenedestroy()-Methode. Zerstört man das Gerät, werden damit auch alle seine Puffer ungültig.