Signatur
Beschreibung
Die Schnittstelle GPUQuerySet der WebGPU API ist eine feste Menge von Abfrageplätzen (Slots), in die die GPU während eines Durchlaufs Messwerte schreibt. Erzeugt wird sie über GPUDevice.createQuerySet(). Einen öffentlichen Konstruktor gibt es nicht.
Es gibt zwei Abfragetypen:
"occlusion": zählt, ob bzw. wie viele Fragmente zwischenbeginOcclusionQuery()undendOcclusionQuery()den Tiefen- und Stencil-Test bestanden haben. Das nutzt man z. B. für Occlusion Culling. Das Set wird überocclusionQuerySetim Render-Pass-Deskriptor angegeben."timestamp": schreibt GPU-Zeitstempel in Nanosekunden zu Beginn und Ende eines Durchlaufs, angegeben übertimestampWrites. Das dient der Messung der GPU-Leistung. Dafür muss das Feature"timestamp-query"angefordert werden.
Die Ergebnisse sind 64-Bit-Ganzzahlen ohne Vorzeichen. Mit GPUCommandEncoder.resolveQuerySet() werden sie in einen GPUBuffer mit QUERY_RESOLVE-Nutzung kopiert. Danach kopiert man sie in einen Buffer mit MAP_READ-Nutzung und liest sie auf der CPU aus, z. B. als BigUint64Array.
Instanzeigenschaften: count (Anzahl der Slots, nur lesbar), type ("occlusion" oder "timestamp", nur lesbar) und label (Bezeichnung zur Fehlersuche). Instanzmethode: destroy() gibt die GPU-Ressourcen frei.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $descriptor.type Pflicht | "occlusion" | "timestamp" | Art der Abfragen im Set. Wird an device.createQuerySet() übergeben. |
|
| $descriptor.count Pflicht | number | Anzahl der Abfrage-Slots, höchstens 4096. | |
| $descriptor.label | string | "" | Optionale Bezeichnung, die in Fehlermeldungen und Debug-Werkzeugen erscheint. |
Beispiele
QuerySet anlegen und Eigenschaften lesen
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const occlusionSet = device.createQuerySet({
label: 'Sichtbarkeitstest',
type: 'occlusion',
count: 32
});
console.log(occlusionSet.type, occlusionSet.count, occlusionSet.label);
// Ressourcen freigeben, wenn das Set nicht mehr gebraucht wird
occlusionSet.destroy();
GPU-Laufzeit eines Compute-Durchlaufs messen
const adapter = await navigator.gpu.requestAdapter();
if (!adapter.features.has('timestamp-query')) {
throw new Error('Zeitstempelabfragen werden nicht unterstützt');
}
const device = await adapter.requestDevice({
requiredFeatures: ['timestamp-query']
});
const querySet = device.createQuerySet({ type: 'timestamp', count: 2 });
// Zielpuffer für resolveQuerySet (je Slot 8 Byte)
const resolveBuffer = device.createBuffer({
size: 2 * 8,
usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC
});
const readBuffer = device.createBuffer({
size: 2 * 8,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
});
const module = device.createShaderModule({
code: '@compute @workgroup_size(64) fn main() {}'
});
const pipeline = device.createComputePipeline({
layout: 'auto',
compute: { module, entryPoint: 'main' }
});
const encoder = device.createCommandEncoder();
const pass = encoder.beginComputePass({
timestampWrites: {
querySet,
beginningOfPassWriteIndex: 0,
endOfPassWriteIndex: 1
}
});
pass.setPipeline(pipeline);
pass.dispatchWorkgroups(1024);
pass.end();
encoder.resolveQuerySet(querySet, 0, 2, resolveBuffer, 0);
encoder.copyBufferToBuffer(resolveBuffer, 0, readBuffer, 0, 16);
device.queue.submit([encoder.finish()]);
await readBuffer.mapAsync(GPUMapMode.READ);
const [start, end] = new BigUint64Array(readBuffer.getMappedRange());
console.log(`GPU-Zeit: ${Number(end - start) / 1e6} ms`);
readBuffer.unmap();
Okklusionsabfrage in einem Render-Durchlauf
// Annahmen: device, context, pipeline, depthView und
// objects (Array mit vertexCount) existieren bereits
const querySet = device.createQuerySet({ type: 'occlusion', count: objects.length });
const resolveBuffer = device.createBuffer({
size: objects.length * 8,
usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC
});
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
loadOp: 'clear', storeOp: 'store'
}],
depthStencilAttachment: {
view: depthView,
depthLoadOp: 'clear', depthClearValue: 1, depthStoreOp: 'store'
},
occlusionQuerySet: querySet
});
pass.setPipeline(pipeline);
objects.forEach((obj, i) => {
pass.beginOcclusionQuery(i);
pass.draw(obj.vertexCount);
pass.endOcclusionQuery();
});
pass.end();
encoder.resolveQuerySet(querySet, 0, objects.length, resolveBuffer, 0);
device.queue.submit([encoder.finish()]);
// Ein Wert ungleich 0 bedeutet: Das Objekt war (teilweise) sichtbar
// Wichtig · Fallstricke
- Browser-Unterstützung: WebGPU ist nur in sicheren Kontexten (HTTPS) verfügbar und noch nicht in allen Browsern und auf allen Plattformen aktiviert. Prüfe vorher
navigator.gpu. - Feature erforderlich: Timestamp-Sets funktionieren nur, wenn beim Anfordern des Geräts
requiredFeatures: ['timestamp-query']angegeben wurde. Sonst gibt es einen Validierungsfehler. - Datenschutz und Genauigkeit: Browser runden Zeitstempel aus Sicherheitsgründen (Timing-Angriffe) oft grob, z. B. auf 100 µs. Werte können auch 0 sein oder nicht monoton steigen. Nutze sie nur als grobe Richtwerte.
- Ein Okklusionswert ist nur verlässlich als „0 = unsichtbar / ungleich 0 = sichtbar“. Die genaue Fragmentanzahl ist nicht garantiert.
- Der Offset in
resolveQuerySet()muss ein Vielfaches von 256 sein. Der Zielpuffer braucht die NutzungGPUBufferUsage.QUERY_RESOLVE. - Werte sind 64-Bit-Ganzzahlen. Lies sie mit
BigUint64Arrayaus, nicht mitFloat64Array. - Nach
destroy()darf das Set nicht mehr in Befehlen verwendet werden.