Move timestamp query inside compute/render passes to new feature

Feature::TimestampQuery is used for timestamp query in command encoder
and compute/render descriptor to match WebGPU SPEC.

Add a new feature timestamp-query-inside-passes for writeTimestamp API
on compute pass and render pass.

Split timestamp query tests in dawn_end2end_tests and dawn_unit_tests.

Bug: dawn:1193, dawn:1250
Change-Id: I8dd66c1d40939877e37ec2b979a573cc4812c21f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/106500
Reviewed-by: Austin Eng <enga@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Hao Li <hao.x.li@intel.com>
diff --git a/dawn.json b/dawn.json
index 554a95e..f58121f 100644
--- a/dawn.json
+++ b/dawn.json
@@ -1400,7 +1400,8 @@
             {"value": 1002, "name": "dawn internal usages", "tags": ["dawn"]},
             {"value": 1003, "name": "dawn multi planar formats", "tags": ["dawn"]},
             {"value": 1004, "name": "dawn native", "tags": ["dawn", "native"]},
-            {"value": 1005, "name": "chromium experimental dp4a", "tags": ["dawn"]}
+            {"value": 1005, "name": "chromium experimental dp4a", "tags": ["dawn"]},
+            {"value": 1006, "name": "timestamp query inside passes", "tags": ["dawn"]}
         ]
     },
     "filter mode": {
diff --git a/src/dawn/native/CommandValidation.cpp b/src/dawn/native/CommandValidation.cpp
index a3929f3..836c811 100644
--- a/src/dawn/native/CommandValidation.cpp
+++ b/src/dawn/native/CommandValidation.cpp
@@ -19,11 +19,13 @@
 #include <utility>
 
 #include "dawn/common/BitSetIterator.h"
+#include "dawn/native/Adapter.h"
 #include "dawn/native/BindGroup.h"
 #include "dawn/native/Buffer.h"
 #include "dawn/native/CommandBufferStateTracker.h"
 #include "dawn/native/Commands.h"
 #include "dawn/native/Device.h"
+#include "dawn/native/Instance.h"
 #include "dawn/native/PassResourceUsage.h"
 #include "dawn/native/QuerySet.h"
 #include "dawn/native/RenderBundle.h"
@@ -68,9 +70,17 @@
 
 MaybeError ValidateTimestampQuery(const DeviceBase* device,
                                   const QuerySetBase* querySet,
-                                  uint32_t queryIndex) {
+                                  uint32_t queryIndex,
+                                  Feature requiredFeature) {
     DAWN_TRY(device->ValidateObject(querySet));
 
+    DAWN_INVALID_IF(!device->HasFeature(requiredFeature),
+                    "Timestamp queries used without the %s feature enabled.",
+                    device->GetAdapter()
+                        ->GetInstance()
+                        ->GetFeatureInfo(FeatureEnumToAPIFeature(requiredFeature))
+                        ->name);
+
     DAWN_INVALID_IF(querySet->GetQueryType() != wgpu::QueryType::Timestamp,
                     "The type of %s is not %s.", querySet, wgpu::QueryType::Timestamp);
 
diff --git a/src/dawn/native/CommandValidation.h b/src/dawn/native/CommandValidation.h
index bf4e8aa..d4f66d7 100644
--- a/src/dawn/native/CommandValidation.h
+++ b/src/dawn/native/CommandValidation.h
@@ -19,6 +19,7 @@
 
 #include "dawn/native/CommandAllocator.h"
 #include "dawn/native/Error.h"
+#include "dawn/native/Features.h"
 #include "dawn/native/Texture.h"
 #include "dawn/native/UsageValidationMode.h"
 
@@ -32,7 +33,8 @@
 
 MaybeError ValidateTimestampQuery(const DeviceBase* device,
                                   const QuerySetBase* querySet,
-                                  uint32_t queryIndex);
+                                  uint32_t queryIndex,
+                                  Feature requiredFeature = Feature::TimestampQuery);
 
 MaybeError ValidateWriteBuffer(const DeviceBase* device,
                                const BufferBase* buffer,
diff --git a/src/dawn/native/ComputePassEncoder.cpp b/src/dawn/native/ComputePassEncoder.cpp
index 4c8d9ca..2b9a8ce 100644
--- a/src/dawn/native/ComputePassEncoder.cpp
+++ b/src/dawn/native/ComputePassEncoder.cpp
@@ -433,7 +433,8 @@
         this,
         [&](CommandAllocator* allocator) -> MaybeError {
             if (IsValidationEnabled()) {
-                DAWN_TRY(ValidateTimestampQuery(GetDevice(), querySet, queryIndex));
+                DAWN_TRY(ValidateTimestampQuery(GetDevice(), querySet, queryIndex,
+                                                Feature::TimestampQueryInsidePasses));
             }
 
             mCommandEncoder->TrackQueryAvailability(querySet, queryIndex);
diff --git a/src/dawn/native/Features.cpp b/src/dawn/native/Features.cpp
index c7d00b7..d839bd2 100644
--- a/src/dawn/native/Features.cpp
+++ b/src/dawn/native/Features.cpp
@@ -51,6 +51,10 @@
     {Feature::TimestampQuery,
      {"timestamp-query", "Support Timestamp Query",
       "https://bugs.chromium.org/p/dawn/issues/detail?id=434", FeatureInfo::FeatureState::Stable}},
+    {Feature::TimestampQueryInsidePasses,
+     {"timestamp-query-inside-passes", "Support Timestamp Query inside render/compute pass",
+      "https://bugs.chromium.org/p/dawn/issues/detail?id=434",
+      FeatureInfo::FeatureState::Experimental}},
     {Feature::DepthClipControl,
      {"depth-clip-control", "Disable depth clipping of primitives to the clip volume",
       "https://bugs.chromium.org/p/dawn/issues/detail?id=1178", FeatureInfo::FeatureState::Stable}},
@@ -100,6 +104,8 @@
 
         case wgpu::FeatureName::TimestampQuery:
             return Feature::TimestampQuery;
+        case wgpu::FeatureName::TimestampQueryInsidePasses:
+            return Feature::TimestampQueryInsidePasses;
         case wgpu::FeatureName::PipelineStatisticsQuery:
             return Feature::PipelineStatisticsQuery;
         case wgpu::FeatureName::TextureCompressionBC:
@@ -142,6 +148,8 @@
             return wgpu::FeatureName::PipelineStatisticsQuery;
         case Feature::TimestampQuery:
             return wgpu::FeatureName::TimestampQuery;
+        case Feature::TimestampQueryInsidePasses:
+            return wgpu::FeatureName::TimestampQueryInsidePasses;
         case Feature::DepthClipControl:
             return wgpu::FeatureName::DepthClipControl;
         case Feature::Depth32FloatStencil8:
diff --git a/src/dawn/native/Features.h b/src/dawn/native/Features.h
index 4bcb93a..2a35963 100644
--- a/src/dawn/native/Features.h
+++ b/src/dawn/native/Features.h
@@ -32,6 +32,7 @@
     TextureCompressionASTC,
     PipelineStatisticsQuery,
     TimestampQuery,
+    TimestampQueryInsidePasses,
     DepthClipControl,
     Depth32FloatStencil8,
     ChromiumExperimentalDp4a,
diff --git a/src/dawn/native/QuerySet.cpp b/src/dawn/native/QuerySet.cpp
index 2776ddd..0682495 100644
--- a/src/dawn/native/QuerySet.cpp
+++ b/src/dawn/native/QuerySet.cpp
@@ -82,7 +82,8 @@
                             "Timestamp queries are disallowed because they may expose precise "
                             "timing information.");
 
-            DAWN_INVALID_IF(!device->HasFeature(Feature::TimestampQuery),
+            DAWN_INVALID_IF(!device->HasFeature(Feature::TimestampQuery) &&
+                                !device->HasFeature(Feature::TimestampQueryInsidePasses),
                             "Timestamp query set created without the feature being enabled.");
 
             DAWN_INVALID_IF(descriptor->pipelineStatisticsCount != 0,
diff --git a/src/dawn/native/RenderPassEncoder.cpp b/src/dawn/native/RenderPassEncoder.cpp
index 6965f12..8967c91 100644
--- a/src/dawn/native/RenderPassEncoder.cpp
+++ b/src/dawn/native/RenderPassEncoder.cpp
@@ -400,7 +400,8 @@
         this,
         [&](CommandAllocator* allocator) -> MaybeError {
             if (IsValidationEnabled()) {
-                DAWN_TRY(ValidateTimestampQuery(GetDevice(), querySet, queryIndex));
+                DAWN_TRY(ValidateTimestampQuery(GetDevice(), querySet, queryIndex,
+                                                Feature::TimestampQueryInsidePasses));
                 DAWN_TRY_CONTEXT(ValidateQueryIndexOverwrite(
                                      querySet, queryIndex, mUsageTracker.GetQueryAvailabilityMap()),
                                  "validating the timestamp query index (%u) of %s", queryIndex,
diff --git a/src/dawn/native/d3d12/AdapterD3D12.cpp b/src/dawn/native/d3d12/AdapterD3D12.cpp
index 3f61a40..a5da117 100644
--- a/src/dawn/native/d3d12/AdapterD3D12.cpp
+++ b/src/dawn/native/d3d12/AdapterD3D12.cpp
@@ -131,6 +131,7 @@
 MaybeError Adapter::InitializeSupportedFeaturesImpl() {
     if (AreTimestampQueriesSupported()) {
         mSupportedFeatures.EnableFeature(Feature::TimestampQuery);
+        mSupportedFeatures.EnableFeature(Feature::TimestampQueryInsidePasses);
     }
     mSupportedFeatures.EnableFeature(Feature::TextureCompressionBC);
     mSupportedFeatures.EnableFeature(Feature::PipelineStatisticsQuery);
diff --git a/src/dawn/native/metal/BackendMTL.mm b/src/dawn/native/metal/BackendMTL.mm
index c2e75af..63321e0 100644
--- a/src/dawn/native/metal/BackendMTL.mm
+++ b/src/dawn/native/metal/BackendMTL.mm
@@ -339,6 +339,12 @@
             if (IsGPUCounterSupported(*mDevice, MTLCommonCounterSetTimestamp,
                                       {MTLCommonCounterTimestamp})) {
                 bool enableTimestampQuery = true;
+                bool enableTimestampQueryInsidePasses = true;
+
+                if (@available(macOS 11.0, iOS 14.0, *)) {
+                    enableTimestampQueryInsidePasses =
+                        SupportCounterSamplingAtCommandBoundary(*mDevice);
+                }
 
 #if DAWN_PLATFORM_IS(MACOS)
                 // Disable timestamp query on < macOS 11.0 on AMD GPU because WriteTimestamp
@@ -346,12 +352,17 @@
                 // has been fixed on macOS 11.0. See crbug.com/dawn/545.
                 if (gpu_info::IsAMD(mVendorId) && !IsMacOSVersionAtLeast(11)) {
                     enableTimestampQuery = false;
+                    enableTimestampQueryInsidePasses = false;
                 }
 #endif
 
                 if (enableTimestampQuery) {
                     mSupportedFeatures.EnableFeature(Feature::TimestampQuery);
                 }
+
+                if (enableTimestampQueryInsidePasses) {
+                    mSupportedFeatures.EnableFeature(Feature::TimestampQueryInsidePasses);
+                }
             }
         }
 
diff --git a/src/dawn/native/vulkan/AdapterVk.cpp b/src/dawn/native/vulkan/AdapterVk.cpp
index 3779f9b..b05f03e 100644
--- a/src/dawn/native/vulkan/AdapterVk.cpp
+++ b/src/dawn/native/vulkan/AdapterVk.cpp
@@ -152,6 +152,7 @@
     if (mDeviceInfo.properties.limits.timestampComputeAndGraphics == VK_TRUE &&
         !IsAndroidQualcomm()) {
         mSupportedFeatures.EnableFeature(Feature::TimestampQuery);
+        mSupportedFeatures.EnableFeature(Feature::TimestampQueryInsidePasses);
     }
 
     if (IsDepthStencilFormatSupported(VK_FORMAT_D32_SFLOAT_S8_UINT)) {
diff --git a/src/dawn/tests/end2end/QueryTests.cpp b/src/dawn/tests/end2end/QueryTests.cpp
index 6638875..f8cf53c 100644
--- a/src/dawn/tests/end2end/QueryTests.cpp
+++ b/src/dawn/tests/end2end/QueryTests.cpp
@@ -445,8 +445,6 @@
     }
 }
 
-DAWN_INSTANTIATE_TEST(OcclusionQueryTests, D3D12Backend(), MetalBackend(), VulkanBackend());
-
 class PipelineStatisticsQueryTests : public QueryTests {
   protected:
     void SetUp() override {
@@ -490,13 +488,6 @@
                                             wgpu::PipelineStatisticName::VertexShaderInvocations});
 }
 
-DAWN_INSTANTIATE_TEST(PipelineStatisticsQueryTests,
-                      D3D12Backend(),
-                      MetalBackend(),
-                      OpenGLBackend(),
-                      OpenGLESBackend(),
-                      VulkanBackend());
-
 class TimestampExpectation : public detail::Expectation {
   public:
     ~TimestampExpectation() override = default;
@@ -744,122 +735,6 @@
     }
 }
 
-// Test calling timestamp query from render pass encoder
-TEST_P(TimestampQueryTests, TimestampOnRenderPass) {
-    // TODO (dawn:1250): Split writeTimestamp() to another extension which is not supported on Apple
-    // devices
-    DAWN_TEST_UNSUPPORTED_IF(IsMacOS() && IsMetal() && IsApple());
-
-    constexpr uint32_t kQueryCount = 2;
-
-    // Write timestamp with different query indexes
-    {
-        wgpu::QuerySet querySet = CreateQuerySetForTimestamp(kQueryCount);
-        wgpu::Buffer destination = CreateResolveBuffer(kQueryCount * sizeof(uint64_t));
-
-        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
-        utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1);
-        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
-        pass.WriteTimestamp(querySet, 0);
-        pass.WriteTimestamp(querySet, 1);
-        pass.End();
-        encoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 0);
-        wgpu::CommandBuffer commands = encoder.Finish();
-        queue.Submit(1, &commands);
-
-        EXPECT_BUFFER(destination, 0, kQueryCount * sizeof(uint64_t), new TimestampExpectation);
-    }
-
-    // Write timestamp with same query index, not need test rewrite inside render pass due to it's
-    // not allowed
-    {
-        wgpu::QuerySet querySet = CreateQuerySetForTimestamp(kQueryCount);
-        wgpu::Buffer destination = CreateResolveBuffer(kQueryCount * sizeof(uint64_t));
-
-        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
-        encoder.WriteTimestamp(querySet, 0);
-        encoder.WriteTimestamp(querySet, 1);
-
-        utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1);
-        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
-        pass.WriteTimestamp(querySet, 0);
-        pass.WriteTimestamp(querySet, 1);
-        pass.End();
-        encoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 0);
-        wgpu::CommandBuffer commands = encoder.Finish();
-        queue.Submit(1, &commands);
-
-        EXPECT_BUFFER(destination, 0, kQueryCount * sizeof(uint64_t), new TimestampExpectation);
-    }
-}
-
-// Test calling timestamp query from compute pass encoder
-TEST_P(TimestampQueryTests, TimestampOnComputePass) {
-    // TODO (dawn:1250): Split writeTimestamp() to another extension which is not supported on Apple
-    // devices
-    DAWN_TEST_UNSUPPORTED_IF(IsMacOS() && IsMetal() && IsApple());
-
-    constexpr uint32_t kQueryCount = 2;
-
-    // Write timestamp with different query indexes
-    {
-        wgpu::QuerySet querySet = CreateQuerySetForTimestamp(kQueryCount);
-        wgpu::Buffer destination = CreateResolveBuffer(kQueryCount * sizeof(uint64_t));
-
-        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
-        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
-        pass.WriteTimestamp(querySet, 0);
-        pass.WriteTimestamp(querySet, 1);
-        pass.End();
-        encoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 0);
-        wgpu::CommandBuffer commands = encoder.Finish();
-        queue.Submit(1, &commands);
-
-        EXPECT_BUFFER(destination, 0, kQueryCount * sizeof(uint64_t), new TimestampExpectation);
-    }
-
-    // Write timestamp with same query index on both the outside and the inside of the compute pass
-    {
-        wgpu::QuerySet querySet = CreateQuerySetForTimestamp(kQueryCount);
-        wgpu::Buffer destination = CreateResolveBuffer(kQueryCount * sizeof(uint64_t));
-
-        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
-        encoder.WriteTimestamp(querySet, 0);
-        encoder.WriteTimestamp(querySet, 1);
-
-        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
-        pass.WriteTimestamp(querySet, 0);
-        pass.WriteTimestamp(querySet, 1);
-        pass.End();
-
-        encoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 0);
-        wgpu::CommandBuffer commands = encoder.Finish();
-        queue.Submit(1, &commands);
-
-        EXPECT_BUFFER(destination, 0, kQueryCount * sizeof(uint64_t), new TimestampExpectation);
-    }
-
-    // Write timestamp with same query index inside compute pass
-    {
-        wgpu::QuerySet querySet = CreateQuerySetForTimestamp(kQueryCount);
-        wgpu::Buffer destination = CreateResolveBuffer(kQueryCount * sizeof(uint64_t));
-
-        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
-        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
-        pass.WriteTimestamp(querySet, 0);
-        pass.WriteTimestamp(querySet, 1);
-        pass.WriteTimestamp(querySet, 0);
-        pass.WriteTimestamp(querySet, 1);
-        pass.End();
-
-        encoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 0);
-        wgpu::CommandBuffer commands = encoder.Finish();
-        queue.Submit(1, &commands);
-
-        EXPECT_BUFFER(destination, 0, kQueryCount * sizeof(uint64_t), new TimestampExpectation);
-    }
-}
-
 // Test timestampWrites with query set in compute pass descriptor
 TEST_P(TimestampQueryTests, TimestampWritesQuerySetOnComputePass) {
     // TODO(dawn:1489): Fails on Intel Windows Vulkan due to a driver issue that
@@ -1203,9 +1078,153 @@
     EXPECT_BUFFER(destination, 0, kQueryCount * sizeof(uint64_t), new TimestampExpectation);
 }
 
