Signatur
Beschreibung
submit() ist der letzte Schritt im typischen WebGPU-Ablauf: Mit einem GPUCommandEncoder zeichnet man Render- und Compute-Durchläufe sowie Kopierbefehle auf, schließt sie mit encoder.finish() zu einem GPUCommandBuffer ab und übergibt diesen über device.queue.submit([...]) an die GPU. Erst dann wird tatsächlich gerechnet oder gezeichnet.
Die Methode arbeitet asynchron. Sie reiht die Befehlspuffer nur ein und kehrt sofort zurück, ohne auf die GPU zu warten. Die Puffer eines Aufrufs laufen in der Reihenfolge des Arrays. Mehrere submit()-Aufrufe auf derselben Queue werden ebenfalls nacheinander abgearbeitet. Wenn Sie wissen müssen, wann die Arbeit fertig ist, nutzen Sie queue.onSubmittedWorkDone() oder GPUBuffer.mapAsync().
Jeder GPUCommandBuffer lässt sich nur einmal übergeben. Danach ist er verbraucht. In Render-Schleifen erzeugt man deshalb für jeden Frame einen neuen Encoder und einen neuen Befehlspuffer. Das kostet wenig und ist so vorgesehen.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $commandBuffers Pflicht | Array<GPUCommandBuffer> | Iterable<GPUCommandBuffer> | Liste der Befehlspuffer, die ausgeführt werden sollen. Man erzeugt sie mit GPUCommandEncoder.finish(). Sie werden in der Reihenfolge der Liste ausgeführt. Auch ein leeres Array ist gültig. |
Rückgabewert
pushErrorScope() bzw. das Event uncapturederror).Beispiele
Einfachen Render-Durchlauf übergeben (Bildschirm löschen)
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const canvas = document.querySelector('canvas');
const context = canvas.getContext('webgpu');
const format = navigator.gpu.getPreferredCanvasFormat();
context.configure({ device, format });
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
clearValue: { r: 0.1, g: 0.2, b: 0.4, a: 1 },
loadOp: 'clear',
storeOp: 'store'
}]
});
pass.end();
// Befehlspuffer abschließen und an die GPU übergeben
device.queue.submit([encoder.finish()]);
Render-Schleife: für jeden Frame einen neuen Befehlspuffer
const frame = () => {
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
clearValue: { r: Math.abs(Math.sin(performance.now() / 1000)), g: 0, b: 0, a: 1 },
loadOp: 'clear',
storeOp: 'store'
}]
});
pass.end();
device.queue.submit([encoder.finish()]);
requestAnimationFrame(frame);
};
requestAnimationFrame(frame);
Compute-Ergebnis übergeben und zurücklesen
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 input = new Float32Array([1, 2, 3, 4]);
const storage = device.createBuffer({
size: input.byteLength,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(storage, 0, input);
const readback = device.createBuffer({
size: input.byteLength,
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
});
const bindGroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [{ binding: 0, resource: { buffer: storage } }]
});
const encoder = device.createCommandEncoder();
const pass = encoder.beginComputePass();
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.dispatchWorkgroups(1);
pass.end();
encoder.copyBufferToBuffer(storage, 0, readback, 0, input.byteLength);
device.queue.submit([encoder.finish()]);
// Warten, bis die GPU fertig ist, dann auslesen
await readback.mapAsync(GPUMapMode.READ);
console.log(Array.from(new Float32Array(readback.getMappedRange())));
readback.unmap();
Übergabefehler abfangen und auf Fertigstellung warten
device.pushErrorScope('validation');
device.queue.submit([encoder.finish()]);
const error = await device.popErrorScope();
if (error) {
console.error('submit fehlgeschlagen:', error.message);
}
await device.queue.onSubmittedWorkDone();
console.log('GPU-Arbeit abgeschlossen');
// Wichtig · Fallstricke
- Einmalige Verwendung: Wird derselbe
GPUCommandBufferein zweites Mal übergeben, entsteht ein Validierungsfehler. Kein Puffer dieses Aufrufs wird dann ausgeführt. - Kein throw: Fehler wie ein zerstörter Puffer oder eine zerstörte Textur (
destroy()), ein gerade gemappter Puffer oder ein ungültiger Befehlspuffer werfen keine Exception. Sie erscheinen nur über Error-Scopes oder alsuncapturederror. - Gemappte Puffer: Ein Puffer, der im Befehlspuffer verwendet wird, darf beim Übergeben nicht gemappt sein. Rufen Sie vorher
unmap()auf. - Reihenfolge:
queue.writeBuffer()undqueue.writeTexture()werden in Aufrufreihenfolge relativ zusubmit()abgearbeitet. EinwriteBuffer()vor demsubmit()ist also für die übergebenen Befehle sichtbar. - Leistung: Mehrere Befehlspuffer gebündelt in einem einzigen
submit()sind meist günstiger als viele einzelne Aufrufe pro Frame. - Kompatibilität: WebGPU steht nur in sicheren Kontexten zur Verfügung, und die Unterstützung ist noch nicht vollständig. Prüfen Sie deshalb vorher mit
if (!navigator.gpu)und sehen Sie einen Rückfall (z. B. WebGL) vor.