Skip to content

Commit

Permalink
wip2
Browse files Browse the repository at this point in the history
  • Loading branch information
greggman committed May 24, 2024
1 parent 7e641c2 commit 1da3240
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 18 deletions.
121 changes: 121 additions & 0 deletions webgpu/timing-helper.js
Original file line number Diff line number Diff line change
@@ -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;
}
}
81 changes: 63 additions & 18 deletions webgpu/webgpu-optimization-none.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@
<script type="module">
import GUI from '/3rdparty/muigui-0.x.module.js';
import {mat4, mat3, vec3} from '/3rdparty/wgpu-matrix.module.js';
import {RollingAverage, TimingHelper} from './timing-helper.js';

const fpsAverage = new RollingAverage();
const jsAverage = new RollingAverage();
const gpuAverage = new RollingAverage();
const mathAverage = new RollingAverage();

const cssColorToRGBA8 = (() => {
const canvas = new OffscreenCanvas(1, 1);
Expand Down Expand Up @@ -77,12 +83,17 @@

async function main() {
const adapter = await navigator.gpu?.requestAdapter();
const device = await adapter?.requestDevice();
const canTimestamp = adapter.features.has('timestamp-query');
const device = await adapter?.requestDevice({
requiredFeatures: [
...(canTimestamp ? ['timestamp-query'] : []),
],
});
if (!device) {
fail('need a browser that supports WebGPU');
return;
fail('could not init WebGPU');
}

const timingHelper = new TimingHelper(device);
const infoElem = document.querySelector('#info');

// Get a WebGPU context from the canvas and configure it
Expand Down Expand Up @@ -266,7 +277,7 @@
minFilter: 'nearest',
});

const maxObjects = 100;
const maxObjects = 10000;
const objectInfos = [];

for (let i = 0; i < maxObjects; ++i) {
Expand Down Expand Up @@ -349,6 +360,7 @@
colorAttachments: [
{
// view: <- to be filled out when we render
clearValue: [0.3, 0.3, 0.3, 1],
loadOp: 'clear',
storeOp: 'store',
},
Expand All @@ -361,10 +373,11 @@
},
};

const canvasToSizeMap = new WeakMap();
const degToRad = d => d * Math.PI / 180;

const settings = {
numObjects: maxObjects,
numObjects: 1000,
render: true,
};

Expand All @@ -384,6 +397,20 @@

const startTimeMs = performance.now();

let width = 1;
let height = 1;
if (settings.render) {
const entry = canvasToSizeMap.get(canvas);
if (entry) {
width = Math.max(1, entry.contentBoxSize[0].inlineSize, device.limits.maxTextureDimension2D);
height = Math.max(1, entry.contentBoxSize[0].blockSize, device.limits.maxTextureDimension2D);
}
}
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
}

// Get the current texture from the canvas context and
// set it as the texture to render to.
const canvasTexture = context.getCurrentTexture();
Expand All @@ -406,7 +433,7 @@
renderPassDescriptor.depthStencilAttachment.view = depthTexture.createView();

const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass(renderPassDescriptor);
const pass = timingHelper.beginRenderPass(encoder, renderPassDescriptor);
pass.setPipeline(pipeline);
pass.setVertexBuffer(0, positionBuffer);
pass.setVertexBuffer(1, normalBuffer);
Expand All @@ -431,6 +458,8 @@
// Combine the view and projection matrixes
const viewProjectionMatrix = mat4.multiply(projection, viewMatrix);

let mathElapsedTimeMs = 0;

for (let i = 0; i < settings.numObjects; ++i) {
const {
bindGroup,
Expand All @@ -452,14 +481,23 @@
scale,
shininess,
} = objectInfos[i];
const mathTimeStartMs = performance.now();

// Copy the viewProjectionMatrix into the uniform values for this object
viewProjectionValue.set(viewProjectionMatrix);

// Compute a world matrix
// mat4.identity(worldValue);
// mat4.axisRotate(worldValue, axis, time * speed, worldValue);
// mat4.translate(worldValue, [radius, 0, 0], worldValue);
// mat4.rotateY(worldValue, rotationSpeed * time, worldValue);
// mat4.scale(worldValue, [scale, scale, scale], worldValue);

mat4.identity(worldValue);
mat4.axisRotate(worldValue, axis, time * speed, worldValue);
mat4.translate(worldValue, [radius, 0, 0], worldValue);
mat4.rotateY(worldValue, rotationSpeed * time, worldValue);
mat4.axisRotate(worldValue, axis, i + time * speed, worldValue);
mat4.translate(worldValue, [0, 0, Math.sin(i * 3.721 + time * speed) * radius], worldValue);
mat4.translate(worldValue, [0, 0, Math.sin(i * 9.721 + time * 0.1) * radius], worldValue);
mat4.rotateX(worldValue, time * rotationSpeed + i, worldValue);
mat4.scale(worldValue, [scale, scale, scale], worldValue);

// Inverse and transpose it into the worldInverseTranspose value
Expand All @@ -470,6 +508,8 @@
viewWorldPositionValue.set(eye);
shininessValue[0] = shininess;

mathElapsedTimeMs += performance.now() - mathTimeStartMs;

// upload the uniform values to the uniform buffer
device.queue.writeBuffer(uniformBuffer, 0, uniformValues);

Expand All @@ -482,24 +522,29 @@
const commandBuffer = encoder.finish();
device.queue.submit([commandBuffer]);

timingHelper.getResult().then(gpuTime => {
gpuAverage.addSample(gpuTime / 1000);
});

const elapsedTimeMs = performance.now() - startTimeMs;
fpsAverage.addSample(1 / deltaTime);
jsAverage.addSample(elapsedTimeMs);
mathAverage.addSample(mathElapsedTimeMs);


infoElem.textContent = `\
js : ${elapsedTimeMs.toFixed(0)}ms
fps: ${(1 / deltaTime).toFixed(1)}
js : ${jsAverage.get().toFixed(1)}ms
math: ${mathAverage.get().toFixed(1)}ms
fps : ${fpsAverage.get().toFixed(0)}
gpu : ${canTimestamp ? `${(gpuAverage.get() / 1000).toFixed(1)}ms` : 'N/A'}
`;

requestAnimationFrame(render);
}
requestAnimationFrame(render);

const observer = new ResizeObserver(entries => {
for (const entry of entries) {
const canvas = entry.target;
const width = entry.contentBoxSize[0].inlineSize;
const height = entry.contentBoxSize[0].blockSize;
canvas.width = Math.max(1, Math.min(width, device.limits.maxTextureDimension2D));
canvas.height = Math.max(1, Math.min(height, device.limits.maxTextureDimension2D));
}
entries.forEach(e => canvasToSizeMap.set(e.target, e));
});
observer.observe(canvas);
}
Expand Down

0 comments on commit 1da3240

Please sign in to comment.