@@ -17,6 +17,40 @@ import { AllFeaturesMaxLimitsGPUTest, GPUTest } from '../../../../gpu_test.js';
1717
1818export const g = makeTestGroup ( AllFeaturesMaxLimitsGPUTest ) ;
1919
20+ function encodeTimestampQueries (
21+ encoder : GPUCommandEncoder ,
22+ view : GPUTextureView ,
23+ querySet : GPUQuerySet ,
24+ stage : 'compute' | 'render' ,
25+ slot : number
26+ ) {
27+ switch ( stage ) {
28+ case 'compute' : {
29+ const pass = encoder . beginComputePass ( {
30+ timestampWrites : {
31+ querySet,
32+ beginningOfPassWriteIndex : slot ,
33+ endOfPassWriteIndex : slot + 1 ,
34+ } ,
35+ } ) ;
36+ pass . end ( ) ;
37+ break ;
38+ }
39+ case 'render' : {
40+ const pass = encoder . beginRenderPass ( {
41+ colorAttachments : [ { view, loadOp : 'load' , storeOp : 'store' } ] ,
42+ timestampWrites : {
43+ querySet,
44+ beginningOfPassWriteIndex : slot ,
45+ endOfPassWriteIndex : slot + 1 ,
46+ } ,
47+ } ) ;
48+ pass . end ( ) ;
49+ break ;
50+ }
51+ }
52+ }
53+
2054g . test ( 'many_query_sets' )
2155 . desc (
2256 `
@@ -61,31 +95,7 @@ and prevent pages from running.
6195 count : 2 ,
6296 } ) ;
6397
64- switch ( stage ) {
65- case 'compute' : {
66- const pass = encoder . beginComputePass ( {
67- timestampWrites : {
68- querySet,
69- beginningOfPassWriteIndex : 0 ,
70- endOfPassWriteIndex : 1 ,
71- } ,
72- } ) ;
73- pass . end ( ) ;
74- break ;
75- }
76- case 'render' : {
77- const pass = encoder . beginRenderPass ( {
78- colorAttachments : [ { view, loadOp : 'load' , storeOp : 'store' } ] ,
79- timestampWrites : {
80- querySet,
81- beginningOfPassWriteIndex : 0 ,
82- endOfPassWriteIndex : 1 ,
83- } ,
84- } ) ;
85- pass . end ( ) ;
86- break ;
87- }
88- }
98+ encodeTimestampQueries ( encoder , view , querySet , stage , 0 ) ;
8999 }
90100
91101 const shouldError = false ; // just expect no error
@@ -119,34 +129,8 @@ function encoderQueryUsage(
119129 count : numSlots ,
120130 } ) ;
121131
122- switch ( stage ) {
123- case 'compute' : {
124- for ( let slot = 0 ; slot < numSlots ; slot += 2 ) {
125- const pass = encoder . beginComputePass ( {
126- timestampWrites : {
127- querySet,
128- beginningOfPassWriteIndex : slot ,
129- endOfPassWriteIndex : slot + 1 ,
130- } ,
131- } ) ;
132- pass . end ( ) ;
133- }
134- break ;
135- }
136- case 'render' : {
137- for ( let slot = 0 ; slot < numSlots ; slot += 2 ) {
138- const pass = encoder . beginRenderPass ( {
139- colorAttachments : [ { view, loadOp : 'load' , storeOp : 'store' } ] ,
140- timestampWrites : {
141- querySet,
142- beginningOfPassWriteIndex : slot ,
143- endOfPassWriteIndex : slot + 1 ,
144- } ,
145- } ) ;
146- pass . end ( ) ;
147- }
148- break ;
149- }
132+ for ( let slot = 0 ; slot < numSlots ; slot += 2 ) {
133+ encodeTimestampQueries ( encoder , view , querySet , stage , slot ) ;
150134 }
151135
152136 return querySet ;
@@ -217,3 +201,135 @@ to make sure the implementation doesn't mistakenly mark them as used.
217201 t . expectGPUBufferValuesEqual ( buffer , expected ) ;
218202 }
219203 } ) ;
204+
205+ g . test ( 'multi_resolve' )
206+ . desc ( `Test resolving more than once does not change the results` )
207+ . params ( u => u . combine ( 'stage' , [ 'compute' , 'render' ] as const ) )
208+ . fn ( async t => {
209+ const { stage } = t . params ;
210+ const kNumQueries = 64 ;
211+
212+ t . skipIfDeviceDoesNotHaveFeature ( 'timestamp-query' ) ;
213+
214+ const querySet = t . createQuerySetTracked ( {
215+ type : 'timestamp' ,
216+ count : kNumQueries ,
217+ } ) ;
218+
219+ const view = t
220+ . createTextureTracked ( {
221+ size : [ 1 , 1 , 1 ] ,
222+ format : 'rgba8unorm' ,
223+ usage : GPUTextureUsage . RENDER_ATTACHMENT ,
224+ } )
225+ . createView ( ) ;
226+ const encoder = t . device . createCommandEncoder ( ) ;
227+
228+ for ( let slot = 0 ; slot < kNumQueries ; slot += 2 ) {
229+ // skip every other pair so we can test resolving un-used slots
230+ const pair = ( slot / 2 ) | 0 ;
231+ if ( pair % 2 ) {
232+ continue ;
233+ }
234+ encodeTimestampQueries ( encoder , view , querySet , stage , slot ) ;
235+ }
236+
237+ const size = kNumQueries * 8 ;
238+ const resolveBuffer1 = t . createBufferTracked ( {
239+ size,
240+ usage : GPUBufferUsage . QUERY_RESOLVE | GPUBufferUsage . COPY_SRC ,
241+ } ) ;
242+ const resolveBuffer2 = t . createBufferTracked ( {
243+ size,
244+ usage : GPUBufferUsage . QUERY_RESOLVE | GPUBufferUsage . COPY_SRC ,
245+ } ) ;
246+ const resultBuffer1 = t . createBufferTracked ( {
247+ size,
248+ usage : GPUBufferUsage . COPY_DST | GPUBufferUsage . MAP_READ ,
249+ } ) ;
250+
251+ encoder . resolveQuerySet ( querySet , 0 , kNumQueries , resolveBuffer1 , 0 ) ;
252+ encoder . resolveQuerySet ( querySet , 0 , kNumQueries , resolveBuffer2 , 0 ) ;
253+ encoder . copyBufferToBuffer ( resolveBuffer1 , 0 , resultBuffer1 , 0 , size ) ;
254+
255+ t . device . queue . submit ( [ encoder . finish ( ) ] ) ;
256+
257+ // Read back the first result.
258+ await resultBuffer1 . mapAsync ( GPUMapMode . READ ) ;
259+ const expected = new Uint32Array ( resultBuffer1 . getMappedRange ( ) ) ;
260+ t . expectGPUBufferValuesEqual ( resolveBuffer2 , expected ) ;
261+ } ) ;
262+
263+ g . test ( 'unused_slots_are_zero' )
264+ . desc ( `Test that unused slots are resolved to zero` )
265+ . params ( u => u . combine ( 'stage' , [ 'compute' , 'render' ] as const ) )
266+ . fn ( t => {
267+ const { stage } = t . params ;
268+ const kNumQueries = 64 ;
269+
270+ t . skipIfDeviceDoesNotHaveFeature ( 'timestamp-query' ) ;
271+
272+ const querySet = t . createQuerySetTracked ( {
273+ type : 'timestamp' ,
274+ count : kNumQueries ,
275+ } ) ;
276+
277+ const view = t
278+ . createTextureTracked ( {
279+ size : [ 1 , 1 , 1 ] ,
280+ format : 'rgba8unorm' ,
281+ usage : GPUTextureUsage . RENDER_ATTACHMENT ,
282+ } )
283+ . createView ( ) ;
284+ const usedEncoder = t . device . createCommandEncoder ( ) ;
285+ const unusedEncoder = t . device . createCommandEncoder ( ) ;
286+
287+ for ( let slot = 0 ; slot < kNumQueries ; slot += 2 ) {
288+ const pair = ( slot / 2 ) | 0 ;
289+ const encoder = pair % 2 ? usedEncoder : unusedEncoder ;
290+ encodeTimestampQueries ( encoder , view , querySet , stage , slot ) ;
291+ }
292+
293+ unusedEncoder . finish ( ) ; // don't submit this encoder
294+
295+ const size = kNumQueries * 8 ;
296+ const resolveBuffer = t . createBufferTracked ( {
297+ size,
298+ usage : GPUBufferUsage . QUERY_RESOLVE | GPUBufferUsage . COPY_SRC ,
299+ } ) ;
300+
301+ usedEncoder . resolveQuerySet ( querySet , 0 , kNumQueries , resolveBuffer , 0 ) ;
302+
303+ t . device . queue . submit ( [ usedEncoder . finish ( ) ] ) ;
304+
305+ // Read back the first result.
306+ t . expectGPUBufferValuesPassCheck (
307+ resolveBuffer ,
308+ ( actualU32 : Uint32Array ) => {
309+ // MAINTENANCE_TODO: expectGPUBufferValuesPassCheck doesn't work with BigUint64Array.
310+ const actual = new BigUint64Array (
311+ actualU32 . buffer ,
312+ actualU32 . byteOffset ,
313+ actualU32 . byteLength / 8
314+ ) ;
315+ const errors : string [ ] = [ ] ;
316+ for ( let slot = 0 ; slot < kNumQueries ; ++ slot ) {
317+ t . debug ( ( ) => `slot ${ slot } : ${ actual [ slot ] } ` ) ;
318+ const pair = ( slot / 2 ) | 0 ;
319+ if ( pair % 2 ) {
320+ // used slot, implementation defined so don't check
321+ } else {
322+ // unused slot, expect zero
323+ if ( actual [ slot ] !== 0n ) {
324+ errors . push ( `slot ${ slot } expected 0 but got ${ actual [ slot ] } ` ) ;
325+ }
326+ }
327+ }
328+ return errors . length === 0 ? undefined : new Error ( errors . join ( '\n' ) ) ;
329+ } ,
330+ {
331+ type : Uint32Array ,
332+ typedLength : kNumQueries * 2 , // because it's really BigUint64Array data
333+ }
334+ ) ;
335+ } ) ;
0 commit comments