diff --git a/sample/timestampQuery/TimestampQueryManager.ts b/sample/timestampQuery/TimestampQueryManager.ts index 46696418..4d0533d3 100644 --- a/sample/timestampQuery/TimestampQueryManager.ts +++ b/sample/timestampQuery/TimestampQueryManager.ts @@ -4,94 +4,106 @@ 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; + #timestampQuerySet: GPUQuerySet; // A buffer where to store query results - timestampBuffer: GPUBuffer; + #timestampBuffer: GPUBuffer; // A buffer to map this result back to CPU - timestampMapBuffer: GPUBuffer; + #timestampMapBuffer: GPUBuffer; - // State used to avoid firing concurrent readback of timestamp values - hasOngoingTimestampReadback: boolean; + // Last queried elapsed time of the pass in nanoseconds. + passDurationMeasurementNs: number; // Device must have the "timestamp-query" feature - constructor(device: GPUDevice, timestampCount: number) { + constructor(device: GPUDevice) { this.timestampSupported = device.features.has('timestamp-query'); if (!this.timestampSupported) return; - this.timestampCount = timestampCount; + this.passDurationMeasurementNs = 0; // Create timestamp queries - this.timestampQuerySet = device.createQuerySet({ + this.#timestampQuerySet = device.createQuerySet({ type: 'timestamp', - count: timestampCount, // begin and end + count: 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, + this.#timestampBuffer = device.createBuffer({ + 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, + this.#timestampMapBuffer = device.createBuffer({ + size: this.#timestampBuffer.size, usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, }); + } - this.hasOngoingTimestampReadback = false; + // Add both a start and end timestamp. + addTimestampWrite( + passDescriptor: GPURenderPassDescriptor | GPUComputePassDescriptor + ) { + if (this.timestampSupported) { + // We instruct the render pass to write to the timestamp query before/after + passDescriptor.timestampWrites = { + querySet: this.#timestampQuerySet, + beginningOfPassWriteIndex: 0, + endOfPassWriteIndex: 1, + }; + } + return passDescriptor; } - // Resolve all timestamp queries and copy the result into the map buffer - resolveAll(commandEncoder: GPUCommandEncoder) { + // Resolve the timestamp queries and copy the result into the mappable buffer if possible. + resolve(commandEncoder: GPUCommandEncoder) { if (!this.timestampSupported) return; // After the end of the measured render pass, we resolve queries into a // dedicated buffer. commandEncoder.resolveQuerySet( - this.timestampQuerySet, + this.#timestampQuerySet, 0 /* firstQuery */, - this.timestampCount /* queryCount */, - this.timestampBuffer, + this.#timestampQuerySet.count /* queryCount */, + this.#timestampBuffer, 0 /* destinationOffset */ ); - if (!this.hasOngoingTimestampReadback) { - // Copy values to the mapped buffer + if (this.#timestampMapBuffer.mapState === 'unmapped') { + // Copy values to the mappable buffer commandEncoder.copyBufferToBuffer( - this.timestampBuffer, + this.#timestampBuffer, 0, - this.timestampMapBuffer, + this.#timestampMapBuffer, 0, - this.timestampBuffer.size + this.#timestampBuffer.size ); } } - // Once resolved, we can read back the value of timestamps - readAsync(onTimestampReadBack: (timestamps: BigUint64Array) => void): void { + // Read the values of timestamps. + tryInitiateTimestampDownload(): void { if (!this.timestampSupported) return; - if (this.hasOngoingTimestampReadback) return; + if (this.#timestampMapBuffer.mapState !== 'unmapped') return; - this.hasOngoingTimestampReadback = true; - - const buffer = this.timestampMapBuffer; + const buffer = this.#timestampMapBuffer; void buffer.mapAsync(GPUMapMode.READ).then(() => { const rawData = buffer.getMappedRange(); const timestamps = new BigUint64Array(rawData); - - onTimestampReadBack(timestamps); - + // Subtract the begin time from the end time. + // Cast into number. Number can be 9007199254740991 as max integer + // which is 109 days of nano seconds. + const elapsedNs = Number(timestamps[1] - timestamps[0]); + // It's possible elapsedNs is negative which means it's invalid + // (see spec https://gpuweb.github.io/gpuweb/#timestamp) + if (elapsedNs >= 0) { + this.passDurationMeasurementNs = elapsedNs; + } buffer.unmap(); - this.hasOngoingTimestampReadback = false; }); } } diff --git a/sample/timestampQuery/index.html b/sample/timestampQuery/index.html index 5094605f..a25ccfbb 100644 --- a/sample/timestampQuery/index.html +++ b/sample/timestampQuery/index.html @@ -20,11 +20,24 @@ max-width: 100%; display: block; } + #info { + color: white; + background-color: black; + position: absolute; + top: 10px; + left: 10px; + } + #info pre { + margin: 0.5em; + }
+