Skip to content

Commit

Permalink
address comments
Browse files Browse the repository at this point in the history
  • Loading branch information
greggman committed Nov 19, 2024
1 parent 20cf82d commit 469f3ab
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 70 deletions.
84 changes: 40 additions & 44 deletions sample/timestampQuery/TimestampQueryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
Expand Down
13 changes: 13 additions & 0 deletions sample/timestampQuery/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
</style>
<script defer src="main.js" type="module"></script>
<script defer type="module" src="../../js/iframe-helper.js"></script>
</head>
<body>
<canvas></canvas>
<div id="info">
<pre></pre>
</div>
</body>
</html>
42 changes: 16 additions & 26 deletions sample/timestampQuery/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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';
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down

0 comments on commit 469f3ab

Please sign in to comment.