Signatur
Beschreibung
createCommandEncoder() ist der Einstiegspunkt für jede Arbeit, die in WebGPU auf der GPU ausgeführt werden soll. Der zurückgegebene GPUCommandEncoder zeichnet Befehle auf, führt sie aber noch nicht aus: Mit beginRenderPass() und beginComputePass() startest du Render- bzw. Compute-Durchläufe, mit copyBufferToBuffer(), copyBufferToTexture() und ähnlichen Methoden kodierst du Kopiervorgänge.
Sind alle Befehle aufgezeichnet, rufst du encoder.finish() auf. Das liefert einen GPUCommandBuffer, den du mit device.queue.submit([commandBuffer]) an die GPU übergibst. Ein Encoder kann nach finish() nicht wiederverwendet werden. Üblich ist deshalb, pro Frame bzw. pro Arbeitspaket einen neuen Encoder zu erzeugen. Das ist billig und so auch vorgesehen.
Über den optionalen Deskriptor kannst du ein label vergeben. Dieses Label taucht in Fehlermeldungen, Validierungswarnungen und Debug-Werkzeugen auf. Gerade in größeren Anwendungen mit vielen Encodern ist das sehr hilfreich.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $descriptor | GPUCommandEncoderDescriptor | {} | Optionales Konfigurationsobjekt. Enthält derzeit nur die Eigenschaft label. |
| $descriptor.label | string | "" | Bezeichnung zur Identifikation des Encoders in Fehlermeldungen und Debug-Werkzeugen. |
Rückgabewert
GPUCommandEncoder, mit dem Befehle aufgezeichnet werden. Mit finish() wird daraus ein GPUCommandBuffer.Beispiele
Canvas mit einer Farbe löschen (Render-Pass)
const adapter = await navigator.gpu?.requestAdapter();
if (!adapter) throw new Error('WebGPU wird nicht unterstützt');
const device = await adapter.requestDevice();
const canvas = document.querySelector('canvas');
const context = canvas.getContext('webgpu');
context.configure({ device, format: navigator.gpu.getPreferredCanvasFormat() });
// Encoder für diesen Frame erzeugen
const encoder = device.createCommandEncoder({ label: 'Frame-Encoder' });
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();
// Aufzeichnung abschließen und an die GPU übergeben
device.queue.submit([encoder.finish()]);
Buffer kopieren und Ergebnis zurücklesen
const device = await (await navigator.gpu.requestAdapter()).requestDevice();
const data = new Float32Array([1, 2, 3, 4]);
const src = device.createBuffer({
size: data.byteLength,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(src, 0, data);
const readback = device.createBuffer({
size: data.byteLength,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
});
const encoder = device.createCommandEncoder({ label: 'Kopier-Encoder' });
encoder.copyBufferToBuffer(src, 0, readback, 0, data.byteLength);
device.queue.submit([encoder.finish()]);
await readback.mapAsync(GPUMapMode.READ);
console.log(Array.from(new Float32Array(readback.getMappedRange())));
readback.unmap();
Compute-Shader ausführen
const device = await (await navigator.gpu.requestAdapter()).requestDevice();
const module = device.createShaderModule({
code: `
@group(0) @binding(0) var<storage, read_write> werte: array<f32>;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3u) {
if (id.x < arrayLength(&werte)) { werte[id.x] = werte[id.x] * 2.0; }
}`
});
const pipeline = device.createComputePipeline({
layout: 'auto',
compute: { module, entryPoint: 'main' }
});
const buffer = device.createBuffer({
size: 256 * 4,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
});
const bindGroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [{ binding: 0, resource: { buffer } }]
});
const encoder = device.createCommandEncoder({ label: 'Compute-Encoder' });
const pass = encoder.beginComputePass();
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.dispatchWorkgroups(Math.ceil(256 / 64));
pass.end();
device.queue.submit([encoder.finish()]);
// Wichtig · Fallstricke
- Nur einmal verwendbar: Nach
finish()ist der Encoder ungültig. Weitere Aufrufe darauf führen zu einem Validierungsfehler. Erzeuge pro Frame einen neuen Encoder. - Passes schließen: Jeder mit
beginRenderPass()oderbeginComputePass()geöffnete Pass muss mitend()beendet werden, bevor dufinish()aufrufst. Solange ein Pass offen ist, ist der Encoder gesperrt. - Keine Ausführung ohne Übergabe: Aufgezeichnete Befehle laufen erst, wenn der Befehlspuffer per
device.queue.submit()übergeben wird. - Fehler kommen verzögert: Validierungsfehler werfen keine Ausnahme. Sie landen im Fehlerbereich des Geräts. Fange sie mit
device.pushErrorScope()/popErrorScope()oder über das Ereignisuncapturederrorab. - Kompatibilität: WebGPU gibt es nur in sicheren Kontexten (HTTPS) und noch nicht auf allen Plattformen. Prüfe vorab mit
navigator.gpu, ob die API vorhanden ist, und sieh eine Ausweichlösung vor, zum Beispiel WebGL. Seit Chrome 113 ist WebGPU auch in Web Workern verfügbar.