Signatur
Beschreibung
Mit beginComputePass() öffnest du innerhalb eines GPUCommandEncoder einen Compute-Pass. Das ist ein Abschnitt im Befehlspuffer, in dem Compute-Shader (@compute in WGSL) ausgeführt werden. Der zurückgegebene GPUComputePassEncoder bietet unter anderem setPipeline(), setBindGroup(), dispatchWorkgroups() und dispatchWorkgroupsIndirect().
Compute-Passes nutzt du für allgemeine Berechnungen auf der GPU (GPGPU), etwa Bildfilter, Partikelsimulationen, Matrixrechnungen, Vorverarbeitung für Machine Learning oder Culling vor einem Render-Pass. Anders als bei beginRenderPass() gibt es keine Farb- oder Tiefen-Anhänge. Die Daten laufen ausschließlich über Bind-Groups (Storage-Buffer, Storage-Texturen, Uniforms).
Ablauf: device.createCommandEncoder() → beginComputePass() → Befehle aufzeichnen → pass.end() → encoder.finish() → device.queue.submit([...]). Solange ein Pass offen ist, ist der GPUCommandEncoder gesperrt. Weitere Encoder-Befehle wie copyBufferToBuffer() oder ein neuer Pass sind erst nach end() erlaubt.
Über das optionale timestampWrites kannst du GPU-Zeitstempel am Anfang und Ende des Passes in ein GPUQuerySet schreiben lassen. So misst du die Laufzeit auf der GPU. Voraussetzung dafür ist das Feature "timestamp-query".
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $descriptor | GPUComputePassDescriptor | {} | Optionales Konfigurationsobjekt für den Pass. |
| $descriptor.label | string | "" | Name zur Fehlersuche. Er erscheint in Fehlermeldungen und in GPU-Debug-Werkzeugen. |
| $descriptor.timestampWrites | GPUComputePassTimestampWrites | Objekt mit querySet (ein GPUQuerySet vom Typ "timestamp") sowie optional beginningOfPassWriteIndex und endOfPassWriteIndex. Setzt das Feature "timestamp-query" voraus. |
Rückgabewert
end() abgeschlossen werden.Beispiele
Array auf der GPU verdoppeln
const adapter = await navigator.gpu?.requestAdapter();
if (!adapter) throw new Error('WebGPU nicht verfügbar');
const device = await adapter.requestDevice();
const input = new Float32Array([1, 2, 3, 4]);
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 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({ label: 'Verdoppeln' });
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.dispatchWorkgroups(Math.ceil(input.length / 64));
pass.end(); // Pflicht, bevor der Encoder weiterverwendet wird
encoder.copyBufferToBuffer(storage, 0, readback, 0, input.byteLength);
device.queue.submit([encoder.finish()]);
await readback.mapAsync(GPUMapMode.READ);
console.log(Array.from(new Float32Array(readback.getMappedRange())));
readback.unmap();
Laufzeit des Passes mit timestampWrites messen
const adapter = await navigator.gpu.requestAdapter();
if (!adapter.features.has('timestamp-query')) {
throw new Error('timestamp-query wird nicht unterstützt');
}
const device = await adapter.requestDevice({ requiredFeatures: ['timestamp-query'] });
const querySet = device.createQuerySet({ type: 'timestamp', count: 2 });
const resolveBuf = device.createBuffer({
size: 16,
usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC
});
const resultBuf = device.createBuffer({
size: 16,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
});
const encoder = device.createCommandEncoder();
const pass = encoder.beginComputePass({
label: 'Messung',
timestampWrites: { querySet, beginningOfPassWriteIndex: 0, endOfPassWriteIndex: 1 }
});
// ... hier setPipeline / setBindGroup / dispatchWorkgroups aufrufen
pass.end();
encoder.resolveQuerySet(querySet, 0, 2, resolveBuf, 0);
encoder.copyBufferToBuffer(resolveBuf, 0, resultBuf, 0, 16);
device.queue.submit([encoder.finish()]);
await resultBuf.mapAsync(GPUMapMode.READ);
const [start, end] = new BigUint64Array(resultBuf.getMappedRange());
console.log(`GPU-Zeit: ${Number(end - start) / 1e6} ms`);
resultBuf.unmap();
Mehrere Passes nacheinander in einem Encoder
// Annahme: device, pipelineA/B und bindGroupA/B existieren bereits
const encoder = device.createCommandEncoder({ label: 'Simulation' });
const physics = encoder.beginComputePass({ label: 'Physik' });
physics.setPipeline(pipelineA);
physics.setBindGroup(0, bindGroupA);
physics.dispatchWorkgroups(128);
physics.end();
// Der zweite Pass sieht die Ergebnisse des ersten Passes
const collide = encoder.beginComputePass({ label: 'Kollision' });
collide.setPipeline(pipelineB);
collide.setBindGroup(0, bindGroupB);
collide.dispatchWorkgroups(128);
collide.end();
device.queue.submit([encoder.finish()]);