From 469f3abb7082838765d5d33af3216c401cd89a26 Mon Sep 17 00:00:00 2001 From: Gregg Tavares Date: Mon, 18 Nov 2024 18:26:58 -0800 Subject: [PATCH] address comments --- .../timestampQuery/TimestampQueryManager.ts | 84 +++++++++---------- sample/timestampQuery/index.html | 13 +++ sample/timestampQuery/main.ts | 42 ++++------ 3 files changed, 69 insertions(+), 70 deletions(-) diff --git a/sample/timestampQuery/TimestampQueryManager.ts b/sample/timestampQuery/TimestampQueryManager.ts index 54be3c42..1bdb7fc4 100644 --- a/sample/timestampQuery/TimestampQueryManager.ts +++ b/sample/timestampQuery/TimestampQueryManager.ts @@ -6,106 +6,102 @@ export default class TimestampQueryManager { // 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; - // Last times - timestamps: number[]; + // Last queried elapsed time of the pass. + passElapsedTime: number; // Device must have the "timestamp-query" feature - constructor(device: GPUDevice, timestampPairCount: number) { + constructor(device: GPUDevice) { this.timestampSupported = device.features.has('timestamp-query'); if (!this.timestampSupported) return; - this.timestamps = Array(timestampPairCount).fill(0); + this.passElapsedTime = 0; // Create timestamp queries - this.timestampQuerySet = device.createQuerySet({ + this.#timestampQuerySet = device.createQuerySet({ type: 'timestamp', - count: timestampPairCount * 2, // 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 - this.timestampBuffer = device.createBuffer({ - size: this.timestampQuerySet.count * timestampByteSize, + 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: this.timestampBuffer.size, + this.#timestampMapBuffer = device.createBuffer({ + size: this.#timestampBuffer.size, usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, }); } // Add both a start and end timestamp. addTimestampWrite( - renderPassDescriptor: GPURenderPassDescriptor, - pairId: number + passDescriptor: GPURenderPassDescriptor | GPUComputePassDescriptor ) { if (this.timestampSupported) { // 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, + passDescriptor.timestampWrites = { + querySet: this.#timestampQuerySet, + beginningOfPassWriteIndex: 0, + endOfPassWriteIndex: 1, }; } - return renderPassDescriptor; + 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.timestampQuerySet.count /* queryCount */, - this.timestampBuffer, + this.#timestampQuerySet.count /* queryCount */, + this.#timestampBuffer, 0 /* destinationOffset */ ); - if (this.timestampMapBuffer.mapState === 'unmapped') { + 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 ); } } - // Read the value of timestamps. - update(): void { + // Read the values of timestamps. + tryInitiateTimestampDownload(): void { if (!this.timestampSupported) return; - if (this.timestampMapBuffer.mapState !== 'unmapped') return; + if (this.#timestampMapBuffer.mapState !== 'unmapped') return; - const buffer = this.timestampMapBuffer; + const buffer = this.#timestampMapBuffer; void buffer.mapAsync(GPUMapMode.READ).then(() => { const rawData = buffer.getMappedRange(); const timestamps = new BigUint64Array(rawData); - 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; - } + // 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.passElapsedTime = elapsedNs; } buffer.unmap(); }); 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; + } +
+

+    
diff --git a/sample/timestampQuery/main.ts b/sample/timestampQuery/main.ts index 326619b7..10dedac3 100644 --- a/sample/timestampQuery/main.ts +++ b/sample/timestampQuery/main.ts @@ -33,7 +33,7 @@ quitIfWebGPUNotAvailable(adapter, device); // NB: Look for 'timestampQueryManager' in this file to locate parts of this // snippets that are related to timestamps. Most of the logic is in // TimestampQueryManager.ts. -const timestampQueryManager = new TimestampQueryManager(device, 2); +const timestampQueryManager = new TimestampQueryManager(device); const renderPassDurationCounter = new PerfCounter(); const context = canvas.getContext('webgpu') as GPUCanvasContext; @@ -48,21 +48,7 @@ context.configure({ format: presentationFormat, }); -// UI for perf counter -const perfDisplayContainer = document.createElement('div'); -perfDisplayContainer.style.color = 'white'; -perfDisplayContainer.style.background = 'black'; -perfDisplayContainer.style.position = 'absolute'; -perfDisplayContainer.style.top = '10px'; -perfDisplayContainer.style.left = '10px'; - -const perfDisplay = document.createElement('pre'); -perfDisplayContainer.appendChild(perfDisplay); -if (canvas.parentNode) { - canvas.parentNode.appendChild(perfDisplayContainer); -} else { - console.error('canvas.parentNode is null'); -} +const perfDisplay = document.querySelector('#info pre'); if (!supportsTimestampQueries) { perfDisplay.innerHTML = 'Timestamp queries are not supported'; @@ -174,7 +160,7 @@ const renderPassDescriptor: GPURenderPassDescriptor = { }, }; -timestampQueryManager.addTimestampWrite(renderPassDescriptor, 0); +timestampQueryManager.addTimestampWrite(renderPassDescriptor); const aspect = canvas.width / canvas.height; const projectionMatrix = mat4.perspective((2 * Math.PI) / 5, aspect, 1, 100.0); @@ -219,18 +205,22 @@ function frame() { // Resolve timestamp queries, so that their result is available in // a GPU-side buffer. - timestampQueryManager.resolveAll(commandEncoder); + timestampQueryManager.resolve(commandEncoder); device.queue.submit([commandEncoder.finish()]); - 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`; + if (timestampQueryManager.timestampSupported) { + // Show the last successfully downloaded elapsed time. + const elapsedNs = timestampQueryManager.passElapsedTime; + // 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`; + } + + timestampQueryManager.tryInitiateTimestampDownload(); requestAnimationFrame(frame); }