Query API: Non-precise occlusion query on Vulkan

- Implement BeginOcclusionQuery/EndOcclusionQuery on Vulkan
- Add compute pipeline to convert non-zero values to 1.

Bug: dawn:434
Change-Id: Ie0238078dfd26334caf36d23ded1d7742dd829a3
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/38221
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Hao Li <hao.x.li@intel.com>
diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp
index 47adf79..1c7cdd2 100644
--- a/src/dawn_native/vulkan/CommandBufferVk.cpp
+++ b/src/dawn_native/vulkan/CommandBufferVk.cpp
@@ -1188,11 +1188,19 @@
                 }
 
                 case Command::BeginOcclusionQuery: {
-                    return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation.");
+                    BeginOcclusionQueryCmd* cmd = mCommands.NextCommand<BeginOcclusionQueryCmd>();
+
+                    device->fn.CmdBeginQuery(commands, ToBackend(cmd->querySet.Get())->GetHandle(),
+                                             cmd->queryIndex, 0);
+                    break;
                 }
 
                 case Command::EndOcclusionQuery: {
-                    return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation.");
+                    EndOcclusionQueryCmd* cmd = mCommands.NextCommand<EndOcclusionQueryCmd>();
+
+                    device->fn.CmdEndQuery(commands, ToBackend(cmd->querySet.Get())->GetHandle(),
+                                           cmd->queryIndex);
+                    break;
                 }
 
                 case Command::WriteTimestamp: {
diff --git a/src/tests/end2end/QueryTests.cpp b/src/tests/end2end/QueryTests.cpp
index 7b768fb..951545c 100644
--- a/src/tests/end2end/QueryTests.cpp
+++ b/src/tests/end2end/QueryTests.cpp
@@ -33,6 +33,46 @@
     }
 };
 
