[js/webgpu] Change timestamp-query-in-passes to timestamp-query (#18108)

Timestamp-query has a broader support than timestamp-query-in-passes on
all the platforms, including macOS.
Note that to enable timestamp-query, you still need to add switch
"--enable-dawn-features=allow_unsafe_apis" to Chrome. By default, the
lowest 16 bits are masked with 0 (at a granularity about 0.1ms) for
privacy. To get the highest precision, you need to add another switch
"--enable-webgpu-developer-features".
This commit is contained in:
Yang Gu 2023-10-27 07:33:03 +08:00 committed by GitHub
parent b7bee621cd
commit 52f4968359
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 50 additions and 48 deletions

View file

@ -126,14 +126,14 @@ export class WebGpuBackend {
*/
kernels: Map<number, [string, string, RunFunction, [((attribute: unknown) => unknown) | undefined, unknown]]>;
commandEncoder: GPUCommandEncoder|null = null;
computePassEncoder: GPUComputePassEncoder|null = null;
private commandEncoder: GPUCommandEncoder|null = null;
private computePassEncoder: GPUComputePassEncoder|null = null;
pendingDispatchNumber = 0;
supportTimestampQuery = false;
profilingQuerySet: GPUQuerySet;
profilingQueryData: GpuData;
profilingTimeBase?: bigint;
queryData?: GpuData;
querySet?: GPUQuerySet;
querySetCount = 2;
queryTimeBase?: bigint;
env: Env;
@ -168,11 +168,9 @@ export class WebGpuBackend {
},
requiredFeatures,
};
// WebGPU Spec: Timestamp Queries Inside Passes
// https://github.com/gpuweb/gpuweb/blob/main/proposals/timestamp-query-inside-passes.md
if (adapter.features.has('timestamp-query-inside-passes')) {
this.supportTimestampQuery = true;
requiredFeatures.push('timestamp-query-inside-passes' as GPUFeatureName);
if (adapter.features.has('timestamp-query')) {
requiredFeatures.push('timestamp-query');
}
if (adapter.features.has('shader-f16')) {
requiredFeatures.push('shader-f16');
@ -197,21 +195,14 @@ export class WebGpuBackend {
}
};
if (this.supportTimestampQuery) {
this.profilingQuerySet = this.device.createQuerySet({
type: 'timestamp',
count: 2,
});
}
Object.defineProperty(this.env.webgpu, 'device', {value: this.device});
}
dispose(): void {
// currently, we do not do anything in this function. In all known use cases, we don't have the requirement to
// actually dispose the WebGpuBackend instance, because it's always used as a singleton.
//
// revisit this place if we get real requirement to dispose the instance.
if (typeof this.querySet !== 'undefined') {
this.querySet.destroy();
}
this.gpuDataManager.dispose();
}
getCommandEncoder(): GPUCommandEncoder {
@ -223,7 +214,22 @@ export class WebGpuBackend {
getComputePassEncoder(): GPUComputePassEncoder {
if (!this.computePassEncoder) {
this.computePassEncoder = this.getCommandEncoder().beginComputePass();
const computePassDescriptor: GPUComputePassDescriptor = {};
if (this.isQueryEnabled()) {
if (typeof this.querySet === 'undefined') {
this.querySet = this.device.createQuerySet({
type: 'timestamp',
count: this.querySetCount,
});
}
computePassDescriptor.timestampWrites = {
querySet: this.querySet,
beginningOfPassWriteIndex: 0,
endOfPassWriteIndex: 1,
};
}
this.computePassEncoder = this.getCommandEncoder().beginComputePass(computePassDescriptor);
}
return this.computePassEncoder;
}
@ -245,6 +251,14 @@ export class WebGpuBackend {
}
}
isQueryEnabled(): boolean {
if (this.device.features.has('timestamp-query') && this.env.webgpu.profilingMode === 'default') {
return true;
} else {
return false;
}
}
/**
* run a WebGPU program.
* @param program a ProgramInfo instance

View file

@ -38,14 +38,6 @@ export class ProgramManager {
const device = this.backend.device;
const computePassEncoder = this.backend.getComputePassEncoder();
const profilingEnabled = this.backend.supportTimestampQuery && this.backend.env.webgpu.profilingMode === 'default';
if (profilingEnabled) {
// profiling write start timestamp
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(computePassEncoder as any).writeTimestamp(this.backend.profilingQuerySet, 0);
}
computePassEncoder.setPipeline(buildArtifact.computePipeline);
const entries = [];
for (const input of inputs) {
@ -65,24 +57,20 @@ export class ProgramManager {
this.backend.pendingDispatchNumber++;
if (profilingEnabled) {
// profiling write end timestamp
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(computePassEncoder as any).writeTimestamp(this.backend.profilingQuerySet, 1);
if (this.backend.profilingQueryData == null) {
this.backend.profilingQueryData =
if (this.backend.isQueryEnabled()) {
if (typeof this.backend.queryData === 'undefined') {
this.backend.queryData = this.backend.gpuDataManager.create(
// eslint-disable-next-line no-bitwise
this.backend.gpuDataManager.create(16, GPUBufferUsage.COPY_SRC | GPUBufferUsage.QUERY_RESOLVE);
this.backend.querySetCount * 8, GPUBufferUsage.COPY_SRC | GPUBufferUsage.QUERY_RESOLVE);
}
// eslint-disable-next-line no-bitwise
const syncData = this.backend.gpuDataManager.create(16, GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST);
const syncData = this.backend.gpuDataManager.create(
// eslint-disable-next-line no-bitwise
this.backend.querySetCount * 8, GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST);
this.backend.endComputePass();
this.backend.getCommandEncoder().resolveQuerySet(
this.backend.profilingQuerySet, 0, 2, this.backend.profilingQueryData.buffer, 0);
this.backend.getCommandEncoder().resolveQuerySet(this.backend.querySet, 0, 2, this.backend.queryData.buffer, 0);
this.backend.getCommandEncoder().copyBufferToBuffer(
this.backend.profilingQueryData.buffer, 0, syncData.buffer, 0, 16);
this.backend.queryData.buffer, 0, syncData.buffer, 0, this.backend.querySetCount * 8);
this.backend.flush();
const kernelId = this.backend.currentKernelId!;
@ -96,12 +84,12 @@ export class ProgramManager {
syncData.buffer.unmap();
if (typeof this.backend.profilingTimeBase === 'undefined') {
this.backend.profilingTimeBase = startTimeU64;
if (typeof this.backend.queryTimeBase === 'undefined') {
this.backend.queryTimeBase = startTimeU64;
}
const startTime = Number(startTimeU64 - this.backend.profilingTimeBase);
const endTime = Number(endTimeU64 - this.backend.profilingTimeBase);
const startTime = Number(startTimeU64 - this.backend.queryTimeBase);
const endTime = Number(endTimeU64 - this.backend.queryTimeBase);
if (!Number.isSafeInteger(startTime) || !Number.isSafeInteger(endTime)) {
throw new RangeError('incorrect timestamp range');