@@ -15,6 +15,40 @@ import { AllFeaturesMaxLimitsGPUTest } from '../../../../gpu_test.js';
1515
1616export 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+
1852g . 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