+class TimestampQueryInsidePassesTests : public TimestampQueryTests {
+  protected:
+    void SetUp() override {
+        DawnTest::SetUp();
+
+        // Skip all tests if timestamp feature is not supported
+        DAWN_TEST_UNSUPPORTED_IF(
+            !SupportsFeatures({wgpu::FeatureName::TimestampQueryInsidePasses}));
+    }
+
+    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
+        std::vector<wgpu::FeatureName> requiredFeatures = {};
+        if (SupportsFeatures({wgpu::FeatureName::TimestampQueryInsidePasses})) {
+            requiredFeatures.push_back(wgpu::FeatureName::TimestampQueryInsidePasses);
+            // The timestamp query feature must be supported if the timestamp query inside passes
+            // feature is supported. Enable timestamp query for testing queries overwrite inside and
+            // outside of the passes.
+            requiredFeatures.push_back(wgpu::FeatureName::TimestampQuery);
+        }
+        return requiredFeatures;
+    }
+};
+
+// Test calling timestamp query from render pass encoder
+TEST_P(TimestampQueryInsidePassesTests, FromOnRenderPass) {
+    constexpr uint32_t kQueryCount = 2;
+
+    // Write timestamp with different query indexes
+    {
+        wgpu::QuerySet querySet = CreateQuerySetForTimestamp(kQueryCount);
+        wgpu::Buffer destination = CreateResolveBuffer(kQueryCount * sizeof(uint64_t));
+
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1);
+        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
+        pass.WriteTimestamp(querySet, 0);
+        pass.WriteTimestamp(querySet, 1);
+        pass.End();
+        encoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 0);
+        wgpu::CommandBuffer commands = encoder.Finish();
+        queue.Submit(1, &commands);
+
+        EXPECT_BUFFER(destination, 0, kQueryCount * sizeof(uint64_t), new TimestampExpectation);
+    }
+
+    // Write timestamp with same query index, not need test rewrite inside render pass due to it's
+    // not allowed
+    {
+        wgpu::QuerySet querySet = CreateQuerySetForTimestamp(kQueryCount);
+        wgpu::Buffer destination = CreateResolveBuffer(kQueryCount * sizeof(uint64_t));
+
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        encoder.WriteTimestamp(querySet, 0);
+        encoder.WriteTimestamp(querySet, 1);
+
+        utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1);
+        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
+        pass.WriteTimestamp(querySet, 0);
+        pass.WriteTimestamp(querySet, 1);
+        pass.End();
+        encoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 0);
+        wgpu::CommandBuffer commands = encoder.Finish();
+        queue.Submit(1, &commands);
+
+        EXPECT_BUFFER(destination, 0, kQueryCount * sizeof(uint64_t), new TimestampExpectation);
+    }
+}
+
+// Test calling timestamp query from compute pass encoder
+TEST_P(TimestampQueryInsidePassesTests, FromComputePass) {
+    constexpr uint32_t kQueryCount = 2;
+
+    // Write timestamp with different query indexes
+    {
+        wgpu::QuerySet querySet = CreateQuerySetForTimestamp(kQueryCount);
+        wgpu::Buffer destination = CreateResolveBuffer(kQueryCount * sizeof(uint64_t));
+
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
+        pass.WriteTimestamp(querySet, 0);
+        pass.WriteTimestamp(querySet, 1);
+        pass.End();
+        encoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 0);
+        wgpu::CommandBuffer commands = encoder.Finish();
+        queue.Submit(1, &commands);
+
+        EXPECT_BUFFER(destination, 0, kQueryCount * sizeof(uint64_t), new TimestampExpectation);
+    }
+
+    // Write timestamp with same query index on both the outside and the inside of the compute pass
+    {
+        wgpu::QuerySet querySet = CreateQuerySetForTimestamp(kQueryCount);
+        wgpu::Buffer destination = CreateResolveBuffer(kQueryCount * sizeof(uint64_t));
+
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        encoder.WriteTimestamp(querySet, 0);
+        encoder.WriteTimestamp(querySet, 1);
+
+        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
+        pass.WriteTimestamp(querySet, 0);
+        pass.WriteTimestamp(querySet, 1);
+        pass.End();
+
+        encoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 0);
+        wgpu::CommandBuffer commands = encoder.Finish();
+        queue.Submit(1, &commands);
+
+        EXPECT_BUFFER(destination, 0, kQueryCount * sizeof(uint64_t), new TimestampExpectation);
+    }
+
+    // Write timestamp with same query index inside compute pass
+    {
+        wgpu::QuerySet querySet = CreateQuerySetForTimestamp(kQueryCount);
+        wgpu::Buffer destination = CreateResolveBuffer(kQueryCount * sizeof(uint64_t));
+
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
+        pass.WriteTimestamp(querySet, 0);
+        pass.WriteTimestamp(querySet, 1);
+        pass.WriteTimestamp(querySet, 0);
+        pass.WriteTimestamp(querySet, 1);
+        pass.End();
+
+        encoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 0);
+        wgpu::CommandBuffer commands = encoder.Finish();
+        queue.Submit(1, &commands);
+
+        EXPECT_BUFFER(destination, 0, kQueryCount * sizeof(uint64_t), new TimestampExpectation);
+    }
+}
+
+DAWN_INSTANTIATE_TEST(OcclusionQueryTests, D3D12Backend(), MetalBackend(), VulkanBackend());
+DAWN_INSTANTIATE_TEST(PipelineStatisticsQueryTests,
+                      D3D12Backend(),
+                      MetalBackend(),
+                      OpenGLBackend(),
+                      OpenGLESBackend(),
+                      VulkanBackend());
 DAWN_INSTANTIATE_TEST(TimestampQueryTests,
                       D3D12Backend(),
                       MetalBackend(),
                       OpenGLBackend(),
                       OpenGLESBackend(),
                       VulkanBackend());
