Add more tests in white_box/D3D12GPUTimestampCalibrationTests.cpp

Add more coverage for timestamp query on D3D12 backend to make sure
timestamps are converted correctly:
- All timestamp queries inside and outside passes
- The 'disable_timestamp_query_conversion' toggle disabled and enabled

Bug: dawn:1250

Change-Id: Ibdc6b35faed7cc1e1a8b60df4a5032914b411bc1
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/108022
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Hao Li <hao.x.li@intel.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/dawn/tests/white_box/D3D12GPUTimestampCalibrationTests.cpp b/src/dawn/tests/white_box/D3D12GPUTimestampCalibrationTests.cpp
index 2bad627..a6681e3 100644
--- a/src/dawn/tests/white_box/D3D12GPUTimestampCalibrationTests.cpp
+++ b/src/dawn/tests/white_box/D3D12GPUTimestampCalibrationTests.cpp
@@ -22,23 +22,54 @@
 
 namespace dawn::native::d3d12 {
 namespace {
+
+using FeatureName = wgpu::FeatureName;
+enum class EncoderType { NonPass, ComputePass, RenderPass };
+
+std::ostream& operator<<(std::ostream& o, EncoderType type) {
+    switch (type) {
+        case EncoderType::NonPass:
+            o << "NonPass";
+            break;
+        case EncoderType::ComputePass:
+            o << "ComputePass";
+            break;
+        case EncoderType::RenderPass:
+            o << "RenderPass";
+            break;
+        default:
+            UNREACHABLE();
+            break;
+    }
+
+    return o;
+}
+
+DAWN_TEST_PARAM_STRUCT(GPUTimestampCalibrationTestParams, FeatureName, EncoderType);
+
 class ExpectBetweenTimestamps : public ::detail::Expectation {
   public:
     ~ExpectBetweenTimestamps() override = default;
 
-    ExpectBetweenTimestamps(uint64_t value0, uint64_t value1) {
-        mValue0 = value0;
-        mValue1 = value1;
-    }
+    ExpectBetweenTimestamps(uint64_t minValue, uint64_t maxValue, float errorToleranceRatio = 0.0f)
+        : mMinValue(minValue), mMaxValue(maxValue), mErrorToleranceRatio(errorToleranceRatio) {}
 
-    // Expect the actual results are between mValue0 and mValue1.
+    // Expect the actual results are between mMinValue and mMaxValue with error tolerance ratio.
     testing::AssertionResult Check(const void* data, size_t size) override {
         const uint64_t* actual = static_cast<const uint64_t*>(data);
+
+        if (mErrorToleranceRatio != 0.0f) {
+            mMinValue -=
+                static_cast<uint64_t>(static_cast<double>(mMinValue * mErrorToleranceRatio));
+            mMaxValue +=
+                static_cast<uint64_t>(static_cast<double>(mMaxValue * mErrorToleranceRatio));
+        }
+
         for (size_t i = 0; i < size / sizeof(uint64_t); ++i) {
-            if (actual[i] < mValue0 || actual[i] > mValue1) {
+            if (actual[i] < mMinValue || actual[i] > mMaxValue) {
                 return testing::AssertionFailure()
-                       << "Expected data[" << i << "] to be between " << mValue0 << " and "
-                       << mValue1 << ", actual " << actual[i] << std::endl;
+                       << "Expected data[" << i << "] to be between " << mMinValue << " and "
+                       << mMaxValue << ", actual " << actual[i] << std::endl;
             }
         }
 
@@ -46,75 +77,186 @@
     }
 
   private:
-    uint64_t mValue0;
-    uint64_t mValue1;
+    uint64_t mMinValue;
+    uint64_t mMaxValue;
+    float mErrorToleranceRatio;
 };
 
 }  // anonymous namespace
 
-class D3D12GPUTimestampCalibrationTests : public DawnTest {
+class D3D12GPUTimestampCalibrationTests
+    : public DawnTestWithParams<GPUTimestampCalibrationTestParams> {
   protected:
     void SetUp() override {
-        DawnTest::SetUp();
+        DawnTestWithParams<GPUTimestampCalibrationTestParams>::SetUp();
 
         DAWN_TEST_UNSUPPORTED_IF(UsesWire());
         // Requires that timestamp query feature is enabled and timestamp query conversion is
         // disabled.
-        DAWN_TEST_UNSUPPORTED_IF(!SupportsFeatures({wgpu::FeatureName::TimestampQuery}) ||
-                                 !HasToggleEnabled("disable_timestamp_query_conversion"));
+        DAWN_TEST_UNSUPPORTED_IF(!mIsFeatureSupported);
+        // The "timestamp-query-inside-passes" feature is not supported on command encoder.
+        DAWN_TEST_UNSUPPORTED_IF(GetParam().mFeatureName ==
+                                     wgpu::FeatureName::TimestampQueryInsidePasses &&
+                                 GetParam().mEncoderType == EncoderType::NonPass);
     }
 
     std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
         std::vector<wgpu::FeatureName> requiredFeatures = {};
-        if (SupportsFeatures({wgpu::FeatureName::TimestampQuery})) {
-            requiredFeatures.push_back(wgpu::FeatureName::TimestampQuery);
+        if (SupportsFeatures({GetParam().mFeatureName})) {
+            requiredFeatures.push_back(GetParam().mFeatureName);
+            mIsFeatureSupported = true;
         }
         return requiredFeatures;
     }
+
+    void EncodeTimestampQueryOnComputePass(const wgpu::CommandEncoder& encoder,
+                                           const wgpu::QuerySet& querySet) {
+        switch (GetParam().mFeatureName) {
+            case wgpu::FeatureName::TimestampQuery: {
+                std::vector<wgpu::ComputePassTimestampWrite> timestampWrites;
+                timestampWrites.push_back(
+                    {querySet, 0, wgpu::ComputePassTimestampLocation::Beginning});
+                timestampWrites.push_back({querySet, 1, wgpu::ComputePassTimestampLocation::End});
+
+                wgpu::ComputePassDescriptor descriptor;
+                descriptor.timestampWriteCount = timestampWrites.size();
+                descriptor.timestampWrites = timestampWrites.data();
+
+                wgpu::ComputePassEncoder pass = encoder.BeginComputePass(&descriptor);
+                pass.End();
+                break;
+            }
+            case wgpu::FeatureName::TimestampQueryInsidePasses: {
+                wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
+                pass.WriteTimestamp(querySet, 0);
+                pass.WriteTimestamp(querySet, 1);
+                pass.End();
+                break;
+            }
+            default:
+                break;
+        }
+    }
+
+    void EncodeTimestampQueryOnRenderPass(const wgpu::CommandEncoder& encoder,
+                                          const wgpu::QuerySet& querySet) {
+        constexpr static unsigned int kRTSize = 4;
+        utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize);
+
+        switch (GetParam().mFeatureName) {
+            case wgpu::FeatureName::TimestampQuery: {
+                std::vector<wgpu::RenderPassTimestampWrite> timestampWrites;
+                timestampWrites.push_back(
+                    {querySet, 0, wgpu::RenderPassTimestampLocation::Beginning});
+                timestampWrites.push_back({querySet, 1, wgpu::RenderPassTimestampLocation::End});
+
+                renderPass.renderPassInfo.timestampWriteCount = timestampWrites.size();
+                renderPass.renderPassInfo.timestampWrites = timestampWrites.data();
+
+                wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
+                pass.End();
+                break;
+            }
+            case wgpu::FeatureName::TimestampQueryInsidePasses: {
+                wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
+                pass.WriteTimestamp(querySet, 0);
+                pass.WriteTimestamp(querySet, 1);
+                pass.End();
+                break;
+            }
+            default:
+                break;
+        }
+    }
+
+    void RunTest() {
+        constexpr uint32_t kQueryCount = 2;
+
+        // Create query set
+        wgpu::QuerySetDescriptor querySetDescriptor;
+        querySetDescriptor.count = kQueryCount;
+        querySetDescriptor.type = wgpu::QueryType::Timestamp;
+        wgpu::QuerySet querySet = device.CreateQuerySet(&querySetDescriptor);
+
+        // Create resolve buffer
+        wgpu::BufferDescriptor bufferDescriptor;
+        bufferDescriptor.size = kQueryCount * sizeof(uint64_t);
+        bufferDescriptor.usage = wgpu::BufferUsage::QueryResolve | wgpu::BufferUsage::CopySrc |
+                                 wgpu::BufferUsage::CopyDst;
+        wgpu::Buffer destination = device.CreateBuffer(&bufferDescriptor);
+
+        // Encode timestamp query
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        switch (GetParam().mEncoderType) {
+            case EncoderType::NonPass: {
+                // The "timestamp-query-inside-passes" feature is not supported on command encoder
+                ASSERT(GetParam().mFeatureName != wgpu::FeatureName::TimestampQueryInsidePasses);
+                encoder.WriteTimestamp(querySet, 0);
+                encoder.WriteTimestamp(querySet, 1);
+                break;
+            }
+            case EncoderType::ComputePass: {
+                EncodeTimestampQueryOnComputePass(encoder, querySet);
+                break;
+            }
+            case EncoderType::RenderPass: {
+                EncodeTimestampQueryOnRenderPass(encoder, querySet);
+                break;
+            }
+        }
+        wgpu::CommandBuffer commands = encoder.Finish();
+
+        // Start calibration between GPU timestamp and CPU timestamp
+        Device* d3DDevice = reinterpret_cast<Device*>(device.Get());
+        uint64_t gpuTimestamp0, gpuTimestamp1;
+        uint64_t cpuTimestamp0, cpuTimestamp1;
+        d3DDevice->GetCommandQueue()->GetClockCalibration(&gpuTimestamp0, &cpuTimestamp0);
+        queue.Submit(1, &commands);
+        WaitForAllOperations();
+        d3DDevice->GetCommandQueue()->GetClockCalibration(&gpuTimestamp1, &cpuTimestamp1);
+
+        // Separate resolve queryset to reduce the execution time of the queue with WriteTimestamp,
+        // so that the timestamp in the querySet will be closer to both gpuTimestamps from
+        // GetClockCalibration.
+        wgpu::CommandEncoder resolveEncoder = device.CreateCommandEncoder();
+        resolveEncoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 0);
+        wgpu::CommandBuffer resolveCommands = resolveEncoder.Finish();
+        queue.Submit(1, &resolveCommands);
+
+        float errorToleranceRatio = 0.0f;
+        if (!HasToggleEnabled("disable_timestamp_query_conversion")) {
+            uint64_t gpuFrequency;
+            d3DDevice->GetCommandQueue()->GetTimestampFrequency(&gpuFrequency);
+            float period = static_cast<float>(1e9) / gpuFrequency;
+            gpuTimestamp0 = static_cast<uint64_t>(static_cast<double>(gpuTimestamp0 * period));
+            gpuTimestamp1 = static_cast<uint64_t>(static_cast<double>(gpuTimestamp1 * period));
+
+            // We have 15 bits of precision in the timestamp query conversion so we
+            // expect that for the error tolerance.
+            errorToleranceRatio = 1.0 / (1 << 15);  // about 3e-5.
+        }
+
+        EXPECT_BUFFER(
+            destination, 0, kQueryCount * sizeof(uint64_t),
+            new ExpectBetweenTimestamps(gpuTimestamp0, gpuTimestamp1, errorToleranceRatio));
+    }
+
+  private:
+    bool mIsFeatureSupported = false;
 };
 
 // Check that the timestamps got by timestamp query are between the two timestamps from
-// GetClockCalibration() after the timestamp conversion is disabled.
-TEST_P(D3D12GPUTimestampCalibrationTests, TimestampsInOrder) {
-    constexpr uint32_t kQueryCount = 2;
-
-    wgpu::QuerySetDescriptor querySetDescriptor;
-    querySetDescriptor.count = kQueryCount;
-    querySetDescriptor.type = wgpu::QueryType::Timestamp;
-    wgpu::QuerySet querySet = device.CreateQuerySet(&querySetDescriptor);
-
-    wgpu::BufferDescriptor bufferDescriptor;
-    bufferDescriptor.size = kQueryCount * sizeof(uint64_t);
-    bufferDescriptor.usage =
-        wgpu::BufferUsage::QueryResolve | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
-    wgpu::Buffer destination = device.CreateBuffer(&bufferDescriptor);
-
-    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
-    encoder.WriteTimestamp(querySet, 0);
-    encoder.WriteTimestamp(querySet, 1);
-    wgpu::CommandBuffer commands = encoder.Finish();
-
-    Device* d3DDevice = reinterpret_cast<Device*>(device.Get());
-    uint64_t gpuTimestamp0, gpuTimestamp1;
-    uint64_t cpuTimestamp0, cpuTimestamp1;
-    d3DDevice->GetCommandQueue()->GetClockCalibration(&gpuTimestamp0, &cpuTimestamp0);
-    queue.Submit(1, &commands);
-    WaitForAllOperations();
-    d3DDevice->GetCommandQueue()->GetClockCalibration(&gpuTimestamp1, &cpuTimestamp1);
-
-    // Separate resolve queryset to reduce the execution time of the queue with WriteTimestamp,
-    // so that the timestamp in the querySet will be closer to both gpuTimestamps from
-    // GetClockCalibration.
-    wgpu::CommandEncoder resolveEncoder = device.CreateCommandEncoder();
-    resolveEncoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 0);
-    wgpu::CommandBuffer resolveCommands = resolveEncoder.Finish();
-    queue.Submit(1, &resolveCommands);
-
-    EXPECT_BUFFER(destination, 0, kQueryCount * sizeof(uint64_t),
-                  new ExpectBetweenTimestamps(gpuTimestamp0, gpuTimestamp1));
+// GetClockCalibration() with the 'disable_timestamp_query_conversion' toggle disabled or enabled.
+TEST_P(D3D12GPUTimestampCalibrationTests, TimestampsCalibration) {
+    RunTest();
 }
 
-DAWN_INSTANTIATE_TEST(D3D12GPUTimestampCalibrationTests,
-                      D3D12Backend({"disable_timestamp_query_conversion"}));
+DAWN_INSTANTIATE_TEST_P(
+    D3D12GPUTimestampCalibrationTests,
+    // Test with the disable_timestamp_query_conversion toggle forced on and off.
+    {D3D12Backend({"disable_timestamp_query_conversion"}, {}),
+     D3D12Backend({}, {"disable_timestamp_query_conversion"})},
+    {wgpu::FeatureName::TimestampQuery, wgpu::FeatureName::TimestampQueryInsidePasses},
+    {EncoderType::NonPass, EncoderType::ComputePass, EncoderType::RenderPass});
 
 }  // namespace dawn::native::d3d12