diff --git a/webgpu/resources/js/rolling-average.js b/webgpu/resources/js/rolling-average.js new file mode 100644 index 00000000..a2f36c97 --- /dev/null +++ b/webgpu/resources/js/rolling-average.js @@ -0,0 +1,20 @@ +// See https://webgpufundamentals.org/webgpu/lessons/webgpu-timing.html +export default 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; + } +} diff --git a/webgpu/resources/js/timing-helper.js b/webgpu/resources/js/timing-helper.js index 306fb32f..aa645924 100644 --- a/webgpu/resources/js/timing-helper.js +++ b/webgpu/resources/js/timing-helper.js @@ -4,6 +4,7 @@ function assert(cond, msg = '') { } } +// See https://webgpufundamentals.org/webgpu/lessons/webgpu-timing.html export default class TimingHelper { #canTimestamp; #device; diff --git a/webgpu/timing-helper.js b/webgpu/timing-helper.js deleted file mode 100644 index dfb9c345..00000000 --- a/webgpu/timing-helper.js +++ /dev/null @@ -1,121 +0,0 @@ -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-all.html b/webgpu/webgpu-optimization-all.html index ce5b6c3f..657523d7 100644 --- a/webgpu/webgpu-optimization-all.html +++ b/webgpu/webgpu-optimization-all.html @@ -45,7 +45,10 @@