Start · Sprachen · JavaScript · Referenz · GPUCommandEncoder.resolveQuerySet()

GPUCommandEncoder.resolveQuerySet()

Methode

Kodiert einen Befehl, der Abfragen aus einem <code>GPUQuerySet</code> auflöst und die Ergebnisse als 64-Bit-Ganzzahlen in einen <code>GPUBuffer</code> schreibt.

seit JavaScript WebGPU – Chrome/Edge 113+, Safari 26+, Firefox 141 Kategorie: webapi

Signatur

GPUCommandEncoder.prototype.resolveQuerySet(querySet, firstQuery, queryCount, destination, destinationOffset)

Beschreibung

resolveQuerySet() ist der letzte Schritt, wenn man mit WebGPU-Abfragen arbeitet. Während eines Render- oder Compute-Passes schreibt die GPU Messwerte in ein GPUQuerySet: Zeitstempel ("timestamp") oder Verdeckungswerte ("occlusion"). Diese Werte liegen in einem undurchsichtigen, GPU-internen Format. Mit resolveQuerySet() wandelt der Command-Encoder sie in einen normalen Puffer um, den man anschließend auslesen kann.

Jedes Ergebnis belegt 8 Byte (vorzeichenloser 64-Bit-Integer). Man liest die Werte deshalb am besten mit einem BigUint64Array aus. Zeitstempel sind in Nanosekunden angegeben. Bei Verdeckungsabfragen ist ein Wert ungleich 0 das Zeichen dafür, dass mindestens ein Sample den Tiefen- und Stenciltest bestanden hat.

Der Zielpuffer braucht das Usage-Flag GPUBufferUsage.QUERY_RESOLVE. Ein solcher Puffer darf aber nicht gleichzeitig MAP_READ haben. Das übliche Vorgehen sieht so aus:

  • Die Ergebnisse in einen Puffer mit QUERY_RESOLVE | COPY_SRC auflösen.
  • Mit copyBufferToBuffer() in einen Staging-Puffer mit COPY_DST | MAP_READ kopieren.
  • Den Staging-Puffer mit mapAsync() auf der CPU lesen.

Die Methode führt nichts sofort aus. Sie hängt nur einen Befehl an den Encoder an. Ausgeführt wird er erst, wenn der mit finish() erzeugte Command-Buffer über device.queue.submit() abgeschickt wird.

Parameter

Name Typ Default Beschreibung
$querySet Pflicht GPUQuerySet Das Query-Set, dessen Ergebnisse aufgelöst werden.
$firstQuery Pflicht number Index der ersten Abfrage im Set, die aufgelöst wird.
$queryCount Pflicht number Anzahl der aufzulösenden Abfragen. firstQuery + queryCount darf nicht größer sein als querySet.count.
$destination Pflicht GPUBuffer Zielpuffer. Er muss mit GPUBufferUsage.QUERY_RESOLVE erstellt worden sein.
$destinationOffset Pflicht number Byte-Offset im Zielpuffer. Er muss ein Vielfaches von 256 sein, und destinationOffset + 8 * queryCount darf destination.size nicht überschreiten.

Rückgabewert

Typ
undefined
Beschreibung
Kein Rückgabewert. Der Befehl wird nur im Encoder aufgezeichnet.

Beispiele

GPU-Laufzeit eines Compute-Passes mit Zeitstempeln 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 });

// Puffer für das Auflösen (nicht mappbar)
const resolveBuffer = device.createBuffer({
  size: 2 * 8,
  usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC,
});
// Staging-Puffer zum Lesen auf der CPU
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();

// Abfragen 0 und 1 an Offset 0 auflösen
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

Verdeckungsabfrage (Occlusion Query) auswerten

// Annahme: device, renderPassDescriptor (mit Tiefenpuffer), pipeline und
// vertexBuffer sind bereits eingerichtet.
const OBJECTS = 4;
const occlusionSet = device.createQuerySet({ type: 'occlusion', count: OBJECTS });

const resolveBuffer = device.createBuffer({
  size: OBJECTS * 8,
  usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC,
});
const readBuffer = device.createBuffer({
  size: OBJECTS * 8,
  usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
});

const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
  ...renderPassDescriptor,
  occlusionQuerySet: occlusionSet,
});
pass.setPipeline(pipeline);
pass.setVertexBuffer(0, vertexBuffer);
for (let i = 0; i < OBJECTS; i++) {
  pass.beginOcclusionQuery(i);
  pass.draw(36, 1, i * 36); // Bounding-Box von Objekt i
  pass.endOcclusionQuery();
}
pass.end();

encoder.resolveQuerySet(occlusionSet, 0, OBJECTS, resolveBuffer, 0);
encoder.copyBufferToBuffer(resolveBuffer, 0, readBuffer, 0, OBJECTS * 8);
device.queue.submit([encoder.finish()]);

await readBuffer.mapAsync(GPUMapMode.READ);
const results = new BigUint64Array(readBuffer.getMappedRange());
const visible = [...results].map((v) => v !== 0n);
console.log('Sichtbar:', visible);
readBuffer.unmap();
Sichtbar: [true, false, true, true]

Teilbereich an einen ausgerichteten Offset auflösen

// Abfragen 4 bis 7 aus einem Set mit 8 Abfragen an Byte-Offset 256 schreiben
const querySet = device.createQuerySet({ type: 'occlusion', count: 8 });
const buffer = device.createBuffer({
  size: 256 + 4 * 8, // Offset + 4 Ergebnisse à 8 Byte
  usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC,
});

const encoder = device.createCommandEncoder();
// ... Render-Pass mit Occlusion-Queries ...
encoder.resolveQuerySet(querySet, 4, 4, buffer, 256); // gültig: 256 % 256 === 0
// encoder.resolveQuerySet(querySet, 4, 4, buffer, 8); // Validierungsfehler!
device.queue.submit([encoder.finish()]);