Timing Your Pipelines
To measure kernel execution time, you can use timestamp queries, which instruct compute or render passes to write timestamps at the start and end of execution. By reading back these timestamps, you can calculate the pipeline’s execution duration in nanoseconds.
TypeGPU offers two ways to employ timestamp queries:
- High-level API via the pipeline’s
.withPerformanceCallback()
method - Low-level API using a
TgpuQuerySet
, which you can attach to a TypeGPU pipeline or a raw WebGPU command encoder
Performance callbacks
Section titled “Performance callbacks”Rather than managing query sets yourself, you can measure shader dispatch times easily by chaining .withPerformanceCallback()
onto a compute or render pipeline:
const pipeline = root['~unstable'] .withCompute(computeShader) .createPipeline() .withPerformanceCallback((start, end) => { const durationNs = Number(end - start); console.log(`Pipeline execution time: ${durationNs} ns`); });
-
Callback signature
(start: bigint, end: bigint) => void | Promise<void>Both
start
andend
are timestamps in nanoseconds. -
Multiple callbacks If you call
.withPerformanceCallback()
more than once, only the last callback is used. If your timing logic doesn’t change, attach it a single time after creating the pipeline rather than on every dispatch. -
Automatic query set If you haven’t provided a
TgpuQuerySet
before calling.withPerformanceCallback()
, TypeGPU will allocate one for you along with the necessary resolve buffers.
Using TgpuQuerySet
Section titled “Using TgpuQuerySet”For finer control, create and manage your own TgpuQuerySet
. You can attach it either to a TypeGPU pipeline or directly to a raw WebGPU encoder.
Creating and attaching to a pipeline
Section titled “Creating and attaching to a pipeline”// Create a query set with two timestamp slotsconst querySet = root.createQuerySet('timestamp', 2);
const pipeline = root['~unstable'] .withCompute(computeShader) .createPipeline() .withTimestampWrites({ querySet: querySet, beginningOfPassWriteIndex: 0, // Write start time at index 0 endOfPassWriteIndex: 1, // Write end time at index 1 });
Omit beginningOfPassWriteIndex
or endOfPassWriteIndex
to skip writing at the start or end of the pass, respectively.
Attaching to a raw WebGPU command encoder
Section titled “Attaching to a raw WebGPU command encoder”You can also attach timestamp queries directly to a raw WebGPU command encoder by specifying timestampWrites
in the pass descriptor:
const querySet = root.createQuerySet('timestamp', 2);const encoder = device.createCommandEncoder();
const timestampWrites = { querySet: root.unwrap(querySet), beginningOfPassWriteIndex: 0, // Write start time at index 0 endOfPassWriteIndex: 1, // Write end time at index 1};
const pass = encoder.beginComputePass({ timestampWrites });// …dispatch calls…pass.end();
device.queue.submit([encoder.finish()]);await device.queue.onSubmittedWorkDone();
Reading Timestamps
Section titled “Reading Timestamps”To read timestamp results, you must first resolve the query set, then read the data:
querySet.resolve();const timestamps: bigint[] = await querySet.read();// timestamps[0] is start, timestamps[1] is end
Continuous timestamp queries
Section titled “Continuous timestamp queries”Reading timestamp queries involves several steps on both the GPU and CPU. While the query set can be written to and resolved every frame, mapping the read buffer on the CPU often takes longer than the GPU pipeline execution. If you attempt to resolve or read the query results while a previous read is still in progress, you risk conflicting operations.
To prevent such issues, TgpuQuerySet enforces a safety check: an error will be thrown if you call resolve() or read() while the query set is busy. Use the TgpuQuerySet.available property to check if the data is ready before accessing it.
When profiling inside a render loop, structure your logic like this:
async function renderLoop() { // …your rendering logic…
if (querySet.available) { querySet.resolve(); const timestamps: bigint[] = await querySet.read(); console.log(`Start: ${timestamps[0]}, End: ${timestamps[1]}`); } else { console.warn('Query set not available yet'); }
requestAnimationFrame(renderLoop);}
The querySet.available
property returns true
once the last resolve has completed.
Combining TgpuQuerySet
with performance callbacks
Section titled “Combining TgpuQuerySet with performance callbacks”You can use a custom query set and still attach a performance callback. TypeGPU will write to the indices you specify, then resolve and read the full set for the callback:
const querySet = root.createQuerySet('timestamp', 36);
const pipeline = root['~unstable'] .withCompute(computeShader) .createPipeline() .withTimestampWrites({ querySet: querySet, beginningOfPassWriteIndex: 3, // start at slot 3 endOfPassWriteIndex: 21, // end at slot 21 }) .withPerformanceCallback((start, end) => { console.log(`Pipeline execution time: ${Number(end - start)} ns`); });
The callback respects your chosen indices but still resolves and reads the entire query set under the hood.