diff --git a/sample/timestampQuery/TimestampQueryManager.ts b/sample/timestampQuery/TimestampQueryManager.ts index 46696418..1b39694b 100644 --- a/sample/timestampQuery/TimestampQueryManager.ts +++ b/sample/timestampQuery/TimestampQueryManager.ts @@ -4,9 +4,6 @@ export default class TimestampQueryManager { // class does nothing. timestampSupported: boolean; - // Number of timestamp counters - timestampCount: number; - // The query objects. This is meant to be used in a ComputePassDescriptor's // or RenderPassDescriptor's 'timestampWrites' field. timestampQuerySet: GPUQuerySet; @@ -17,37 +14,50 @@ export default class TimestampQueryManager { // A buffer to map this result back to CPU timestampMapBuffer: GPUBuffer; - // State used to avoid firing concurrent readback of timestamp values - hasOngoingTimestampReadback: boolean; + // Last times + timestamps: number[]; // Device must have the "timestamp-query" feature - constructor(device: GPUDevice, timestampCount: number) { + constructor(device: GPUDevice, timestampPairCount: number) { this.timestampSupported = device.features.has('timestamp-query'); if (!this.timestampSupported) return; - this.timestampCount = timestampCount; + this.timestamps = Array(timestampPairCount).fill(0); // Create timestamp queries this.timestampQuerySet = device.createQuerySet({ type: 'timestamp', - count: timestampCount, // begin and end + count: timestampPairCount * 2, // begin and end }); // Create a buffer where to store the result of GPU queries const timestampByteSize = 8; // timestamps are uint64 - const timestampBufferSize = timestampCount * timestampByteSize; this.timestampBuffer = device.createBuffer({ - size: timestampBufferSize, + size: this.timestampQuerySet.count * timestampByteSize, usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.QUERY_RESOLVE, }); // Create a buffer to map the result back to the CPU this.timestampMapBuffer = device.createBuffer({ - size: timestampBufferSize, + size: this.timestampBuffer.size, usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, }); + } + + // Add both a start and end timestamp. + addTimestampWrite( + renderPassDescriptor: GPURenderPassDescriptor, + pairId: number + ) { + if (!this.timestampSupported) return; - this.hasOngoingTimestampReadback = false; + // We instruct the render pass to write to the timestamp query before/after + const ndx = pairId * 2; + renderPassDescriptor.timestampWrites = { + querySet: this.timestampQuerySet, + beginningOfPassWriteIndex: ndx, + endOfPassWriteIndex: ndx + 1, + }; } // Resolve all timestamp queries and copy the result into the map buffer @@ -59,12 +69,12 @@ export default class TimestampQueryManager { commandEncoder.resolveQuerySet( this.timestampQuerySet, 0 /* firstQuery */, - this.timestampCount /* queryCount */, + this.timestampQuerySet.count /* queryCount */, this.timestampBuffer, 0 /* destinationOffset */ ); - if (!this.hasOngoingTimestampReadback) { + if (this.timestampMapBuffer.mapState === 'unmapped') { // Copy values to the mapped buffer commandEncoder.copyBufferToBuffer( this.timestampBuffer, @@ -77,21 +87,26 @@ export default class TimestampQueryManager { } // Once resolved, we can read back the value of timestamps - readAsync(onTimestampReadBack: (timestamps: BigUint64Array) => void): void { + update(): void { if (!this.timestampSupported) return; - if (this.hasOngoingTimestampReadback) return; - - this.hasOngoingTimestampReadback = true; + if (this.timestampMapBuffer.mapState !== 'unmapped') return; const buffer = this.timestampMapBuffer; void buffer.mapAsync(GPUMapMode.READ).then(() => { const rawData = buffer.getMappedRange(); const timestamps = new BigUint64Array(rawData); - - onTimestampReadBack(timestamps); - + for (let i = 0; i < this.timestamps.length; ++i) { + const ndx = i * 2; + // Cast into number. Number can be 9007199254740991 as max integer + // which is 109 days of nano seconds. + const elapsedNs = Number(timestamps[ndx + 1] - timestamps[ndx]); + // It's possible elapsedNs is negative which means it's invalid + // (see spec https://gpuweb.github.io/gpuweb/#timestamp) + if (elapsedNs >= 0) { + this.timestamps[i] = elapsedNs; + } + } buffer.unmap(); - this.hasOngoingTimestampReadback = false; }); } } diff --git a/sample/timestampQuery/main.ts b/sample/timestampQuery/main.ts index 66f11ad0..326619b7 100644 --- a/sample/timestampQuery/main.ts +++ b/sample/timestampQuery/main.ts @@ -172,14 +172,10 @@ const renderPassDescriptor: GPURenderPassDescriptor = { depthLoadOp: 'clear', depthStoreOp: 'store', }, - // We instruct the render pass to write to the timestamp query before/after - timestampWrites: { - querySet: timestampQueryManager.timestampQuerySet, - beginningOfPassWriteIndex: 0, - endOfPassWriteIndex: 1, - }, }; +timestampQueryManager.addTimestampWrite(renderPassDescriptor, 0); + const aspect = canvas.width / canvas.height; const projectionMatrix = mat4.perspective((2 * Math.PI) / 5, aspect, 1, 100.0); const modelViewProjectionMatrix = mat4.create(); @@ -222,32 +218,19 @@ function frame() { passEncoder.end(); // Resolve timestamp queries, so that their result is available in - // a GPU-sude buffer. + // a GPU-side buffer. timestampQueryManager.resolveAll(commandEncoder); device.queue.submit([commandEncoder.finish()]); - // Read timestamp value back from GPU buffers - timestampQueryManager.readAsync((timestamps) => { - // This may happen (see spec https://gpuweb.github.io/gpuweb/#timestamp) - if (timestamps[1] < timestamps[0]) return; - - // Measure difference (in bigints) - const elapsedNs = timestamps[1] - timestamps[0]; - // Cast into regular int (ok because value is small after difference) - // and convert from nanoseconds to milliseconds: - const elapsedMs = Number(elapsedNs) * 1e-6; - renderPassDurationCounter.addSample(elapsedMs); - console.log( - 'timestamps (ms): elapsed', - elapsedMs, - 'avg', - renderPassDurationCounter.getAverage() - ); - perfDisplay.innerHTML = `Render Pass duration: ${renderPassDurationCounter - .getAverage() - .toFixed(3)} ms ± ${renderPassDurationCounter.getStddev().toFixed(3)} ms`; - }); + timestampQueryManager.update(); + const elapsedNs = timestampQueryManager.timestamps[0]; + // Convert from nanoseconds to milliseconds: + const elapsedMs = Number(elapsedNs) * 1e-6; + renderPassDurationCounter.addSample(elapsedMs); + perfDisplay.innerHTML = `Render Pass duration: ${renderPassDurationCounter + .getAverage() + .toFixed(3)} ms ± ${renderPassDurationCounter.getStddev().toFixed(3)} ms`; requestAnimationFrame(frame); }