Skip to content

Commit 9edd78b

Browse files
committed
Add more timestamp tests
* Test that resolving a timestamp twice produces the same result. * Test that slots that are never used resolve to zero.
1 parent 09fdb84 commit 9edd78b

1 file changed

Lines changed: 169 additions & 53 deletions

File tree

src/webgpu/api/operation/command_buffer/queries/timestampQuery.spec.ts

Lines changed: 169 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,40 @@ import { AllFeaturesMaxLimitsGPUTest } from '../../../../gpu_test.js';
1515

1616
export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);
1717

18+
function encodeTimestampQueries(
19+
encoder: GPUCommandEncoder,
20+
view: GPUTextureView,
21+
querySet: GPUQuerySet,
22+
stage: 'compute' | 'render',
23+
slot: number
24+
) {
25+
switch (stage) {
26+
case 'compute': {
27+
const pass = encoder.beginComputePass({
28+
timestampWrites: {
29+
querySet,
30+
beginningOfPassWriteIndex: slot,
31+
endOfPassWriteIndex: slot + 1,
32+
},
33+
});
34+
pass.end();
35+
break;
36+
}
37+
case 'render': {
38+
const pass = encoder.beginRenderPass({
39+
colorAttachments: [{ view, loadOp: 'load', storeOp: 'store' }],
40+
timestampWrites: {
41+
querySet,
42+
beginningOfPassWriteIndex: slot,
43+
endOfPassWriteIndex: slot + 1,
44+
},
45+
});
46+
pass.end();
47+
break;
48+
}
49+
}
50+
}
51+
1852
g.test('many_query_sets')
1953
.desc(
2054
`
@@ -59,31 +93,7 @@ and prevent pages from running.
5993
count: 2,
6094
});
6195

62-
switch (stage) {
63-
case 'compute': {
64-
const pass = encoder.beginComputePass({
65-
timestampWrites: {
66-
querySet,
67-
beginningOfPassWriteIndex: 0,
68-
endOfPassWriteIndex: 1,
69-
},
70-
});
71-
pass.end();
72-
break;
73-
}
74-
case 'render': {
75-
const pass = encoder.beginRenderPass({
76-
colorAttachments: [{ view, loadOp: 'load', storeOp: 'store' }],
77-
timestampWrites: {
78-
querySet,
79-
beginningOfPassWriteIndex: 0,
80-
endOfPassWriteIndex: 1,
81-
},
82-
});
83-
pass.end();
84-
break;
85-
}
86-
}
96+
encodeTimestampQueries(encoder, view, querySet, stage, 0);
8797
}
8898

8999
const shouldError = false; // just expect no error
@@ -127,37 +137,143 @@ So, test we can use 4k slots across a few QuerySets
127137
count: kNumSlots,
128138
});
129139

130-
switch (stage) {
131-
case 'compute': {
132-
for (let slot = 0; slot < kNumSlots; slot += 2) {
133-
const pass = encoder.beginComputePass({
134-
timestampWrites: {
135-
querySet,
136-
beginningOfPassWriteIndex: slot,
137-
endOfPassWriteIndex: slot + 1,
138-
},
139-
});
140-
pass.end();
141-
}
142-
break;
143-
}
144-
case 'render': {
145-
for (let slot = 0; slot < kNumSlots; slot += 2) {
146-
const pass = encoder.beginRenderPass({
147-
colorAttachments: [{ view, loadOp: 'load', storeOp: 'store' }],
148-
timestampWrites: {
149-
querySet,
150-
beginningOfPassWriteIndex: slot,
151-
endOfPassWriteIndex: slot + 1,
152-
},
153-
});
154-
pass.end();
155-
}
156-
break;
157-
}
140+
for (let slot = 0; slot < kNumSlots; slot += 2) {
141+
encodeTimestampQueries(encoder, view, querySet, stage, slot);
158142
}
159143
}
160144

161145
const shouldError = false; // just expect no error
162146
t.expectValidationError(() => t.device.queue.submit([encoder.finish()]), shouldError);
163147
});
148+
149+
g.test('multi_resolve')
150+
.desc(`Test resolving more than once does not change the results`)
151+
.params(u => u.combine('stage', ['compute', 'render'] as const))
152+
.fn(async t => {
153+
const { stage } = t.params;
154+
const kNumQueries = 64;
155+
156+
t.skipIfDeviceDoesNotHaveFeature('timestamp-query');
157+
158+
const querySet = t.createQuerySetTracked({
159+
type: 'timestamp',
160+
count: kNumQueries,
161+
});
162+
163+
const view = t
164+
.createTextureTracked({
165+
size: [1, 1, 1],
166+
format: 'rgba8unorm',
167+
usage: GPUTextureUsage.RENDER_ATTACHMENT,
168+
})
169+
.createView();
170+
const encoder = t.device.createCommandEncoder();
171+
172+
for (let slot = 0; slot < kNumQueries; slot += 2) {
173+
// skip every other pair so we can test resolving un-used slots
174+
const pair = (slot / 2) | 0;
175+
if (pair % 2) {
176+
continue;
177+
}
178+
encodeTimestampQueries(encoder, view, querySet, stage, slot);
179+
}
180+
181+
const size = kNumQueries * 8;
182+
const resolveBuffer1 = t.createBufferTracked({
183+
size,
184+
usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC,
185+
});
186+
const resolveBuffer2 = t.createBufferTracked({
187+
size,
188+
usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC,
189+
});
190+
const resultBuffer1 = t.createBufferTracked({
191+
size,
192+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
193+
});
194+
195+
encoder.resolveQuerySet(querySet, 0, kNumQueries, resolveBuffer1, 0);
196+
encoder.resolveQuerySet(querySet, 0, kNumQueries, resolveBuffer2, 0);
197+
encoder.copyBufferToBuffer(resolveBuffer1, 0, resultBuffer1, 0, size);
198+
199+
t.device.queue.submit([encoder.finish()]);
200+
201+
// Read back the first result.
202+
await resultBuffer1.mapAsync(GPUMapMode.READ);
203+
const expected = new Uint32Array(resultBuffer1.getMappedRange());
204+
t.expectGPUBufferValuesEqual(resolveBuffer2, expected);
205+
});
206+
207+
g.test('unused_slots_are_zero')
208+
.desc(`Test that unused slots are resolved to zero`)
209+
.params(u => u.combine('stage', ['compute', 'render'] as const))
210+
.fn(t => {
211+
const { stage } = t.params;
212+
const kNumQueries = 64;
213+
214+
t.skipIfDeviceDoesNotHaveFeature('timestamp-query');
215+
216+
const querySet = t.createQuerySetTracked({
217+
type: 'timestamp',
218+
count: kNumQueries,
219+
});
220+
221+
const view = t
222+
.createTextureTracked({
223+
size: [1, 1, 1],
224+
format: 'rgba8unorm',
225+
usage: GPUTextureUsage.RENDER_ATTACHMENT,
226+
})
227+
.createView();
228+
const usedEncoder = t.device.createCommandEncoder();
229+
const unusedEncoder = t.device.createCommandEncoder();
230+
231+
for (let slot = 0; slot < kNumQueries; slot += 2) {
232+
const pair = (slot / 2) | 0;
233+
const encoder = pair % 2 ? usedEncoder : unusedEncoder;
234+
encodeTimestampQueries(encoder, view, querySet, stage, slot);
235+
}
236+
237+
unusedEncoder.finish(); // don't submit this encoder
238+
239+
const size = kNumQueries * 8;
240+
const resolveBuffer = t.createBufferTracked({
241+
size,
242+
usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC,
243+
});
244+
245+
usedEncoder.resolveQuerySet(querySet, 0, kNumQueries, resolveBuffer, 0);
246+
247+
t.device.queue.submit([usedEncoder.finish()]);
248+
249+
// Read back the first result.
250+
t.expectGPUBufferValuesPassCheck(
251+
resolveBuffer,
252+
(actualU32: Uint32Array) => {
253+
// MAINTENANCE_TODO: expectGPUBufferValuesPassCheck doesn't work with BigUint64Array.
254+
const actual = new BigUint64Array(
255+
actualU32.buffer,
256+
actualU32.byteOffset,
257+
actualU32.byteLength / 8
258+
);
259+
const errors: string[] = [];
260+
for (let slot = 0; slot < kNumQueries; ++slot) {
261+
t.debug(() => `slot ${slot}: ${actual[slot]}`);
262+
const pair = (slot / 2) | 0;
263+
if (pair % 2) {
264+
// used slot, implementation defined so don't check
265+
} else {
266+
// unused slot, expect zero
267+
if (actual[slot] !== 0n) {
268+
errors.push(`slot ${slot} expected 0 but got ${actual[slot]}`);
269+
}
270+
}
271+
}
272+
return errors.length === 0 ? undefined : new Error(errors.join('\n'));
273+
},
274+
{
275+
type: Uint32Array,
276+
typedLength: kNumQueries,
277+
}
278+
);
279+
});

0 commit comments

Comments
 (0)