Query API: Non Precise Occlusion Query

- Add BeginOcclusionQuery and EndOcclusionQuery in frontend
- Set occlusion query as unsafe APIs
- Add validation tests

Bug: dawn:434
Change-Id: I3d1cefed780812dd62fb082287ff71530b76ebee
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/31321
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
This commit is contained in:
Hao Li 2020-11-18 09:47:52 +00:00 committed by Commit Bot service account
parent 55f251dffe
commit 01e4450729
13 changed files with 373 additions and 4 deletions

View File

@ -1362,6 +1362,15 @@
{"name": "size", "type": "uint64_t", "default": "0"}
]
},
{
"name": "begin occlusion query",
"args": [
{"name": "query index", "type": "uint32_t"}
]
},
{
"name": "end occlusion query"
},
{
"name": "write timestamp",
"args": [

View File

@ -324,7 +324,19 @@ namespace dawn_native {
}
if (descriptor->occlusionQuerySet != nullptr) {
return DAWN_VALIDATION_ERROR("occlusionQuerySet not implemented");
DAWN_TRY(device->ValidateObject(descriptor->occlusionQuerySet));
// Occlusion query has not been implemented completely. Disallow it as unsafe until
// the implementaion is completed.
if (device->IsToggleEnabled(Toggle::DisallowUnsafeAPIs)) {
return DAWN_VALIDATION_ERROR(
"Occlusion query is disallowed because it has not been implemented "
"completely.");
}
if (descriptor->occlusionQuerySet->GetQueryType() != wgpu::QueryType::Occlusion) {
return DAWN_VALIDATION_ERROR("The type of query set must be Occlusion");
}
}
if (descriptor->colorAttachmentCount == 0 &&
@ -508,8 +520,9 @@ namespace dawn_native {
});
if (success) {
RenderPassEncoder* passEncoder = new RenderPassEncoder(
device, this, &mEncodingContext, std::move(usageTracker), width, height);
RenderPassEncoder* passEncoder =
new RenderPassEncoder(device, this, &mEncodingContext, std::move(usageTracker),
descriptor->occlusionQuerySet, width, height);
mEncodingContext.EnterPass(passEncoder);
return passEncoder;
}

View File

@ -159,6 +159,16 @@ namespace dawn_native {
Command type;
while (commands->NextCommandId(&type)) {
switch (type) {
case Command::BeginOcclusionQuery: {
commands->NextCommand<BeginOcclusionQueryCmd>();
break;
}
case Command::EndOcclusionQuery: {
commands->NextCommand<EndOcclusionQueryCmd>();
break;
}
case Command::EndRenderPass: {
commands->NextCommand<EndRenderPassCmd>();
DAWN_TRY(ValidateFinalDebugGroupStackSize(debugGroupStackSize));

View File

@ -36,6 +36,11 @@ namespace dawn_native {
begin->~BeginComputePassCmd();
break;
}
case Command::BeginOcclusionQuery: {
BeginOcclusionQueryCmd* begin = commands->NextCommand<BeginOcclusionQueryCmd>();
begin->~BeginOcclusionQueryCmd();
break;
}
case Command::BeginRenderPass: {
BeginRenderPassCmd* begin = commands->NextCommand<BeginRenderPassCmd>();
begin->~BeginRenderPassCmd();
@ -97,6 +102,11 @@ namespace dawn_native {
cmd->~EndComputePassCmd();
break;
}
case Command::EndOcclusionQuery: {
EndOcclusionQueryCmd* cmd = commands->NextCommand<EndOcclusionQueryCmd>();
cmd->~EndOcclusionQueryCmd();
break;
}
case Command::EndRenderPass: {
EndRenderPassCmd* cmd = commands->NextCommand<EndRenderPassCmd>();
cmd->~EndRenderPassCmd();
@ -198,6 +208,10 @@ namespace dawn_native {
commands->NextCommand<BeginComputePassCmd>();
break;
case Command::BeginOcclusionQuery:
commands->NextCommand<BeginOcclusionQueryCmd>();
break;
case Command::BeginRenderPass:
commands->NextCommand<BeginRenderPassCmd>();
break;
@ -246,6 +260,10 @@ namespace dawn_native {
commands->NextCommand<EndComputePassCmd>();
break;
case Command::EndOcclusionQuery:
commands->NextCommand<EndOcclusionQueryCmd>();
break;
case Command::EndRenderPass:
commands->NextCommand<EndRenderPassCmd>();
break;

View File

@ -34,6 +34,7 @@ namespace dawn_native {
enum class Command {
BeginComputePass,
BeginOcclusionQuery,
BeginRenderPass,
CopyBufferToBuffer,
CopyBufferToTexture,
@ -46,6 +47,7 @@ namespace dawn_native {
DrawIndirect,
DrawIndexedIndirect,
EndComputePass,
EndOcclusionQuery,
EndRenderPass,
ExecuteBundles,
InsertDebugMarker,
@ -66,6 +68,11 @@ namespace dawn_native {
struct BeginComputePassCmd {};
struct BeginOcclusionQueryCmd {
Ref<QuerySetBase> querySet;
uint32_t queryIndex;
};
struct RenderPassColorAttachmentInfo {
Ref<TextureViewBase> view;
Ref<TextureViewBase> resolveTarget;
@ -173,6 +180,11 @@ namespace dawn_native {
struct EndComputePassCmd {};
struct EndOcclusionQueryCmd {
Ref<QuerySetBase> querySet;
uint32_t queryIndex;
};
struct EndRenderPassCmd {};
struct ExecuteBundlesCmd {

View File

@ -52,12 +52,14 @@ namespace dawn_native {
CommandEncoder* commandEncoder,
EncodingContext* encodingContext,
PassResourceUsageTracker usageTracker,
QuerySetBase* occlusionQuerySet,
uint32_t renderTargetWidth,
uint32_t renderTargetHeight)
: RenderEncoderBase(device, encodingContext),
mCommandEncoder(commandEncoder),
mRenderTargetWidth(renderTargetWidth),
mRenderTargetHeight(renderTargetHeight) {
mRenderTargetHeight(renderTargetHeight),
mOcclusionQuerySet(occlusionQuerySet) {
mUsageTracker = std::move(usageTracker);
}
@ -94,6 +96,13 @@ namespace dawn_native {
if (mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
allocator->Allocate<EndRenderPassCmd>(Command::EndRenderPass);
if (GetDevice()->IsValidationEnabled()) {
if (mOcclusionQueryActive) {
return DAWN_VALIDATION_ERROR(
"The occlusion query must be ended before endPass.");
}
}
return {};
})) {
mEncodingContext->ExitPass(this, mUsageTracker.AcquireResourceUsage());
@ -208,6 +217,67 @@ namespace dawn_native {
});
}
void RenderPassEncoder::BeginOcclusionQuery(uint32_t queryIndex) {
mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
if (GetDevice()->IsValidationEnabled()) {
if (mOcclusionQuerySet.Get() == nullptr) {
return DAWN_VALIDATION_ERROR(
"The occlusionQuerySet in RenderPassDescriptor must be set.");
}
// The type of querySet has been validated by ValidateRenderPassDescriptor
if (queryIndex >= mOcclusionQuerySet->GetQueryCount()) {
return DAWN_VALIDATION_ERROR(
"Query index exceeds the number of queries in query set.");
}
if (mOcclusionQueryActive) {
return DAWN_VALIDATION_ERROR(
"Only a single occlusion query can be begun at a time.");
}
DAWN_TRY(ValidateQueryIndexOverwrite(mOcclusionQuerySet.Get(), queryIndex,
GetQueryAvailabilityMap()));
mCommandEncoder->TrackUsedQuerySet(mOcclusionQuerySet.Get());
}
// Record the current query index for endOcclusionQuery.
mCurrentOcclusionQueryIndex = queryIndex;
mOcclusionQueryActive = true;
BeginOcclusionQueryCmd* cmd =
allocator->Allocate<BeginOcclusionQueryCmd>(Command::BeginOcclusionQuery);
cmd->querySet = mOcclusionQuerySet.Get();
cmd->queryIndex = queryIndex;
return {};
});
}
void RenderPassEncoder::EndOcclusionQuery() {
mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
if (GetDevice()->IsValidationEnabled()) {
if (!mOcclusionQueryActive) {
return DAWN_VALIDATION_ERROR(
"EndOcclusionQuery cannot be called without corresponding "
"BeginOcclusionQuery.");
}
}
TrackQueryAvailability(mOcclusionQuerySet.Get(), mCurrentOcclusionQueryIndex);
mOcclusionQueryActive = false;
EndOcclusionQueryCmd* cmd =
allocator->Allocate<EndOcclusionQueryCmd>(Command::EndOcclusionQuery);
cmd->querySet = mOcclusionQuerySet.Get();
cmd->queryIndex = mCurrentOcclusionQueryIndex;
return {};
});
}
void RenderPassEncoder::WriteTimestamp(QuerySetBase* querySet, uint32_t queryIndex) {
mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
if (GetDevice()->IsValidationEnabled()) {

View File

@ -28,6 +28,7 @@ namespace dawn_native {
CommandEncoder* commandEncoder,
EncodingContext* encodingContext,
PassResourceUsageTracker usageTracker,
QuerySetBase* occlusionQuerySet,
uint32_t renderTargetWidth,
uint32_t renderTargetHeight);
@ -51,6 +52,9 @@ namespace dawn_native {
void SetScissorRect(uint32_t x, uint32_t y, uint32_t width, uint32_t height);
void ExecuteBundles(uint32_t count, RenderBundleBase* const* renderBundles);
void BeginOcclusionQuery(uint32_t queryIndex);
void EndOcclusionQuery();
void WriteTimestamp(QuerySetBase* querySet, uint32_t queryIndex);
protected:
@ -71,6 +75,11 @@ namespace dawn_native {
// query cannot be written twice in same render pass, so each render pass also need to have
// its own query availability map.
QueryAvailabilityMap mQueryAvailabilityMap;
// The resources for occlusion query
Ref<QuerySetBase> mOcclusionQuerySet;
uint32_t mCurrentOcclusionQueryIndex = 0;
bool mOcclusionQueryActive = false;
};
} // namespace dawn_native

View File

@ -1482,6 +1482,14 @@ namespace dawn_native { namespace d3d12 {
break;
}
case Command::BeginOcclusionQuery: {
return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation.");
}
case Command::EndOcclusionQuery: {
return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation.");
}
case Command::WriteTimestamp: {
WriteTimestampCmd* cmd = mCommands.NextCommand<WriteTimestampCmd>();

View File

@ -1294,6 +1294,14 @@ namespace dawn_native { namespace metal {
break;
}
case Command::BeginOcclusionQuery: {
return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation.");
}
case Command::EndOcclusionQuery: {
return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation.");
}
case Command::WriteTimestamp: {
WriteTimestampCmd* cmd = mCommands.NextCommand<WriteTimestampCmd>();
QuerySet* querySet = ToBackend(cmd->querySet.Get());

View File

@ -1221,6 +1221,14 @@ namespace dawn_native { namespace opengl {
break;
}
case Command::BeginOcclusionQuery: {
return DAWN_UNIMPLEMENTED_ERROR("BeginOcclusionQuery unimplemented.");
}
case Command::EndOcclusionQuery: {
return DAWN_UNIMPLEMENTED_ERROR("EndOcclusionQuery unimplemented.");
}
case Command::WriteTimestamp:
return DAWN_UNIMPLEMENTED_ERROR("WriteTimestamp unimplemented");

View File

@ -1222,6 +1222,14 @@ namespace dawn_native { namespace vulkan {
break;
}
case Command::BeginOcclusionQuery: {
return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation.");
}
case Command::EndOcclusionQuery: {
return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation.");
}
case Command::WriteTimestamp: {
WriteTimestampCmd* cmd = mCommands.NextCommand<WriteTimestampCmd>();

View File

@ -68,6 +68,146 @@ TEST_F(QuerySetValidationTest, DestroyDestroyedQuerySet) {
querySet.Destroy();
}
class OcclusionQueryValidationTest : public QuerySetValidationTest {};
// Test the occlusionQuerySet in RenderPassDescriptor
TEST_F(OcclusionQueryValidationTest, InvalidOcclusionQuerySet) {
wgpu::QuerySet occlusionQuerySet = CreateQuerySet(device, wgpu::QueryType::Occlusion, 2);
DummyRenderPass renderPass(device);
// Success
{
renderPass.occlusionQuerySet = occlusionQuerySet;
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
pass.BeginOcclusionQuery(0);
pass.EndOcclusionQuery();
pass.BeginOcclusionQuery(1);
pass.EndOcclusionQuery();
pass.EndPass();
encoder.Finish();
}
// Fail to begin occlusion query if the occlusionQuerySet is not set in RenderPassDescriptor
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
DummyRenderPass renderPassWithoutOcclusion(device);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassWithoutOcclusion);
pass.BeginOcclusionQuery(0);
pass.EndOcclusionQuery();
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// Fail to begin render pass if the occlusionQuerySet is created from other device
{
wgpu::Device otherDevice = adapter.CreateDevice();
wgpu::QuerySet occlusionQuerySetOnOther =
CreateQuerySet(otherDevice, wgpu::QueryType::Occlusion, 2);
renderPass.occlusionQuerySet = occlusionQuerySetOnOther;
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.BeginRenderPass(&renderPass);
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// Fail to submit occlusion query with a destroyed query set
{
renderPass.occlusionQuerySet = occlusionQuerySet;
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
pass.BeginOcclusionQuery(0);
pass.EndOcclusionQuery();
pass.EndPass();
wgpu::CommandBuffer commands = encoder.Finish();
wgpu::Queue queue = device.GetDefaultQueue();
occlusionQuerySet.Destroy();
ASSERT_DEVICE_ERROR(queue.Submit(1, &commands));
}
}
// Test query index of occlusion query
TEST_F(OcclusionQueryValidationTest, InvalidQueryIndex) {
wgpu::QuerySet occlusionQuerySet = CreateQuerySet(device, wgpu::QueryType::Occlusion, 2);
DummyRenderPass renderPass(device);
renderPass.occlusionQuerySet = occlusionQuerySet;
// Fail to begin occlusion query if the query index exceeds the number of queries in query set
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
pass.BeginOcclusionQuery(2);
pass.EndOcclusionQuery();
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// Success to begin occlusion query with same query index twice on different render encoder
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass0 = encoder.BeginRenderPass(&renderPass);
pass0.BeginOcclusionQuery(0);
pass0.EndOcclusionQuery();
pass0.EndPass();
wgpu::RenderPassEncoder pass1 = encoder.BeginRenderPass(&renderPass);
pass1.BeginOcclusionQuery(0);
pass1.EndOcclusionQuery();
pass1.EndPass();
encoder.Finish();
}
// Fail to begin occlusion query with same query index twice on a same render encoder
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
pass.BeginOcclusionQuery(0);
pass.EndOcclusionQuery();
pass.BeginOcclusionQuery(0);
pass.EndOcclusionQuery();
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
}
// Test the correspondence between BeginOcclusionQuery and EndOcclusionQuery
TEST_F(OcclusionQueryValidationTest, InvalidBeginAndEnd) {
wgpu::QuerySet occlusionQuerySet = CreateQuerySet(device, wgpu::QueryType::Occlusion, 2);
DummyRenderPass renderPass(device);
renderPass.occlusionQuerySet = occlusionQuerySet;
// Fail to begin an occlusion query without corresponding end operation
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
pass.BeginOcclusionQuery(0);
pass.BeginOcclusionQuery(1);
pass.EndOcclusionQuery();
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// Fail to end occlusion query twice in a row even the begin occlusion query twice
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
pass.BeginOcclusionQuery(0);
pass.BeginOcclusionQuery(1);
pass.EndOcclusionQuery();
pass.EndOcclusionQuery();
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// Fail to end occlusion query without begin operation
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
pass.EndOcclusionQuery();
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
}
class TimestampQueryValidationTest : public QuerySetValidationTest {
protected:
wgpu::Device CreateTestDevice() override {
@ -101,6 +241,18 @@ TEST_F(TimestampQueryValidationTest, UnnecessaryPipelineStatistics) {
{wgpu::PipelineStatisticName::VertexShaderInvocations}));
}
// Test query set with type of timestamp is set to the occlusionQuerySet of RenderPassDescriptor.
TEST_F(TimestampQueryValidationTest, BeginRenderPassWithTimestampQuerySet) {
// Fail to begin render pass if the type of occlusionQuerySet is not Occlusion
wgpu::QuerySet querySet = CreateQuerySet(device, wgpu::QueryType::Timestamp, 1);
DummyRenderPass renderPass(device);
renderPass.occlusionQuerySet = querySet;
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.BeginRenderPass(&renderPass);
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// Test write timestamp on command encoder
TEST_F(TimestampQueryValidationTest, WriteTimestampOnCommandEncoder) {
wgpu::QuerySet timestampQuerySet = CreateQuerySet(device, wgpu::QueryType::Timestamp, 2);
@ -311,6 +463,21 @@ TEST_F(PipelineStatisticsQueryValidationTest, InvalidPipelineStatistics) {
}
}
// Test query set with type of pipeline statistics is set to the occlusionQuerySet of
// RenderPassDescriptor.
TEST_F(PipelineStatisticsQueryValidationTest, BeginRenderPassWithPipelineStatisticsQuerySet) {
// Fail to begin render pass if the type of occlusionQuerySet is not Occlusion
wgpu::QuerySet querySet =
CreateQuerySet(device, wgpu::QueryType::PipelineStatistics, 1,
{wgpu::PipelineStatisticName::VertexShaderInvocations});
DummyRenderPass renderPass(device);
renderPass.occlusionQuerySet = querySet;
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.BeginRenderPass(&renderPass);
ASSERT_DEVICE_ERROR(encoder.Finish());
}
class ResolveQuerySetValidationTest : public QuerySetValidationTest {
protected:
wgpu::Buffer CreateBuffer(wgpu::Device cDevice, uint64_t size, wgpu::BufferUsage usage) {

View File

@ -167,3 +167,32 @@ TEST_F(UnsafeAPIValidationTest, DynamicStorageBuffer) {
ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&desc));
}
}
// Check that occlusion query is disallowed as part of unsafe APIs.
TEST_F(UnsafeAPIValidationTest, OcclusionQueryDisallowed) {
DummyRenderPass renderPass(device);
// Control case: BeginRenderPass without occlusionQuerySet is allowed.
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
pass.EndPass();
encoder.Finish();
}
// Error case: BeginRenderPass with occlusionQuerySet is disallowed.
{
wgpu::QuerySetDescriptor descriptor;
descriptor.type = wgpu::QueryType::Occlusion;
descriptor.count = 1;
wgpu::QuerySet querySet = device.CreateQuerySet(&descriptor);
renderPass.occlusionQuerySet = querySet;
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
}