Signatur
Beschreibung
onSubmittedWorkDone() ist eine Methode der GPUQueue-Schnittstelle der WebGPU-API. Sie liefert ein Promise, das erfüllt wird, sobald die GPU alle Arbeiten fertig bearbeitet hat, die bis zum Aufruf über diese Warteschlange eingereicht wurden. Dazu gehören Command-Buffer aus queue.submit() sowie Operationen wie queue.writeBuffer(), queue.writeTexture() und queue.copyExternalImageToTexture().
WebGPU arbeitet asynchron: submit() kehrt sofort zurück, ohne auf die GPU zu warten. Mit onSubmittedWorkDone() kann JavaScript an einem festen Punkt auf die GPU warten, ohne dass ein Buffer zurückgelesen werden muss. Das ist nützlich für grobe Zeitmessungen (Benchmarks), für die Drosselung, damit nicht zu viele Frames oder Compute-Jobs auf einmal in der Warteschlange stehen, und für das Freigeben oder Wiederverwenden von Ressourcen, etwa mit buffer.destroy(), sobald die GPU sie sicher nicht mehr braucht.
Arbeiten, die nach dem Aufruf eingereicht werden, beeinflussen das Promise nicht. Wer Ergebnisse auf der CPU lesen will, nutzt dagegen GPUBuffer.mapAsync(), das ohnehin auf die zugehörigen GPU-Arbeiten wartet.
Rückgabewert
undefined erfüllt wird, sobald alle bis zum Aufruf übermittelten Arbeiten der Warteschlange abgeschlossen sind.Beispiele
Auf das Ende eines Compute-Durchlaufs warten
const adapter = await navigator.gpu?.requestAdapter();
if (!adapter) throw new Error('WebGPU wird nicht unterstützt');
const device = await adapter.requestDevice();
const module = device.createShaderModule({
code: `
@group(0) @binding(0) var<storage, read_write> data: array<f32>;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3u) {
if (id.x < arrayLength(&data)) { data[id.x] = data[id.x] * 2.0; }
}`
});
const pipeline = device.createComputePipeline({
layout: 'auto',
compute: { module, entryPoint: 'main' }
});
const count = 1024;
const buffer = device.createBuffer({
size: count * 4,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(buffer, 0, new Float32Array(count).fill(1));
const bindGroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [{ binding: 0, resource: { buffer } }]
});
const encoder = device.createCommandEncoder();
const pass = encoder.beginComputePass();
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.dispatchWorkgroups(Math.ceil(count / 64));
pass.end();
device.queue.submit([encoder.finish()]);
await device.queue.onSubmittedWorkDone();
console.log('GPU-Arbeit abgeschlossen');
buffer.destroy(); // jetzt gefahrlos freigeben
Grobe Zeitmessung der GPU-Arbeit
const measureGpu = async (device, commandBuffers) => {
// Vorherige Arbeit abwarten, damit sie nicht mitgemessen wird
await device.queue.onSubmittedWorkDone();
const start = performance.now();
device.queue.submit(commandBuffers);
await device.queue.onSubmittedWorkDone();
return performance.now() - start;
};
// Aufruf, z. B. mit einem fertig kodierten Command-Buffer:
// const ms = await measureGpu(device, [encoder.finish()]);
// console.log(`GPU-Zeit ca. ${ms.toFixed(2)} ms`);
Frames drosseln: höchstens zwei Frames in der Warteschlange
const MAX_FRAMES_IN_FLIGHT = 2;
const inFlight = [];
const renderFrame = async (device, encodeFrame) => {
if (inFlight.length >= MAX_FRAMES_IN_FLIGHT) {
await inFlight.shift(); // ältesten Frame abwarten
}
const encoder = device.createCommandEncoder();
encodeFrame(encoder);
device.queue.submit([encoder.finish()]);
inFlight.push(device.queue.onSubmittedWorkDone());
};
// Wichtig · Fallstricke
- Keine genaue Zeitmessung: Das Promise wird erst in einem späteren Durchlauf der Ereignisschleife erfüllt und enthält den Verwaltungsaufwand von Browser und Treiber. Für exakte GPU-Zeiten nutzt man Timestamp-Queries (Feature
"timestamp-query",GPUQuerySet). Browser runden diese Werte aus Sicherheitsgründen. - Nicht für jeden Frame nötig: Wer nach jedem
submit()wartet, lässt CPU und GPU nicht mehr parallel arbeiten und verliert dadurch Leistung. Die Methode sollte gezielt eingesetzt werden. - Zum Zurücklesen ungeeignet: Um Daten auszulesen, ist
GPUBuffer.mapAsync()der richtige Weg. Ein vorherigesonSubmittedWorkDone()ist dafür überflüssig. - Geräteverlust: Geht das Gerät verloren (
device.lost), wird das Promise laut Spezifikation trotzdem erfüllt. Deshalb sollte mandevice.lostseparat überwachen. - Kompatibilität: WebGPU ist nur in sicheren Kontexten (HTTPS) und nicht in allen Browsern bzw. auf allen Plattformen verfügbar. Vorher auf
navigator.gpuprüfen. In Workern steht WebGPU ebenfalls zur Verfügung.