Signatur
Beschreibung
end() schließt einen Compute-Pass ab, den du vorher mit GPUCommandEncoder.beginComputePass() geöffnet hast. Zwischen Öffnen und Beenden zeichnest du Befehle wie setPipeline(), setBindGroup() und dispatchWorkgroups() auf. Erst mit end() ist der Pass abgeschlossen. Danach kannst du auf dem GPUCommandEncoder weitere Befehle aufzeichnen, zum Beispiel copyBufferToBuffer() oder einen weiteren Pass, oder den Encoder mit finish() abschließen.
Solange ein Pass offen ist, ist der übergeordnete Command-Encoder gesperrt. Rufst du finish() auf, ohne vorher end() aufzurufen, entsteht ein Validierungsfehler und der erzeugte GPUCommandBuffer ist ungültig.
Nach end() ist der Pass-Encoder verbraucht: Weitere Aufrufe von Methoden auf demselben GPUComputePassEncoder führen zu Validierungsfehlern. Für einen neuen Pass rufst du erneut beginComputePass() auf.
Rückgabewert
pushErrorScope()/popErrorScope() bzw. das Ereignis uncapturederror).Beispiele
Einfacher Compute-Pass: Werte in einem Buffer verdoppeln
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 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 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(Math.ceil(input.length / 64));
pass.end(); // Pass abschließen – Encoder ist wieder frei
// Ergebnis in einen lesbaren Buffer kopieren
const readback = device.createBuffer({
size: input.byteLength,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
});
encoder.copyBufferToBuffer(storage, 0, readback, 0, input.byteLength);
device.queue.submit([encoder.finish()]);
await readback.mapAsync(GPUMapMode.READ);
console.log(new Float32Array(readback.getMappedRange().slice(0)));
readback.unmap();
Mehrere Passes nacheinander in einem Command-Encoder
// Annahme: device, pipelineA, pipelineB, bindGroupA, bindGroupB existieren
const encoder = device.createCommandEncoder({ label: 'Simulation' });
const passA = encoder.beginComputePass({ label: 'Kräfte berechnen' });
passA.setPipeline(pipelineA);
passA.setBindGroup(0, bindGroupA);
passA.dispatchWorkgroups(128);
passA.end(); // muss vor dem nächsten Pass stehen
const passB = encoder.beginComputePass({ label: 'Positionen integrieren' });
passB.setPipeline(pipelineB);
passB.setBindGroup(0, bindGroupB);
passB.dispatchWorkgroups(128);
passB.end();
device.queue.submit([encoder.finish()]);
Fehlendes end() mit Error Scopes erkennen
device.pushErrorScope('validation');
const encoder = device.createCommandEncoder();
const pass = encoder.beginComputePass();
pass.setPipeline(pipeline);
pass.dispatchWorkgroups(1);
// pass.end(); fehlt absichtlich
const commandBuffer = encoder.finish();
device.queue.submit([commandBuffer]);
const error = await device.popErrorScope();
if (error) console.warn('Validierungsfehler:', error.message);
// Wichtig · Fallstricke
- Pflichtaufruf: Ohne
end()bleibt derGPUCommandEncodergesperrt.finish()liefert dann einen ungültigen Command-Buffer, undsubmit()erzeugt einen Validierungsfehler. - Nur einmal aufrufen: Ein zweiter Aufruf von
end()oder weitere Befehle auf demselben Pass-Encoder führen zu Validierungsfehlern. Diese werden nicht als JavaScript-Ausnahme geworfen, sondern still über Error Scopes bzw.uncapturederrorgemeldet. Das macht das Debuggen schwerer, deshalb helfenlabel-Angaben. - Gleichgewicht bei Debug-Gruppen: Jeder Aufruf von
pushDebugGroup()braucht vorend()ein passendespopDebugGroup(), sonst ist der Pass ungültig. - Keine Ausführung:
end()führt nichts auf der GPU aus. Die Befehle laufen erst nachdevice.queue.submit(). - Veraltet: In frühen Entwürfen der Spezifikation hieß die Methode
endPass(). Sie wurde inend()umbenannt und sollte nicht mehr verwendet werden. - Kompatibilität: WebGPU steht nur in sicheren Kontexten (HTTPS) zur Verfügung. Die Unterstützung ist je nach Browser und Plattform noch uneinheitlich. Prüfe deshalb vorher mit
navigator.gpuundrequestAdapter(), ob WebGPU verfügbar ist.