Skip to content

Commit 8ff367f

Browse files
authored
impr: Better withPerformanceCallback API (#2309)
1 parent a658909 commit 8ff367f

5 files changed

Lines changed: 78 additions & 44 deletions

File tree

packages/typegpu/src/core/pipeline/computePipeline.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,21 @@ class TgpuComputePipelineImpl implements TgpuComputePipeline {
220220
}
221221

222222
withPerformanceCallback(callback: (start: bigint, end: bigint) => void | Promise<void>): this {
223-
const newPriors = createWithPerformanceCallback(this.#priors, callback, this.#core.root);
223+
if (this.#priors.timestampWrites) {
224+
return new TgpuComputePipelineImpl(this.#core, {
225+
...this.#priors,
226+
performanceCallback: callback,
227+
}) as this;
228+
}
229+
230+
const querySet = this.#core.performanceCallbackQuerySet;
231+
if (!querySet) {
232+
console.warn(
233+
'Performance callback cannot be used because the timestamp-query feature is not enabled on the root.',
234+
);
235+
return this;
236+
}
237+
const newPriors = createWithPerformanceCallback(this.#priors, callback, querySet);
224238
return new TgpuComputePipelineImpl(this.#core, newPriors) as this;
225239
}
226240

@@ -353,6 +367,7 @@ class ComputePipelineCore implements SelfResolvable {
353367

354368
#slotBindings: [TgpuSlot<unknown>, unknown][];
355369
#descriptor: TgpuComputePipeline.Descriptor;
370+
#performanceCallbackQuerySet: TgpuQuerySet<'timestamp'> | undefined;
356371

357372
constructor(
358373
root: ExperimentalTgpuRoot,
@@ -375,6 +390,13 @@ class ComputePipelineCore implements SelfResolvable {
375390
return 'computePipelineCore';
376391
}
377392

393+
get performanceCallbackQuerySet() {
394+
if (!this.root.enabledFeatures.has('timestamp-query')) {
395+
return undefined;
396+
}
397+
return (this.#performanceCallbackQuerySet ??= this.root.createQuerySet('timestamp', 2));
398+
}
399+
378400
public unwrap(): Memo {
379401
if (this._memo === undefined) {
380402
const device = this.root.device;

packages/typegpu/src/core/pipeline/renderPipeline.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -550,11 +550,22 @@ class TgpuRenderPipelineImpl implements TgpuRenderPipeline {
550550

551551
withPerformanceCallback(callback: (start: bigint, end: bigint) => void | Promise<void>): this {
552552
const internals = this[$internal];
553-
const newPriors = createWithPerformanceCallback(
554-
internals.priors,
555-
callback,
556-
internals.core.options.root,
557-
);
553+
554+
if (internals.priors.timestampWrites) {
555+
return new TgpuRenderPipelineImpl(internals.core, {
556+
...internals.priors,
557+
performanceCallback: callback,
558+
}) as this;
559+
}
560+
561+
const querySet = internals.core.performanceCallbackQuerySet;
562+
if (!querySet) {
563+
console.warn(
564+
'Performance callback cannot be used because the timestamp-query feature is not enabled on the root.',
565+
);
566+
return this;
567+
}
568+
const newPriors = createWithPerformanceCallback(internals.priors, callback, querySet);
558569
return new TgpuRenderPipelineImpl(internals.core, newPriors) as this;
559570
}
560571

@@ -955,6 +966,7 @@ class RenderPipelineCore implements SelfResolvable {
955966

956967
#latestAutoVertexIn: TgpuVertexFn.In | undefined;
957968
#latestAutoFragmentOut: BaseData | undefined;
969+
#performanceCallbackQuerySet: TgpuQuerySet<'timestamp'> | undefined;
958970

959971
constructor(options: RenderPipelineCoreOptions) {
960972
this.options = options;
@@ -1011,6 +1023,13 @@ class RenderPipelineCore implements SelfResolvable {
10111023
return 'renderPipelineCore';
10121024
}
10131025

1026+
get performanceCallbackQuerySet() {
1027+
if (!this.options.root.enabledFeatures.has('timestamp-query')) {
1028+
return undefined;
1029+
}
1030+
return (this.#performanceCallbackQuerySet ??= this.options.root.createQuerySet('timestamp', 2));
1031+
}
1032+
10141033
public unwrap(): Memo {
10151034
if (this._memo !== undefined) {
10161035
return this._memo;

packages/typegpu/src/core/pipeline/timeable.ts

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,27 +19,19 @@ export type TimestampWritesPriors = {
1919
endOfPassWriteIndex?: number;
2020
};
2121
readonly performanceCallback?: (start: bigint, end: bigint) => void | Promise<void>;
22-
readonly hasAutoQuerySet?: boolean;
2322
};
2423

2524
export function createWithPerformanceCallback<T extends TimestampWritesPriors>(
2625
currentPriors: T,
2726
callback: (start: bigint, end: bigint) => void | Promise<void>,
28-
root: ExperimentalTgpuRoot,
27+
querySet: TgpuQuerySet<'timestamp'>,
2928
): T {
30-
if (!root.enabledFeatures.has('timestamp-query')) {
31-
throw new Error(
32-
'Performance callback requires the "timestamp-query" feature to be enabled on GPU device.',
33-
);
34-
}
35-
3629
if (!currentPriors.timestampWrites) {
3730
return {
3831
...currentPriors,
3932
performanceCallback: callback,
40-
hasAutoQuerySet: true,
4133
timestampWrites: {
42-
querySet: root.createQuerySet('timestamp', 2),
34+
querySet,
4335
beginningOfPassWriteIndex: 0,
4436
endOfPassWriteIndex: 1,
4537
},
@@ -67,10 +59,6 @@ export function createWithTimestampWrites<T extends TimestampWritesPriors>(
6759
);
6860
}
6961

70-
if (currentPriors.hasAutoQuerySet && currentPriors.timestampWrites) {
71-
currentPriors.timestampWrites.querySet.destroy();
72-
}
73-
7462
const timestampWrites: TimestampWritesPriors['timestampWrites'] = {
7563
querySet: options.querySet,
7664
};
@@ -84,7 +72,6 @@ export function createWithTimestampWrites<T extends TimestampWritesPriors>(
8472

8573
return {
8674
...currentPriors,
87-
hasAutoQuerySet: false,
8875
timestampWrites,
8976
};
9077
}

packages/typegpu/tests/computePipeline.test.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -141,23 +141,24 @@ describe('TgpuComputePipeline', () => {
141141
expect(pipeline[$internal].priors.performanceCallback).not.toBe(callback1);
142142
});
143143

144-
it('should throw error if timestamp-query feature is not enabled', ({ root, device }) => {
145-
const originalFeatures = device.features;
146-
//@ts-expect-error
144+
it('should warn if timestamp-query feature is not enabled', ({ root, device }) => {
145+
// @ts-expect-error
147146
device.features = new Set();
147+
using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
148148

149149
const entryFn = tgpu.computeFn({ workgroupSize: [1] })(() => {});
150150

151151
const callback = vi.fn();
152152

153153
expect(() => {
154-
root.createComputePipeline({ compute: entryFn }).withPerformanceCallback(callback);
155-
}).toThrow(
156-
'Performance callback requires the "timestamp-query" feature to be enabled on GPU device.',
154+
const before = root.createComputePipeline({ compute: entryFn });
155+
const after = before.withPerformanceCallback(callback);
156+
// no-op
157+
expect(after).toBe(before);
158+
}).not.toThrow();
159+
expect(consoleWarnSpy).toHaveBeenCalledWith(
160+
'Performance callback cannot be used because the timestamp-query feature is not enabled on the root.',
157161
);
158-
159-
//@ts-expect-error
160-
device.features = originalFeatures;
161162
});
162163
});
163164

@@ -331,6 +332,7 @@ describe('TgpuComputePipeline', () => {
331332
describe('Combined Performance callback and Timestamp Writes', () => {
332333
it('should work with both performance callback and custom timestamp writes', ({
333334
root,
335+
device,
334336
commandEncoder,
335337
}) => {
336338
const entryFn = tgpu.computeFn({ workgroupSize: [1] })(() => {});
@@ -371,6 +373,12 @@ describe('TgpuComputePipeline', () => {
371373
querySet[$internal].resolveBuffer,
372374
0,
373375
);
376+
377+
expect(device.mock.createQuerySet).toHaveBeenCalledTimes(1);
378+
expect(device.mock.createQuerySet).toHaveBeenCalledWith({
379+
type: 'timestamp',
380+
count: 10,
381+
});
374382
});
375383

376384
it('should prioritize custom timestamp writes over automatic ones', ({
@@ -394,8 +402,6 @@ describe('TgpuComputePipeline', () => {
394402
endOfPassWriteIndex: 5,
395403
});
396404

397-
expect((autoQuerySet as TgpuQuerySet<'timestamp'>).destroyed).toBe(true);
398-
399405
const priors = pipeline[$internal].priors;
400406
expect(priors.performanceCallback).toBe(callback);
401407
expect(priors.timestampWrites?.querySet).toBe(querySet);

packages/typegpu/tests/renderPipeline.test.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -501,10 +501,10 @@ describe('root.withVertex(...).withFragment(...)', () => {
501501
expect(pipeline[$internal].priors.performanceCallback).not.toBe(callback1);
502502
});
503503

504-
it('should throw error if timestamp-query feature is not enabled', ({ root, device }) => {
505-
const originalFeatures = device.features;
504+
it('should warn if timestamp-query feature is not enabled', ({ root, device }) => {
506505
//@ts-expect-error
507506
device.features = new Set();
507+
using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
508508

509509
const vertexFn = tgpu.vertexFn({
510510
out: { pos: d.builtin.position },
@@ -517,17 +517,17 @@ describe('root.withVertex(...).withFragment(...)', () => {
517517
const callback = vi.fn();
518518

519519
expect(() => {
520-
root
521-
.withVertex(vertexFn, {})
522-
.withFragment(fragmentFn, { color: { format: 'rgba8unorm' } })
523-
.createPipeline()
524-
.withPerformanceCallback(callback);
525-
}).toThrow(
526-
'Performance callback requires the "timestamp-query" feature to be enabled on GPU device.',
520+
const before = root.createRenderPipeline({
521+
vertex: vertexFn,
522+
fragment: fragmentFn,
523+
});
524+
const after = before.withPerformanceCallback(callback);
525+
// no-op
526+
expect(after).toBe(before);
527+
}).not.toThrow();
528+
expect(consoleWarnSpy).toHaveBeenCalledWith(
529+
'Performance callback cannot be used because the timestamp-query feature is not enabled on the root.',
527530
);
528-
529-
//@ts-expect-error
530-
device.features = originalFeatures;
531531
});
532532

533533
it("should not throw 'A color target was not provided to the shader'", ({ root, device }) => {

0 commit comments

Comments
 (0)