Skip to content

Commit 3962314

Browse files
authored
Test Reusing the same query in the same command buffer (#4626)
Queries are not allowed to be used more than once in the same render pass but they are allowed to be used more than once in the same command buffer, without resolving inbetween use. Add a test this works as expected.
1 parent 0f4e899 commit 3962314

1 file changed

Lines changed: 108 additions & 20 deletions

File tree

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

Lines changed: 108 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ API operations tests for occlusion queries.
1616
- test resolving twice in same pass keeps values
1717
- test resolving twice across pass keeps values
1818
- test resolveQuerySet destinationOffset
19+
- test using the same query multiple times in different passes without resolving in between
1920
`;
2021

2122
import { kUnitCaseParamsBuilder } from '../../../../../common/framework/params_builder.js';
@@ -436,38 +437,49 @@ class OcclusionQueryTest extends AllFeaturesMaxLimitsGPUTest {
436437
renderMode,
437438
};
438439
}
439-
async runQueryTest(
440+
441+
encodeQueries(
440442
resources: ReturnType<OcclusionQueryTest['setup']>,
441-
renderPassDescriptor: GPURenderPassDescriptor | null,
442-
encodePassFn: (helper: RenderPassHelper, queryIndex: number) => void,
443-
checkQueryIndexResultFn: (passed: boolean, queryIndex: number) => void
443+
encoder: GPUCommandEncoder,
444+
renderPassDescriptor: GPURenderPassDescriptor,
445+
encodePassFn: (helper: RenderPassHelper, queryIndex: number) => void
444446
) {
445447
const { device } = this;
448+
const { occlusionQuerySet, querySetOffset, renderMode = 'direct' } = resources;
449+
const numQueries = occlusionQuerySet.count - querySetOffset;
450+
const queryIndices = range(numQueries, (i: number) => i + querySetOffset);
451+
452+
const pass = encoder.beginRenderPass(renderPassDescriptor);
453+
const helper = new RenderPassHelper(
454+
pass,
455+
renderMode === 'direct'
456+
? new QueryStarterDirect(pass)
457+
: new QueryStarterRenderBundle(device, pass, renderPassDescriptor)
458+
);
459+
460+
for (const queryIndex of queryIndices) {
461+
encodePassFn(helper, queryIndex);
462+
}
463+
pass.end();
464+
}
465+
466+
encodeAndResolveQueries(
467+
resources: ReturnType<OcclusionQueryTest['setup']>,
468+
encoder: GPUCommandEncoder,
469+
renderPassDescriptor: GPURenderPassDescriptor | null,
470+
encodePassFn: (helper: RenderPassHelper, queryIndex: number) => void
471+
) {
446472
const {
447473
readBuffer,
448474
queryResolveBuffer,
449475
queryResolveBufferOffset,
450476
occlusionQuerySet,
451477
querySetOffset,
452-
renderMode = 'direct',
453478
} = resources;
454479
const numQueries = occlusionQuerySet.count - querySetOffset;
455-
const queryIndices = range(numQueries, (i: number) => i + querySetOffset);
456480

457-
const encoder = device.createCommandEncoder();
458481
if (renderPassDescriptor) {
459-
const pass = encoder.beginRenderPass(renderPassDescriptor);
460-
const helper = new RenderPassHelper(
461-
pass,
462-
renderMode === 'direct'
463-
? new QueryStarterDirect(pass)
464-
: new QueryStarterRenderBundle(device, pass, renderPassDescriptor)
465-
);
466-
467-
for (const queryIndex of queryIndices) {
468-
encodePassFn(helper, queryIndex);
469-
}
470-
pass.end();
482+
this.encodeQueries(resources, encoder, renderPassDescriptor, encodePassFn);
471483
}
472484

473485
encoder.resolveQuerySet(
@@ -484,7 +496,15 @@ class OcclusionQueryTest extends AllFeaturesMaxLimitsGPUTest {
484496
0,
485497
readBuffer.size
486498
);
487-
device.queue.submit([encoder.finish()]);
499+
}
500+
501+
async checkQueryResults(
502+
resources: ReturnType<OcclusionQueryTest['setup']>,
503+
checkQueryIndexResultFn: (passed: boolean, queryIndex: number) => void
504+
) {
505+
const { readBuffer, occlusionQuerySet, querySetOffset } = resources;
506+
const numQueries = occlusionQuerySet.count - querySetOffset;
507+
const queryIndices = range(numQueries, (i: number) => i + querySetOffset);
488508

489509
const result = await this.readBufferAsBigUint64(readBuffer);
490510
for (const queryIndex of queryIndices) {
@@ -495,6 +515,21 @@ class OcclusionQueryTest extends AllFeaturesMaxLimitsGPUTest {
495515

496516
return result;
497517
}
518+
519+
async runQueryTest(
520+
resources: ReturnType<OcclusionQueryTest['setup']>,
521+
renderPassDescriptor: GPURenderPassDescriptor | null,
522+
encodePassFn: (helper: RenderPassHelper, queryIndex: number) => void,
523+
checkQueryIndexResultFn: (passed: boolean, queryIndex: number) => void
524+
) {
525+
const { device } = this;
526+
527+
const encoder = device.createCommandEncoder();
528+
this.encodeAndResolveQueries(resources, encoder, renderPassDescriptor, encodePassFn);
529+
device.queue.submit([encoder.finish()]);
530+
531+
return this.checkQueryResults(resources, checkQueryIndexResultFn);
532+
}
498533
}
499534

500535
const kQueryTestBaseParams = kUnitCaseParamsBuilder
@@ -598,6 +633,59 @@ g.test('occlusion_query,basic')
598633
);
599634
});
600635

636+
g.test('occlusion_query,reuse')
637+
.desc(
638+
`
639+
Test queries can be reused.
640+
641+
This tests that if you write to a query twice in the same command buffer (different passes)
642+
and resolve after, that you get the 2nd result.
643+
`
644+
)
645+
.params(kQueryTestBaseParams.combine('noop', ['before', 'after'] as const))
646+
.fn(async t => {
647+
const { writeMask, renderMode, bufferOffset, querySetOffset, noop } = t.params;
648+
const kNumQueries = 30;
649+
const resources = t.setup({
650+
writeMask,
651+
renderMode,
652+
bufferOffset,
653+
querySetOffset,
654+
numQueries: kNumQueries,
655+
});
656+
const { renderPassDescriptor, vertexBuffer, pipeline } = resources;
657+
658+
const opEncodePassFn = (helper: RenderPassHelper, queryIndex: number) => {
659+
const queryHelper = helper.beginOcclusionQuery(queryIndex);
660+
queryHelper.setPipeline(pipeline);
661+
queryHelper.setVertexBuffer(vertexBuffer);
662+
queryHelper.draw(3);
663+
queryHelper.end();
664+
};
665+
666+
const noopEncodePassFn = (helper: RenderPassHelper, queryIndex: number) => {
667+
const queryHelper = helper.beginOcclusionQuery(queryIndex);
668+
queryHelper.end();
669+
};
670+
671+
const [firstOp, secondOp] =
672+
noop === 'before' ? [noopEncodePassFn, opEncodePassFn] : [opEncodePassFn, noopEncodePassFn];
673+
674+
const { device } = t;
675+
const encoder = device.createCommandEncoder();
676+
t.encodeQueries(resources, encoder, renderPassDescriptor, firstOp);
677+
t.encodeAndResolveQueries(resources, encoder, renderPassDescriptor, secondOp);
678+
device.queue.submit([encoder.finish()]);
679+
680+
await t.checkQueryResults(resources, (passed, queryIndex) => {
681+
const expectPassed = noop === 'before';
682+
t.expect(
683+
!!passed === expectPassed,
684+
`queryIndex: ${queryIndex}, was: ${!!passed}, expected: ${expectPassed}`
685+
);
686+
});
687+
});
688+
601689
g.test('occlusion_query,empty')
602690
.desc(
603691
`

0 commit comments

Comments
 (0)