+DAWN_INSTANTIATE_TEST(TimestampQueryInsidePassesTests,
+                      D3D12Backend(),
+                      MetalBackend(),
+                      OpenGLBackend(),
+                      OpenGLESBackend(),
+                      VulkanBackend());
diff --git a/src/dawn/tests/unittests/validation/QueryValidationTests.cpp b/src/dawn/tests/unittests/validation/QueryValidationTests.cpp
index 5088351..d0289e2 100644
--- a/src/dawn/tests/unittests/validation/QueryValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/QueryValidationTests.cpp
@@ -562,8 +562,30 @@
     }
 }
 
+class TimestampQueryInsidePassesValidationTest : public QuerySetValidationTest {
+  protected:
+    WGPUDevice CreateTestDevice(dawn::native::Adapter dawnAdapter) override {
+        wgpu::DeviceDescriptor descriptor;
+        // The timestamp query feature must be supported if the timestamp query inside passes
+        // feature is supported. Enable timestamp query for validating queries overwrite inside and
+        // outside of the passes.
+        wgpu::FeatureName requiredFeatures[2] = {wgpu::FeatureName::TimestampQuery,
+                                                 wgpu::FeatureName::TimestampQueryInsidePasses};
+        descriptor.requiredFeatures = requiredFeatures;
+        descriptor.requiredFeaturesCount = 2;
+
+        wgpu::DawnTogglesDeviceDescriptor togglesDesc;
+        descriptor.nextInChain = &togglesDesc;
+        const char* forceDisabledToggles[1] = {"disallow_unsafe_apis"};
+        togglesDesc.forceDisabledToggles = forceDisabledToggles;
+        togglesDesc.forceDisabledTogglesCount = 1;
+
+        return dawnAdapter.CreateDevice(&descriptor);
+    }
+};
+
 // Test write timestamp on compute pass encoder
