From 1da3240e368d6f0daf7f7f0d063a31fd1753ab6e Mon Sep 17 00:00:00 2001 From: Gregg Tavares Date: Fri, 24 May 2024 15:30:23 +0900 Subject: [PATCH] wip2 --- webgpu/timing-helper.js | 121 +++++++++++++++++++++++++++ webgpu/webgpu-optimization-none.html | 81 ++++++++++++++---- 2 files changed, 184 insertions(+), 18 deletions(-) create mode 100644 webgpu/timing-helper.js diff --git a/webgpu/timing-helper.js b/webgpu/timing-helper.js new file mode 100644 index 00000000..dfb9c345 --- /dev/null +++ b/webgpu/timing-helper.js @@ -0,0 +1,121 @@ +export class RollingAverage { + #total = 0; + #samples = []; + #cursor = 0; + #numSamples; + constructor(numSamples = 30) { + this.#numSamples = numSamples; + } + addSample(v) { + if (!Number.isNaN(v) && Number.isFinite(v)) { + this.#total += v - (this.#samples[this.#cursor] || 0); + this.#samples[this.#cursor] = v; + this.#cursor = (this.#cursor + 1) % this.#numSamples; + } + } + get() { + return this.#total / this.#samples.length; + } +} + +function assert(cond, msg = '') { + if (!cond) { + throw new Error(msg); + } +} + +export class TimingHelper { + #canTimestamp; + #device; + #querySet; + #resolveBuffer; + #resultBuffer; + #resultBuffers = []; + // state can be 'free', 'need resolve', 'wait for result' + #state = 'free'; + + constructor(device) { + this.#device = device; + this.#canTimestamp = device.features.has('timestamp-query'); + if (this.#canTimestamp) { + this.#querySet = device.createQuerySet({ + type: 'timestamp', + count: 2, + }); + this.#resolveBuffer = device.createBuffer({ + size: this.#querySet.count * 8, + usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC, + }); + } + } + + #beginTimestampPass(encoder, fnName, descriptor) { + if (this.#canTimestamp) { + assert(this.#state === 'free', 'state not free'); + this.#state = 'need resolve'; + + const pass = encoder[fnName]({ + ...descriptor, + ...{ + timestampWrites: { + querySet: this.#querySet, + beginningOfPassWriteIndex: 0, + endOfPassWriteIndex: 1, + }, + }, + }); + + const resolve = () => this.#resolveTiming(encoder); + pass.end = (function(origFn) { + return function() { + origFn.call(this); + resolve(); + }; + })(pass.end); + + return pass; + } else { + return encoder[fnName](descriptor); + } + } + + beginRenderPass(encoder, descriptor = {}) { + return this.#beginTimestampPass(encoder, 'beginRenderPass', descriptor); + } + + beginComputePass(encoder, descriptor = {}) { + return this.#beginTimestampPass(encoder, 'beginComputePass', descriptor); + } + + #resolveTiming(encoder) { + if (!this.#canTimestamp) { + return; + } + assert(this.#state === 'need resolve', 'must call addTimestampToPass'); + this.#state = 'wait for result'; + + this.#resultBuffer = this.#resultBuffers.pop() || this.#device.createBuffer({ + size: this.#resolveBuffer.size, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, + }); + + encoder.resolveQuerySet(this.#querySet, 0, this.#querySet.count, this.#resolveBuffer, 0); + encoder.copyBufferToBuffer(this.#resolveBuffer, 0, this.#resultBuffer, 0, this.#resultBuffer.size); + } + + async getResult() { + if (!this.#canTimestamp) { + return 0; + } + assert(this.#state === 'wait for result', 'must call resolveTiming'); + this.#state = 'free'; + + const resultBuffer = this.#resultBuffer; + await resultBuffer.mapAsync(GPUMapMode.READ); + const times = new BigInt64Array(resultBuffer.getMappedRange()); + const duration = Number(times[1] - times[0]); + resultBuffer.unmap(); + this.#resultBuffers.push(resultBuffer); + return duration; + } +} diff --git a/webgpu/webgpu-optimization-none.html b/webgpu/webgpu-optimization-none.html index 78340317..15982cee 100644 --- a/webgpu/webgpu-optimization-none.html +++ b/webgpu/webgpu-optimization-none.html @@ -45,6 +45,12 @@