Start · Sprachen · JavaScript · Referenz · GPUQuerySet

GPUQuerySet

Schnittstelle

Ein <code>GPUQuerySet</code> ist ein Container für GPU-Abfrageergebnisse, z. B. Okklusions- oder Zeitstempelabfragen innerhalb von Render- und Compute-Durchläufen.

seit JavaScript WebGPU: Chrome/Edge 113+, Firefox 141+ (Windows), Kategorie: webapi

Signatur

interface GPUQuerySet

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 zwischen beginOcclusionQuery() und endOcclusionQuery() den Tiefen- und Stencil-Test bestanden haben. Das nutzt man z. B. für Occlusion Culling. Das Set wird über occlusionQuerySet im Render-Pass-Deskriptor angegeben.
  • "timestamp": schreibt GPU-Zeitstempel in Nanosekunden zu Beginn und Ende eines Durchlaufs, angegeben über timestampWrites. 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();
occlusion 32 Sichtbarkeitstest

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();
GPU-Zeit: 0.042 ms

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 Nutzung GPUBufferUsage.QUERY_RESOLVE.
  • Werte sind 64-Bit-Ganzzahlen. Lies sie mit BigUint64Array aus, nicht mit Float64Array.
  • Nach destroy() darf das Set nicht mehr in Befehlen verwendet werden.