+// Clear the content of the result buffer into 0xFFFFFFFF.
+constexpr static uint64_t kSentinelValue = ~uint64_t(0);
+
+class OcclusionExpectation : public detail::Expectation {
+  public:
+    enum class Result { Zero, NonZero };
+
+    ~OcclusionExpectation() override = default;
+
+    OcclusionExpectation(Result expected) {
+        mExpected = expected;
+    }
+
+    testing::AssertionResult Check(const void* data, size_t size) override {
+        ASSERT(size % sizeof(uint64_t) == 0);
+        const uint64_t* actual = static_cast<const uint64_t*>(data);
+        for (size_t i = 0; i < size / sizeof(uint64_t); i++) {
+            if (actual[i] == kSentinelValue) {
+                return testing::AssertionFailure()
+                       << "Data[" << i << "] was not written (it kept the sentinel value of "
+                       << kSentinelValue << ")." << std::endl;
+            }
+            if (mExpected == Result::Zero && actual[i] != 0) {
+                return testing::AssertionFailure()
+                       << "Expected data[" << i << "] to be zero, actual: " << actual[i] << "."
+                       << std::endl;
+            }
+            if (mExpected == Result::NonZero && actual[i] == 0) {
+                return testing::AssertionFailure()
+                       << "Expected data[" << i << "] to be non-zero." << std::endl;
+            }
+        }
+
+        return testing::AssertionSuccess();
+    }
+
+  private:
+    Result mExpected;
+};
+
 class OcclusionQueryTests : public QueryTests {
   protected:
     void SetUp() override {
@@ -80,7 +120,7 @@
 
     void TestOcclusionQueryWithDepthStencilTest(bool depthTestEnabled,
                                                 bool stencilTestEnabled,
-                                                uint64_t expected) {
+                                                OcclusionExpectation::Result expected) {
         utils::ComboRenderPipelineDescriptor descriptor(device);
         descriptor.vertexStage.module = vsModule;
         descriptor.cFragmentStage.module = fsModule;
@@ -108,10 +148,9 @@
 
         wgpu::QuerySet querySet = CreateOcclusionQuerySet(kQueryCount);
         wgpu::Buffer destination = CreateResolveBuffer(kQueryCount * sizeof(uint64_t));
-        uint64_t myData = ~uint64_t(1);
         // Set all bits in buffer to check 0 is correctly written if there is no sample passed the
         // occlusion testing
-        queue.WriteBuffer(destination, 0, &myData, sizeof(myData));
+        queue.WriteBuffer(destination, 0, &kSentinelValue, sizeof(kSentinelValue));
 
         utils::ComboRenderPassDescriptor renderPass({renderTargetView}, depthTextureView);
         renderPass.occlusionQuerySet = querySet;
@@ -129,10 +168,11 @@
         wgpu::CommandBuffer commands = encoder.Finish();
         queue.Submit(1, &commands);
 
-        EXPECT_BUFFER_U64_EQ(expected, destination, 0);
+        EXPECT_BUFFER(destination, 0, sizeof(uint64_t), new OcclusionExpectation(expected));
     }
 
-    void TestOcclusionQueryWithScissorTest(ScissorRect rect, uint64_t expected) {
+    void TestOcclusionQueryWithScissorTest(ScissorRect rect,
+                                           OcclusionExpectation::Result expected) {
         utils::ComboRenderPipelineDescriptor descriptor(device);
         descriptor.vertexStage.module = vsModule;
         descriptor.cFragmentStage.module = fsModule;
@@ -141,10 +181,9 @@
 
         wgpu::QuerySet querySet = CreateOcclusionQuerySet(kQueryCount);
         wgpu::Buffer destination = CreateResolveBuffer(kQueryCount * sizeof(uint64_t));
-        uint64_t myData = ~uint64_t(1);
         // Set all bits in buffer to check 0 is correctly written if there is no sample passed the
         // occlusion testing
-        queue.WriteBuffer(destination, 0, &myData, sizeof(myData));
+        queue.WriteBuffer(destination, 0, &kSentinelValue, sizeof(kSentinelValue));
 
         utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize);
         renderPass.renderPassInfo.occlusionQuerySet = querySet;
@@ -162,7 +201,7 @@
         wgpu::CommandBuffer commands = encoder.Finish();
         queue.Submit(1, &commands);
 
-        EXPECT_BUFFER_U64_EQ(expected, destination, 0);
+        EXPECT_BUFFER(destination, 0, sizeof(uint64_t), new OcclusionExpectation(expected));
     }
 
     wgpu::ShaderModule vsModule;
@@ -184,39 +223,39 @@
 }
 
 // Draw a bottom right triangle with depth/stencil testing enabled and check whether there is
-// sample passed the testing by non-precise occlusion query with the binary results:
-// 0 indicates that no sample passed depth/stencil testing,
-// 1 indicates that at least one sample passed depth/stencil testing.
+// sample passed the testing by non-precise occlusion query with the results:
+// zero indicates that no sample passed depth/stencil testing,
+// non-zero indicates that at least one sample passed depth/stencil testing.
 TEST_P(OcclusionQueryTests, QueryWithDepthStencilTest) {
-    // TODO(hao.x.li@intel.com): Implement non-precise occlusion on Metal and Vulkan
-    DAWN_SKIP_TEST_IF(IsMetal() || IsVulkan());
+    // TODO(hao.x.li@intel.com): Implement non-precise occlusion on Metal
+    DAWN_SKIP_TEST_IF(IsMetal());
 
     // Disable depth/stencil testing, the samples always pass the testing, the expected occlusion
-    // result is 1.
-    TestOcclusionQueryWithDepthStencilTest(false, false, 1);
+    // result is non-zero.
+    TestOcclusionQueryWithDepthStencilTest(false, false, OcclusionExpectation::Result::NonZero);
 
     // Only enable depth testing and set the samples never pass the testing, the expected occlusion
-    // result is 0.
-    TestOcclusionQueryWithDepthStencilTest(true, false, 0);
+    // result is zero.
+    TestOcclusionQueryWithDepthStencilTest(true, false, OcclusionExpectation::Result::Zero);
 
     // Only enable stencil testing and set the samples never pass the testing, the expected
-    // occlusion result is 0.
-    TestOcclusionQueryWithDepthStencilTest(false, true, 0);
+    // occlusion result is zero.
+    TestOcclusionQueryWithDepthStencilTest(false, true, OcclusionExpectation::Result::Zero);
 }
 
 // Draw a bottom right triangle with scissor testing enabled and check whether there is
-// sample passed the testing by non-precise occlusion query with the binary results:
-// 0 indicates that no sample passed scissor testing,
-// 1 indicates that at least one sample passed scissor testing.
+// sample passed the testing by non-precise occlusion query with the results:
+// zero indicates that no sample passed scissor testing,
+// non-zero indicates that at least one sample passed scissor testing.
 TEST_P(OcclusionQueryTests, QueryWithScissorTest) {
-    // TODO(hao.x.li@intel.com): Implement non-precise occlusion on Metal and Vulkan
-    DAWN_SKIP_TEST_IF(IsMetal() || IsVulkan());
+    // TODO(hao.x.li@intel.com): Implement non-precise occlusion on Metal
+    DAWN_SKIP_TEST_IF(IsMetal());
 
-    // Test there are samples passed scissor testing, the expected occlusion result is 1.
-    TestOcclusionQueryWithScissorTest({2, 1, 2, 1}, 1);
+    // Test there are samples passed scissor testing, the expected occlusion result is non-zero.
+    TestOcclusionQueryWithScissorTest({2, 1, 2, 1}, OcclusionExpectation::Result::NonZero);
 
-    // Test there is no sample passed scissor testing, the expected occlusion result is 0.
-    TestOcclusionQueryWithScissorTest({0, 0, 2, 1}, 0);
+    // Test there is no sample passed scissor testing, the expected occlusion result is zero.
+    TestOcclusionQueryWithScissorTest({0, 0, 2, 1}, OcclusionExpectation::Result::Zero);
 }
 
 DAWN_INSTANTIATE_TEST(OcclusionQueryTests, D3D12Backend(), MetalBackend(), VulkanBackend());