-TEST_F(TimestampQueryValidationTest, WriteTimestampOnComputePassEncoder) {
+TEST_F(TimestampQueryInsidePassesValidationTest, WriteTimestampOnComputePassEncoder) {
     wgpu::QuerySet timestampQuerySet = CreateQuerySet(device, wgpu::QueryType::Timestamp, 2);
     wgpu::QuerySet occlusionQuerySet = CreateQuerySet(device, wgpu::QueryType::Occlusion, 2);
 
@@ -609,7 +631,7 @@
 }
 
 // Test write timestamp on render pass encoder
-TEST_F(TimestampQueryValidationTest, WriteTimestampOnRenderPassEncoder) {
+TEST_F(TimestampQueryInsidePassesValidationTest, WriteTimestampOnRenderPassEncoder) {
     PlaceholderRenderPass renderPass(device);
 
     wgpu::QuerySet timestampQuerySet = CreateQuerySet(device, wgpu::QueryType::Timestamp, 2);
diff --git a/src/dawn/wire/SupportedFeatures.cpp b/src/dawn/wire/SupportedFeatures.cpp
index 59ab14f..1003dcd 100644
--- a/src/dawn/wire/SupportedFeatures.cpp
+++ b/src/dawn/wire/SupportedFeatures.cpp
@@ -27,6 +27,7 @@
             return false;
         case WGPUFeatureName_Depth32FloatStencil8:
         case WGPUFeatureName_TimestampQuery:
+        case WGPUFeatureName_TimestampQueryInsidePasses:
         case WGPUFeatureName_PipelineStatisticsQuery:
         case WGPUFeatureName_TextureCompressionBC:
         case WGPUFeatureName_TextureCompressionETC2: