Symbols in this topic
@vgpu/render/perf
WebGPU verification helpers for the optimize loop. These utilities live under @vgpu/render/perf; they are not meant for runtime use.
gpuFrameTime
Measures GPU frame time statistics for a render routine. The harness handles warmup, command encoding, submission, and timing (timestamp queries when available, wall clock otherwise).
Import
import { gpuFrameTime } from "@vgpu/render/perf";Signature
export function gpuFrameTime(
device: Device,
encode: GpuFrameEncoder,
options?: GpuFrameTimeOptions,
): Promise<GpuFrameTimeResult>;Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| device | Device | ✔ | — | Source device used to create command encoders, submit work, and flush the queue. Must expose timestamp-query if you want GPU timestamps. |
| encode | GpuFrameEncoder | ✔ | — | Callback invoked per frame with a fresh GPUCommandEncoder; record passes exactly as in production. |
| options | GpuFrameTimeOptions | ✖ | {} | Tunables for warmup, sample count, and measurement mode. |
| options.frames | number | ✖ | 120 | Measured frames after warmup. Values below 1 are clamped to 1. |
| options.warmup | number | ✖ | 30 | Throws away the first N frames so shader compilation and lazy allocations settle. |
| options.batch | number | ✖ | 8 | Frames per batch when falling back to wall-clock. Ignored for timestamp queries. |
| options.forceWallClock | boolean | ✖ | false | Forces the wall-clock path even if timestamp-query is available. Useful when you intentionally want queue-submit timings. |
| options.label | string | ✖ | "vgpu-gpuFrameTime" | Assigns encoder labels and buffer names to make debugging captures easier. |
Returns: Promise<GpuFrameTimeResult> — resolves with median, mean, min, p95, sample count, and the measurement method ("timestamp-query" or "wall-clock").
Throws: Propagates any error from the encoder callback, queue submission, or timestamp buffer mapping. Timestamp-specific errors automatically fall back to wall-clock before surfacing.
Examples
import { gpuFrameTime } from "@vgpu/render/perf";
import { createMockAdapter } from "@vgpu/adapter-mock";
async function bench(): Promise<void> {
const adapter = createMockAdapter();
const device = await adapter.requestDevice();
const renderPassDescriptor: GPURenderPassDescriptor = { colorAttachments: [{ view: {} as GPUTextureView, loadOp: "clear", storeOp: "store" }] };
const pipeline = device.gpu.createRenderPipeline({
layout: "auto",
vertex: { module: device.gpu.createShaderModule({ code: "@vertex fn vs_main() -> @builtin(position) vec4f { return vec4f(); }" }), entryPoint: "vs_main" },
fragment: { module: device.gpu.createShaderModule({ code: "@fragment fn fs_main() -> @location(0) vec4f { return vec4f(1.0); }" }), entryPoint: "fs_main", targets: [{ format: "bgra8unorm" }] },
primitive: { topology: "triangle-list" },
});
const result = await gpuFrameTime(device, (encoder) => {
const pass = encoder.beginRenderPass(renderPassDescriptor);
pass.setPipeline(pipeline);
pass.draw(3);
pass.end();
}, { frames: 60, warmup: 10 });
console.log(result.method, result.medianMs);
}
bench().catch((error) => {
console.error(error);
});Notes
- Timestamp queries run only when the device exposes the
timestamp-queryfeature; otherwise wall-clock timings usedevice.queue.flush()to amortize submit latency via batching. - Warmup still submits commands; budget time for shader compilation before measuring.
- Always compare two runs with identical options;
medianMsis the headline figure for relative regressions. - See also:
pixelDiff
GpuFrameTimeOptions
Optional configuration passed to gpuFrameTime.
Fields
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| frames | number | ✖ | 120 | Number of measured frames (post-warmup). Clamped to at least 1. |
| warmup | number | ✖ | 30 | Frames to submit and discard before measuring. |
| batch | number | ✖ | 8 | Wall-clock batch size; ignored for timestamp mode. |
| forceWallClock | boolean | ✖ | false | Skip timestamp queries even when supported. |
| label | string | ✖ | "vgpu-gpuFrameTime" | Label used for encoders and buffers created internally. |
GpuFrameTimeResult
Return type for gpuFrameTime.
Fields
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| medianMs | number | ✔ | — | 50th percentile frame time, the main number to compare across builds. |
| meanMs | number | ✔ | — | Arithmetic mean of collected samples. |
| minMs | number | ✔ | — | Fastest observed frame. |
| p95Ms | number | ✔ | — | 95th percentile; highlights long tails. |
| samples | number | ✔ | — | Count of valid samples captured. |
| method | "timestamp-query" | "wall-clock" | ✔ | — | Measurement backend used. |
pixelDiff
Compares two renders byte-for-byte. Accepts either Texture instances (will call read()) or already read Uint8Arrays.
Import
import { pixelDiff } from "@vgpu/render/perf";Signature
export function pixelDiff(
a: Texture | Uint8Array,
b: Texture | Uint8Array,
): Promise<PixelDiffResult>;Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| a | Texture | Uint8Array | ✔ | — | First render; when a Texture, it is read via Texture.read(). |
| b | Texture | Uint8Array | ✔ | — | Second render to compare against a. |
Returns: Promise<PixelDiffResult> — contains maxByte, meanByte, changedBytes, totalBytes, and changedFraction.
Throws: Propagates any error while reading textures (e.g., unreadable usage flags) or when the inputs cannot be read.
Examples
import { pixelDiff } from "@vgpu/render/perf";
import type { Texture } from "@vgpu/core";
async function assertVisualParity(before: Texture, after: Texture): Promise<void> {
const result = await pixelDiff(before, after);
if (result.maxByte > 2) {
throw new Error(`Visual regression detected (max delta ${result.maxByte})`);
}
}Notes
- A length mismatch forces
maxByteto255; inspecttotalBytesbefore trusting smallmaxBytevalues. - Treat
maxByte <= 2as driver rounding noise on most devices; higher values indicate true visual changes. - When comparing multiple frames, feed already-read
Uint8Arrays to avoid repeated GPU readbacks. - See also:
gpuFrameTime
PixelDiffResult
Shape returned by pixelDiff.
Fields
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| maxByte | number | ✔ | — | Largest absolute per-byte difference (0–255). Use this as the headline regression metric. |
| meanByte | number | ✔ | — | Mean absolute per-byte difference across compared buffers. |
| changedBytes | number | ✔ | — | Count of bytes whose value differs at all. |
| totalBytes | number | ✔ | — | Number of bytes compared (min of both buffer lengths). |
| changedFraction | number | ✔ | — | changedBytes / totalBytes; near-zero fractions with low maxByte typically indicate harmless rounding noise. |