Signatur
Beschreibung
Mit createQuerySet() legst du auf einem GPUDevice einen Satz von Abfrage-Slots an. Jeder Slot speichert ein 64-Bit-Ergebnis. Es gibt zwei Abfragetypen:
"occlusion": zählt, ob bzw. wie viele Fragmente eines Zeichenaufrufs den Tiefen- und Stencil-Test bestanden haben. So kannst du prüfen, ob ein Objekt sichtbar ist (Occlusion Culling)."timestamp": speichert GPU-Zeitstempel in Nanosekunden am Anfang und Ende eines Passes. Damit misst du die Laufzeit auf der GPU. Dafür muss das Device mit dem Feature"timestamp-query"angefordert worden sein.
Die Ergebnisse lassen sich nicht direkt aus JavaScript lesen. Der übliche Ablauf: Du ordnest das QuerySet einem Pass zu (occlusionQuerySet bzw. timestampWrites im Pass-Deskriptor). Dann kopierst du die Werte mit GPUCommandEncoder.resolveQuerySet() in einen Puffer mit dem Flag QUERY_RESOLVE. Von dort kopierst du sie in einen mappbaren Puffer (MAP_READ) und liest sie als BigUint64Array aus.
Brauchst du ein QuerySet nicht mehr, gib seine GPU-Ressourcen mit querySet.destroy() frei.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $descriptor Pflicht | GPUQuerySetDescriptor | Konfigurationsobjekt mit folgenden Eigenschaften. | |
| $descriptor.type Pflicht | "occlusion" | "timestamp" | Art der Abfragen. "timestamp" setzt das Device-Feature "timestamp-query" voraus. |
|
| $descriptor.count Pflicht | number | Anzahl der Abfrage-Slots. Höchstens 4096. | |
| $descriptor.label | string | "" | Optionaler Name für Fehlermeldungen und Debugging-Werkzeuge. |
Rückgabewert
GPUQuerySet-Objekt. Es hat die schreibgeschützten Eigenschaften type, count und label sowie die Methode destroy().Beispiele
Okklusions-QuerySet anlegen
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
// Ein Slot pro Objekt, dessen Sichtbarkeit geprüft werden soll
const occlusionSet = device.createQuerySet({
label: 'Sichtbarkeitstest',
type: 'occlusion',
count: 32,
});
console.log(occlusionSet.type, occlusionSet.count);
GPU-Laufzeit eines Compute-Passes 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 });
// Zielpuffer für resolveQuerySet: 2 × 8 Byte
const resolveBuffer = device.createBuffer({
size: 16,
usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC,
});
const readBuffer = device.createBuffer({
size: 16,
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
});
const encoder = device.createCommandEncoder();
const pass = encoder.beginComputePass({
timestampWrites: {
querySet,
beginningOfPassWriteIndex: 0,
endOfPassWriteIndex: 1,
},
});
// ... pass.setPipeline(...); pass.dispatchWorkgroups(...);
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();
querySet.destroy();
Okklusionsabfrage in einem Render-Pass
// Voraussetzung: device, occlusionSet (count ≥ 1), colorView, depthView, pipeline
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [{ view: colorView, loadOp: 'clear', storeOp: 'store' }],
depthStencilAttachment: {
view: depthView,
depthClearValue: 1,
depthLoadOp: 'clear',
depthStoreOp: 'store',
},
occlusionQuerySet: occlusionSet,
});
pass.setPipeline(pipeline);
pass.beginOcclusionQuery(0);
pass.draw(36); // Bounding-Box des Objekts
pass.endOcclusionQuery();
pass.end();
// Anschließend mit resolveQuerySet() auslesen: Wert 0 bedeutet "verdeckt"
// Wichtig · Fallstricke
- Feature nötig:
type: \"timestamp\"ohne das Feature\"timestamp-query\"führt zu einem Validierungsfehler. Ebenso eincountüber 4096. Wie bei WebGPU üblich wird dabei keine Exception geworfen: Du erhältst ein ungültiges Objekt, und der Fehler wird überpushErrorScope()/popErrorScope()oder dasuncapturederror-Event gemeldet. - Ungenaue Zeitstempel: Zum Schutz vor Timing-Angriffen runden Browser die Zeitstempel oft auf etwa 100 µs. Die Werte können auch nicht monoton sein, also
end < start. Prüfe darauf, bevor du Differenzen bildest. - Bei
resolveQuerySet()muss der Ziel-Offset ein Vielfaches von 256 sein. Der Puffer braucht das FlagGPUBufferUsage.QUERY_RESOLVE. - Die Ergebnisse sind 64-Bit-Ganzzahlen. Lies sie mit
BigUint64Arrayund nicht mitFloat32Array. - Das alte API
writeTimestamp()auf Encodern wurde entfernt. Verwende stattdessentimestampWritesim Pass-Deskriptor. - WebGPU gibt es nur in sicheren Kontexten und noch nicht in allen Browsern. Prüfe vorher, ob
navigator.gpuvorhanden ist.