Query API: WriteTimestamp

Add WriteTimestamp API on CommandEncoder, ComputePassEncoder and
RenderPassEncoder.

Bug: dawn:434
Change-Id: Ifeca4efed01d80459d6fefa22ba05bea699b541f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/23244
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Hao Li <hao.x.li@intel.com>
diff --git a/dawn.json b/dawn.json
index 2575d18..bd4b5b2 100644
--- a/dawn.json
+++ b/dawn.json
@@ -391,6 +391,13 @@
                 "args": [
                     {"name": "group label", "type": "char", "annotation": "const*", "length": "strlen"}
                 ]
+            },
+            {
+                "name": "write timestamp",
+                "args": [
+                    {"name": "query set", "type": "query set"},
+                    {"name": "query index", "type": "uint32_t"}
+                ]
             }
         ]
     },
@@ -457,6 +464,13 @@
                 ]
             },
             {
+                "name": "write timestamp",
+                "args": [
+                    {"name": "query set", "type": "query set"},
+                    {"name": "query index", "type": "uint32_t"}
+                ]
+            },
+            {
                 "name": "dispatch",
                 "args": [
                     {"name": "x", "type": "uint32_t"},
@@ -1262,6 +1276,13 @@
                 ]
             },
             {
+                "name": "write timestamp",
+                "args": [
+                    {"name": "query set", "type": "query set"},
+                    {"name": "query index", "type": "uint32_t"}
+                ]
+            },
+            {
                 "name": "end pass"
             }
         ]
diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp
index b282dc8..23e2403 100644
--- a/src/dawn_native/CommandEncoder.cpp
+++ b/src/dawn_native/CommandEncoder.cpp
@@ -24,6 +24,7 @@
 #include "dawn_native/ComputePassEncoder.h"
 #include "dawn_native/Device.h"
 #include "dawn_native/ErrorData.h"
+#include "dawn_native/QuerySet.h"
 #include "dawn_native/RenderPassEncoder.h"
 #include "dawn_native/RenderPipeline.h"
 #include "dawn_native/ValidationUtils_autogen.h"
@@ -529,14 +530,18 @@
 
     CommandBufferResourceUsage CommandEncoder::AcquireResourceUsages() {
         return CommandBufferResourceUsage{mEncodingContext.AcquirePassUsages(),
-                                          std::move(mTopLevelBuffers),
-                                          std::move(mTopLevelTextures)};
+                                          std::move(mTopLevelBuffers), std::move(mTopLevelTextures),
+                                          std::move(mUsedQuerySets)};
     }
 
     CommandIterator CommandEncoder::AcquireCommands() {
         return mEncodingContext.AcquireCommands();
     }
 
+    void CommandEncoder::TrackUsedQuerySet(QuerySetBase* querySet) {
+        mUsedQuerySets.insert(querySet);
+    }
+
     // Implementation of the API's command recording methods
 
     ComputePassEncoder* CommandEncoder::BeginComputePass(const ComputePassDescriptor* descriptor) {
@@ -893,6 +898,23 @@
         });
     }
 
+    void CommandEncoder::WriteTimestamp(QuerySetBase* querySet, uint32_t queryIndex) {
+        mEncodingContext.TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            if (GetDevice()->IsValidationEnabled()) {
+                DAWN_TRY(GetDevice()->ValidateObject(querySet));
+                DAWN_TRY(ValidateTimestampQuery(querySet, queryIndex));
+                TrackUsedQuerySet(querySet);
+            }
+
+            WriteTimestampCmd* cmd =
+                allocator->Allocate<WriteTimestampCmd>(Command::WriteTimestamp);
+            cmd->querySet = querySet;
+            cmd->queryIndex = queryIndex;
+
+            return {};
+        });
+    }
+
     CommandBufferBase* CommandEncoder::Finish(const CommandBufferDescriptor* descriptor) {
         DeviceBase* device = GetDevice();
         // Even if mEncodingContext.Finish() validation fails, calling it will mutate the internal
@@ -976,6 +998,11 @@
                     debugGroupStackSize++;
                     break;
                 }
+
+                case Command::WriteTimestamp: {
+                    commands->NextCommand<WriteTimestampCmd>();
+                    break;
+                }
                 default:
                     return DAWN_VALIDATION_ERROR("Command disallowed outside of a pass");
             }
diff --git a/src/dawn_native/CommandEncoder.h b/src/dawn_native/CommandEncoder.h
index 2c89c4b..9caa1b2 100644
--- a/src/dawn_native/CommandEncoder.h
+++ b/src/dawn_native/CommandEncoder.h
@@ -35,6 +35,8 @@
         CommandIterator AcquireCommands();
         CommandBufferResourceUsage AcquireResourceUsages();
 
+        void TrackUsedQuerySet(QuerySetBase* querySet);
+
         // Dawn API
         ComputePassEncoder* BeginComputePass(const ComputePassDescriptor* descriptor);
         RenderPassEncoder* BeginRenderPass(const RenderPassDescriptor* descriptor);
@@ -58,6 +60,8 @@
         void PopDebugGroup();
         void PushDebugGroup(const char* groupLabel);
 
+        void WriteTimestamp(QuerySetBase* querySet, uint32_t queryIndex);
+
         CommandBufferBase* Finish(const CommandBufferDescriptor* descriptor);
 
       private:
@@ -67,6 +71,7 @@
         EncodingContext mEncodingContext;
         std::set<BufferBase*> mTopLevelBuffers;
         std::set<TextureBase*> mTopLevelTextures;
+        std::set<QuerySetBase*> mUsedQuerySets;
     };
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/CommandValidation.cpp b/src/dawn_native/CommandValidation.cpp
index 7f8da9b..0a96c85 100644
--- a/src/dawn_native/CommandValidation.cpp
+++ b/src/dawn_native/CommandValidation.cpp
@@ -20,6 +20,7 @@
 #include "dawn_native/CommandBufferStateTracker.h"
 #include "dawn_native/Commands.h"
 #include "dawn_native/PassResourceUsage.h"
+#include "dawn_native/QuerySet.h"
 #include "dawn_native/RenderBundle.h"
 #include "dawn_native/RenderPipeline.h"
 
@@ -202,6 +203,11 @@
                     break;
                 }
 
+                case Command::WriteTimestamp: {
+                    commands->NextCommand<WriteTimestampCmd>();
+                    break;
+                }
+
                 default:
                     DAWN_TRY(ValidateRenderBundleCommand(
                         commands, type, &commandBufferState, renderPass->attachmentState.Get(),
@@ -274,6 +280,11 @@
                     break;
                 }
 
+                case Command::WriteTimestamp: {
+                    commands->NextCommand<WriteTimestampCmd>();
+                    break;
+                }
+
                 default:
                     return DAWN_VALIDATION_ERROR("Command disallowed inside a compute pass");
             }
@@ -339,6 +350,18 @@
         return {};
     }
 
+    MaybeError ValidateTimestampQuery(QuerySetBase* querySet, uint32_t queryIndex) {
+        if (querySet->GetQueryType() != wgpu::QueryType::Timestamp) {
+            return DAWN_VALIDATION_ERROR("The query type of query set must be Timestamp");
+        }
+
+        if (queryIndex >= querySet->GetQueryCount()) {
+            return DAWN_VALIDATION_ERROR("Query index exceeds the number of queries in query set");
+        }
+
+        return {};
+    }
+
     bool IsRangeOverlapped(uint32_t startA, uint32_t startB, uint32_t length) {
         uint32_t maxStart = std::max(startA, startB);
         uint32_t minStart = std::min(startA, startB);
diff --git a/src/dawn_native/CommandValidation.h b/src/dawn_native/CommandValidation.h
index 53871cc..49da94d 100644
--- a/src/dawn_native/CommandValidation.h
+++ b/src/dawn_native/CommandValidation.h
@@ -23,6 +23,7 @@
 namespace dawn_native {
 
     class AttachmentState;
+    class QuerySetBase;
     struct BeginRenderPassCmd;
     struct PassResourceUsage;
 
@@ -36,6 +37,8 @@
 
     MaybeError ValidatePassResourceUsage(const PassResourceUsage& usage);
 
+    MaybeError ValidateTimestampQuery(QuerySetBase* querySet, uint32_t queryIndex);
+
     bool IsRangeOverlapped(uint32_t startA, uint32_t startB, uint32_t length);
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/Commands.cpp b/src/dawn_native/Commands.cpp
index b409810..7da04ad 100644
--- a/src/dawn_native/Commands.cpp
+++ b/src/dawn_native/Commands.cpp
@@ -18,6 +18,7 @@
 #include "dawn_native/Buffer.h"
 #include "dawn_native/CommandAllocator.h"
 #include "dawn_native/ComputePipeline.h"
+#include "dawn_native/QuerySet.h"
 #include "dawn_native/RenderBundle.h"
 #include "dawn_native/RenderPipeline.h"
 #include "dawn_native/Texture.h"
@@ -175,6 +176,11 @@
                     cmd->~SetVertexBufferCmd();
                     break;
                 }
+                case Command::WriteTimestamp: {
+                    WriteTimestampCmd* cmd = commands->NextCommand<WriteTimestampCmd>();
+                    cmd->~WriteTimestampCmd();
+                    break;
+                }
             }
         }
         commands->DataWasDestroyed();
@@ -300,6 +306,11 @@
                 commands->NextCommand<SetVertexBufferCmd>();
                 break;
             }
+
+            case Command::WriteTimestamp: {
+                commands->NextCommand<WriteTimestampCmd>();
+                break;
+            }
         }
     }
 
diff --git a/src/dawn_native/Commands.h b/src/dawn_native/Commands.h
index 0c2c8f0..70214ff 100644
--- a/src/dawn_native/Commands.h
+++ b/src/dawn_native/Commands.h
@@ -60,6 +60,7 @@
         SetBindGroup,
         SetIndexBuffer,
         SetVertexBuffer,
+        WriteTimestamp,
     };
 
     struct BeginComputePassCmd {};
@@ -228,6 +229,11 @@
         uint64_t size;
     };
 
+    struct WriteTimestampCmd {
+        Ref<QuerySetBase> querySet;
+        uint32_t queryIndex;
+    };
+
     // This needs to be called before the CommandIterator is freed so that the Ref<> present in
     // the commands have a chance to run their destructor and remove internal references.
     class CommandIterator;
diff --git a/src/dawn_native/ComputePassEncoder.cpp b/src/dawn_native/ComputePassEncoder.cpp
index 5e36601..2329c00 100644
--- a/src/dawn_native/ComputePassEncoder.cpp
+++ b/src/dawn_native/ComputePassEncoder.cpp
@@ -16,9 +16,11 @@
 
 #include "dawn_native/Buffer.h"
 #include "dawn_native/CommandEncoder.h"
+#include "dawn_native/CommandValidation.h"
 #include "dawn_native/Commands.h"
 #include "dawn_native/ComputePipeline.h"
 #include "dawn_native/Device.h"
+#include "dawn_native/QuerySet.h"
 
 namespace dawn_native {
 
@@ -96,4 +98,21 @@
         });
     }
 
+    void ComputePassEncoder::WriteTimestamp(QuerySetBase* querySet, uint32_t queryIndex) {
+        mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            if (GetDevice()->IsValidationEnabled()) {
+                DAWN_TRY(GetDevice()->ValidateObject(querySet));
+                DAWN_TRY(ValidateTimestampQuery(querySet, queryIndex));
+                mCommandEncoder->TrackUsedQuerySet(querySet);
+            }
+
+            WriteTimestampCmd* cmd =
+                allocator->Allocate<WriteTimestampCmd>(Command::WriteTimestamp);
+            cmd->querySet = querySet;
+            cmd->queryIndex = queryIndex;
+
+            return {};
+        });
+    }
+
 }  // namespace dawn_native
diff --git a/src/dawn_native/ComputePassEncoder.h b/src/dawn_native/ComputePassEncoder.h
index f790aad..6ae796a 100644
--- a/src/dawn_native/ComputePassEncoder.h
+++ b/src/dawn_native/ComputePassEncoder.h
@@ -36,6 +36,8 @@
         void DispatchIndirect(BufferBase* indirectBuffer, uint64_t indirectOffset);
         void SetPipeline(ComputePipelineBase* pipeline);
 
+        void WriteTimestamp(QuerySetBase* querySet, uint32_t queryIndex);
+
       protected:
         ComputePassEncoder(DeviceBase* device,
                            CommandEncoder* commandEncoder,
diff --git a/src/dawn_native/PassResourceUsage.h b/src/dawn_native/PassResourceUsage.h
index e7b0c1b..60dcd21 100644
--- a/src/dawn_native/PassResourceUsage.h
+++ b/src/dawn_native/PassResourceUsage.h
@@ -23,6 +23,7 @@
 namespace dawn_native {
 
     class BufferBase;
+    class QuerySetBase;
     class TextureBase;
 
     enum class PassType { Render, Compute };
@@ -68,6 +69,7 @@
         PerPassUsages perPass;
         std::set<BufferBase*> topLevelBuffers;
         std::set<TextureBase*> topLevelTextures;
+        std::set<QuerySetBase*> usedQuerySets;
     };
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/QuerySet.cpp b/src/dawn_native/QuerySet.cpp
index 513658d..3eb18ff 100644
--- a/src/dawn_native/QuerySet.cpp
+++ b/src/dawn_native/QuerySet.cpp
@@ -131,6 +131,14 @@
         return mPipelineStatistics;
     }
 
+    MaybeError QuerySetBase::ValidateCanUseInSubmitNow() const {
+        ASSERT(!IsError());
+        if (mState == QuerySetState::Destroyed) {
+            return DAWN_VALIDATION_ERROR("Destroyed query set used in a submit");
+        }
+        return {};
+    }
+
     void QuerySetBase::Destroy() {
         if (GetDevice()->ConsumedError(ValidateDestroy())) {
             return;
diff --git a/src/dawn_native/QuerySet.h b/src/dawn_native/QuerySet.h
index 7883678..a069887 100644
--- a/src/dawn_native/QuerySet.h
+++ b/src/dawn_native/QuerySet.h
@@ -35,6 +35,8 @@
         uint32_t GetQueryCount() const;
         const std::vector<wgpu::PipelineStatisticsName>& GetPipelineStatistics() const;
 
+        MaybeError ValidateCanUseInSubmitNow() const;
+
         void Destroy();
 
       protected:
diff --git a/src/dawn_native/Queue.cpp b/src/dawn_native/Queue.cpp
index 3dcf2b0..19ba51e 100644
--- a/src/dawn_native/Queue.cpp
+++ b/src/dawn_native/Queue.cpp
@@ -22,6 +22,7 @@
 #include "dawn_native/ErrorScopeTracker.h"
 #include "dawn_native/Fence.h"
 #include "dawn_native/FenceSignalTracker.h"
+#include "dawn_native/QuerySet.h"
 #include "dawn_native/Texture.h"
 #include "dawn_platform/DawnPlatform.h"
 #include "dawn_platform/tracing/TraceEvent.h"
@@ -155,6 +156,9 @@
             for (const TextureBase* texture : usages.topLevelTextures) {
                 DAWN_TRY(texture->ValidateCanUseInSubmitNow());
             }
+            for (const QuerySetBase* querySet : usages.usedQuerySets) {
+                DAWN_TRY(querySet->ValidateCanUseInSubmitNow());
+            }
         }
 
         return {};
diff --git a/src/dawn_native/RenderPassEncoder.cpp b/src/dawn_native/RenderPassEncoder.cpp
index e2dc4db..e51b52f 100644
--- a/src/dawn_native/RenderPassEncoder.cpp
+++ b/src/dawn_native/RenderPassEncoder.cpp
@@ -17,8 +17,10 @@
 #include "common/Constants.h"
 #include "dawn_native/Buffer.h"
 #include "dawn_native/CommandEncoder.h"
+#include "dawn_native/CommandValidation.h"
 #include "dawn_native/Commands.h"
 #include "dawn_native/Device.h"
+#include "dawn_native/QuerySet.h"
 #include "dawn_native/RenderBundle.h"
 #include "dawn_native/RenderPipeline.h"
 
@@ -163,4 +165,21 @@
         });
     }
 
+    void RenderPassEncoder::WriteTimestamp(QuerySetBase* querySet, uint32_t queryIndex) {
+        mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            if (GetDevice()->IsValidationEnabled()) {
+                DAWN_TRY(GetDevice()->ValidateObject(querySet));
+                DAWN_TRY(ValidateTimestampQuery(querySet, queryIndex));
+                mCommandEncoder->TrackUsedQuerySet(querySet);
+            }
+
+            WriteTimestampCmd* cmd =
+                allocator->Allocate<WriteTimestampCmd>(Command::WriteTimestamp);
+            cmd->querySet = querySet;
+            cmd->queryIndex = queryIndex;
+
+            return {};
+        });
+    }
+
 }  // namespace dawn_native
diff --git a/src/dawn_native/RenderPassEncoder.h b/src/dawn_native/RenderPassEncoder.h
index cd9ac01..d5a2f7a 100644
--- a/src/dawn_native/RenderPassEncoder.h
+++ b/src/dawn_native/RenderPassEncoder.h
@@ -46,6 +46,8 @@
         void SetScissorRect(uint32_t x, uint32_t y, uint32_t width, uint32_t height);
         void ExecuteBundles(uint32_t count, RenderBundleBase* const* renderBundles);
 
+        void WriteTimestamp(QuerySetBase* querySet, uint32_t queryIndex);
+
       protected:
         RenderPassEncoder(DeviceBase* device,
                           CommandEncoder* commandEncoder,
diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
index 1b4f8cc..98fca16 100644
--- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
@@ -754,6 +754,10 @@
                     break;
                 }
 
+                case Command::WriteTimestamp: {
+                    return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation.");
+                }
+
                 default: {
                     UNREACHABLE();
                     break;
@@ -864,6 +868,10 @@
                     break;
                 }
 
+                case Command::WriteTimestamp: {
+                    return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation.");
+                }
+
                 default: {
                     UNREACHABLE();
                     break;
@@ -1280,6 +1288,10 @@
                     break;
                 }
 
+                case Command::WriteTimestamp: {
+                    return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation.");
+                }
+
                 default: {
                     DAWN_TRY(EncodeRenderBundleCommand(&mCommands, type));
                     break;
diff --git a/src/dawn_native/metal/CommandBufferMTL.h b/src/dawn_native/metal/CommandBufferMTL.h
index e8c81b7..cbaf037 100644
--- a/src/dawn_native/metal/CommandBufferMTL.h
+++ b/src/dawn_native/metal/CommandBufferMTL.h
@@ -17,6 +17,7 @@
 
 #include "dawn_native/CommandAllocator.h"
 #include "dawn_native/CommandBuffer.h"
+#include "dawn_native/Error.h"
 
 #import <Metal/Metal.h>
 
@@ -33,20 +34,20 @@
       public:
         CommandBuffer(CommandEncoder* encoder, const CommandBufferDescriptor* descriptor);
 
-        void FillCommands(CommandRecordingContext* commandContext);
+        MaybeError FillCommands(CommandRecordingContext* commandContext);
 
       private:
         ~CommandBuffer() override;
-        void EncodeComputePass(CommandRecordingContext* commandContext);
-        void EncodeRenderPass(CommandRecordingContext* commandContext,
-                              MTLRenderPassDescriptor* mtlRenderPass,
-                              uint32_t width,
-                              uint32_t height);
+        MaybeError EncodeComputePass(CommandRecordingContext* commandContext);
+        MaybeError EncodeRenderPass(CommandRecordingContext* commandContext,
+                                    MTLRenderPassDescriptor* mtlRenderPass,
+                                    uint32_t width,
+                                    uint32_t height);
 
-        void EncodeRenderPassInternal(CommandRecordingContext* commandContext,
-                                      MTLRenderPassDescriptor* mtlRenderPass,
-                                      uint32_t width,
-                                      uint32_t height);
+        MaybeError EncodeRenderPassInternal(CommandRecordingContext* commandContext,
+                                            MTLRenderPassDescriptor* mtlRenderPass,
+                                            uint32_t width,
+                                            uint32_t height);
 
         CommandIterator mCommands;
     };
diff --git a/src/dawn_native/metal/CommandBufferMTL.mm b/src/dawn_native/metal/CommandBufferMTL.mm
index bd2b8f3..4efc7a6 100644
--- a/src/dawn_native/metal/CommandBufferMTL.mm
+++ b/src/dawn_native/metal/CommandBufferMTL.mm
@@ -673,7 +673,7 @@
         FreeCommands(&mCommands);
     }
 
-    void CommandBuffer::FillCommands(CommandRecordingContext* commandContext) {
+    MaybeError CommandBuffer::FillCommands(CommandRecordingContext* commandContext) {
         const std::vector<PassResourceUsage>& passResourceUsages = GetResourceUsages().perPass;
         size_t nextPassNumber = 0;
 
@@ -698,7 +698,7 @@
                     LazyClearForPass(passResourceUsages[nextPassNumber]);
                     commandContext->EndBlit();
 
-                    EncodeComputePass(commandContext);
+                    DAWN_TRY(EncodeComputePass(commandContext));
 
                     nextPassNumber++;
                     break;
@@ -712,7 +712,7 @@
 
                     LazyClearRenderPassAttachments(cmd);
                     MTLRenderPassDescriptor* descriptor = CreateMTLRenderPassDescriptor(cmd);
-                    EncodeRenderPass(commandContext, descriptor, cmd->width, cmd->height);
+                    DAWN_TRY(EncodeRenderPass(commandContext, descriptor, cmd->width, cmd->height));
 
                     nextPassNumber++;
                     break;
@@ -859,6 +859,10 @@
                     break;
                 }
 
+                case Command::WriteTimestamp: {
+                    return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation.");
+                }
+
                 default: {
                     UNREACHABLE();
                     break;
@@ -867,9 +871,10 @@
         }
 
         commandContext->EndBlit();
+        return {};
     }
 
-    void CommandBuffer::EncodeComputePass(CommandRecordingContext* commandContext) {
+    MaybeError CommandBuffer::EncodeComputePass(CommandRecordingContext* commandContext) {
         ComputePipeline* lastPipeline = nullptr;
         StorageBufferLengthTracker storageBufferLengths = {};
         BindGroupTracker bindGroups(&storageBufferLengths);
@@ -882,7 +887,7 @@
                 case Command::EndComputePass: {
                     mCommands.NextCommand<EndComputePassCmd>();
                     commandContext->EndCompute();
-                    return;
+                    return {};
                 }
 
                 case Command::Dispatch: {
@@ -960,6 +965,10 @@
                     break;
                 }
 
+                case Command::WriteTimestamp: {
+                    return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation.");
+                }
+
                 default: {
                     UNREACHABLE();
                     break;
@@ -971,10 +980,10 @@
         UNREACHABLE();
     }
 
-    void CommandBuffer::EncodeRenderPass(CommandRecordingContext* commandContext,
-                                         MTLRenderPassDescriptor* mtlRenderPass,
-                                         uint32_t width,
-                                         uint32_t height) {
+    MaybeError CommandBuffer::EncodeRenderPass(CommandRecordingContext* commandContext,
+                                               MTLRenderPassDescriptor* mtlRenderPass,
+                                               uint32_t width,
+                                               uint32_t height) {
         ASSERT(mtlRenderPass);
 
         Device* device = ToBackend(GetDevice());
@@ -1018,7 +1027,7 @@
             // If we need to use a temporary resolve texture we need to copy the result of MSAA
             // resolve back to the true resolve targets.
             if (useTemporaryResolveTexture) {
-                EncodeRenderPass(commandContext, mtlRenderPass, width, height);
+                DAWN_TRY(EncodeRenderPass(commandContext, mtlRenderPass, width, height));
                 for (uint32_t i = 0; i < kMaxColorAttachments; ++i) {
                     if (trueResolveTextures[i] == nil) {
                         continue;
@@ -1031,7 +1040,7 @@
                     [temporaryResolveTextures[i] release];
                     temporaryResolveTextures[i] = nil;
                 }
-                return;
+                return {};
             }
         }
 
@@ -1054,19 +1063,20 @@
 
             // If we found a store + MSAA resolve we need to resolve in a different render pass.
             if (hasStoreAndMSAAResolve) {
-                EncodeRenderPass(commandContext, mtlRenderPass, width, height);
+                DAWN_TRY(EncodeRenderPass(commandContext, mtlRenderPass, width, height));
                 ResolveInAnotherRenderPass(commandContext, mtlRenderPass, resolveTextures);
-                return;
+                return {};
             }
         }
 
-        EncodeRenderPassInternal(commandContext, mtlRenderPass, width, height);
+        DAWN_TRY(EncodeRenderPassInternal(commandContext, mtlRenderPass, width, height));
+        return {};
     }
 
-    void CommandBuffer::EncodeRenderPassInternal(CommandRecordingContext* commandContext,
-                                                 MTLRenderPassDescriptor* mtlRenderPass,
-                                                 uint32_t width,
-                                                 uint32_t height) {
+    MaybeError CommandBuffer::EncodeRenderPassInternal(CommandRecordingContext* commandContext,
+                                                       MTLRenderPassDescriptor* mtlRenderPass,
+                                                       uint32_t width,
+                                                       uint32_t height) {
         RenderPipeline* lastPipeline = nullptr;
         id<MTLBuffer> indexBuffer = nil;
         uint32_t indexBufferBaseOffset = 0;
@@ -1256,7 +1266,7 @@
                 case Command::EndRenderPass: {
                     mCommands.NextCommand<EndRenderPassCmd>();
                     commandContext->EndRender();
-                    return;
+                    return {};
                 }
 
                 case Command::SetStencilReference: {
@@ -1323,6 +1333,10 @@
                     break;
                 }
 
+                case Command::WriteTimestamp: {
+                    return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation.");
+                }
+
                 default: {
                     EncodeRenderBundleCommand(&mCommands, type);
                     break;
diff --git a/src/dawn_native/metal/QueueMTL.mm b/src/dawn_native/metal/QueueMTL.mm
index 7c5967a..ffe6ca1 100644
--- a/src/dawn_native/metal/QueueMTL.mm
+++ b/src/dawn_native/metal/QueueMTL.mm
@@ -31,7 +31,7 @@
 
         TRACE_EVENT_BEGIN0(GetDevice()->GetPlatform(), Recording, "CommandBufferMTL::FillCommands");
         for (uint32_t i = 0; i < commandCount; ++i) {
-            ToBackend(commands[i])->FillCommands(commandContext);
+            DAWN_TRY(ToBackend(commands[i])->FillCommands(commandContext));
         }
         TRACE_EVENT_END0(GetDevice()->GetPlatform(), Recording, "CommandBufferMTL::FillCommands");
 
diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp
index f3fcd5c..bba5da0 100644
--- a/src/dawn_native/opengl/CommandBufferGL.cpp
+++ b/src/dawn_native/opengl/CommandBufferGL.cpp
@@ -712,6 +712,12 @@
                     break;
                 }
 
+                case Command::WriteTimestamp: {
+                    // WriteTimestamp is not supported on OpenGL
+                    UNREACHABLE();
+                    break;
+                }
+
                 default: {
                     UNREACHABLE();
                     break;
@@ -786,6 +792,12 @@
                     break;
                 }
 
+                case Command::WriteTimestamp: {
+                    // WriteTimestamp is not supported on OpenGL
+                    UNREACHABLE();
+                    break;
+                }
+
                 default: {
                     UNREACHABLE();
                     break;
@@ -1137,6 +1149,12 @@
                     break;
                 }
 
+                case Command::WriteTimestamp: {
+                    // WriteTimestamp is not supported on OpenGL
+                    UNREACHABLE();
+                    break;
+                }
+
                 default: {
                     DoRenderBundleCommand(&mCommands, type);
                     break;
diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp
index ae0e792..3a2bec9 100644
--- a/src/dawn_native/vulkan/CommandBufferVk.cpp
+++ b/src/dawn_native/vulkan/CommandBufferVk.cpp
@@ -589,12 +589,16 @@
                     mCommands.NextCommand<BeginComputePassCmd>();
 
                     TransitionForPass(device, recordingContext, passResourceUsages[nextPassNumber]);
-                    RecordComputePass(recordingContext);
+                    DAWN_TRY(RecordComputePass(recordingContext));
 
                     nextPassNumber++;
                     break;
                 }
 
+                case Command::WriteTimestamp: {
+                    return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation.");
+                }
+
                 default: {
                     UNREACHABLE();
                     break;
@@ -605,7 +609,7 @@
         return {};
     }
 
-    void CommandBuffer::RecordComputePass(CommandRecordingContext* recordingContext) {
+    MaybeError CommandBuffer::RecordComputePass(CommandRecordingContext* recordingContext) {
         Device* device = ToBackend(GetDevice());
         VkCommandBuffer commands = recordingContext->commandBuffer;
 
@@ -616,7 +620,7 @@
             switch (type) {
                 case Command::EndComputePass: {
                     mCommands.NextCommand<EndComputePassCmd>();
-                    return;
+                    return {};
                 }
 
                 case Command::Dispatch: {
@@ -712,6 +716,10 @@
                     break;
                 }
 
+                case Command::WriteTimestamp: {
+                    return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation.");
+                }
+
                 default: {
                     UNREACHABLE();
                     break;
@@ -981,6 +989,10 @@
                     break;
                 }
 
+                case Command::WriteTimestamp: {
+                    return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation.");
+                }
+
                 default: {
                     EncodeRenderBundleCommand(&mCommands, type);
                     break;
diff --git a/src/dawn_native/vulkan/CommandBufferVk.h b/src/dawn_native/vulkan/CommandBufferVk.h
index c60fa92..fa2dd4b 100644
--- a/src/dawn_native/vulkan/CommandBufferVk.h
+++ b/src/dawn_native/vulkan/CommandBufferVk.h
@@ -42,7 +42,7 @@
         CommandBuffer(CommandEncoder* encoder, const CommandBufferDescriptor* descriptor);
         ~CommandBuffer() override;
 
-        void RecordComputePass(CommandRecordingContext* recordingContext);
+        MaybeError RecordComputePass(CommandRecordingContext* recordingContext);
         MaybeError RecordRenderPass(CommandRecordingContext* recordingContext,
                                     BeginRenderPassCmd* renderPass);
         void RecordCopyImageWithTemporaryBuffer(CommandRecordingContext* recordingContext,
diff --git a/src/tests/unittests/validation/QuerySetValidationTests.cpp b/src/tests/unittests/validation/QuerySetValidationTests.cpp
index 311bc66..3039718 100644
--- a/src/tests/unittests/validation/QuerySetValidationTests.cpp
+++ b/src/tests/unittests/validation/QuerySetValidationTests.cpp
@@ -27,10 +27,11 @@
         deviceWithTimestamp = CreateDeviceFromAdapter(adapter, {"timestamp_query"});
     }
 
-    void CreateQuerySet(wgpu::Device cDevice,
-                        wgpu::QueryType queryType,
-                        uint32_t queryCount,
-                        std::vector<wgpu::PipelineStatisticsName> pipelineStatistics = {}) {
+    wgpu::QuerySet CreateQuerySet(
+        wgpu::Device cDevice,
+        wgpu::QueryType queryType,
+        uint32_t queryCount,
+        std::vector<wgpu::PipelineStatisticsName> pipelineStatistics = {}) {
         wgpu::QuerySetDescriptor descriptor;
         descriptor.type = queryType;
         descriptor.count = queryCount;
@@ -40,7 +41,7 @@
             descriptor.pipelineStatisticsCount = pipelineStatistics.size();
         }
 
-        cDevice.CreateQuerySet(&descriptor);
+        return cDevice.CreateQuerySet(&descriptor);
     }
 
     wgpu::Device deviceWithPipelineStatistics;
@@ -148,3 +149,171 @@
     querySet.Destroy();
     querySet.Destroy();
 }
+
+class TimestampQueryValidationTest : public QuerySetValidationTest {};
+
+// Test write timestamp on command encoder
+TEST_F(TimestampQueryValidationTest, WriteTimestampOnCommandEncoder) {
+    wgpu::QuerySet timestampQuerySet =
+        CreateQuerySet(deviceWithTimestamp, wgpu::QueryType::Timestamp, 2);
+    wgpu::QuerySet occlusionQuerySet =
+        CreateQuerySet(deviceWithTimestamp, wgpu::QueryType::Occlusion, 2);
+
+    // Success on command encoder
+    {
+        wgpu::CommandEncoder encoder = deviceWithTimestamp.CreateCommandEncoder();
+        encoder.WriteTimestamp(timestampQuerySet, 0);
+        encoder.Finish();
+    }
+
+    // Not allow to write timestamp from another device
+    {
+        // Write timestamp from default device
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        encoder.WriteTimestamp(timestampQuerySet, 0);
+        ASSERT_DEVICE_ERROR(encoder.Finish());
+    }
+
+    // Not allow to write timestamp to the query set with other query type
+    {
+        wgpu::CommandEncoder encoder = deviceWithTimestamp.CreateCommandEncoder();
+        encoder.WriteTimestamp(occlusionQuerySet, 0);
+        ASSERT_DEVICE_ERROR(encoder.Finish());
+    }
+
+    // Fail to write timestamp to the index which exceeds the number of queries in query set
+    {
+        wgpu::CommandEncoder encoder = deviceWithTimestamp.CreateCommandEncoder();
+        encoder.WriteTimestamp(timestampQuerySet, 2);
+        ASSERT_DEVICE_ERROR(encoder.Finish());
+    }
+
+    // Fail to submit timestamp query with a destroyed query set
+    {
+        wgpu::CommandEncoder encoder = deviceWithTimestamp.CreateCommandEncoder();
+        encoder.WriteTimestamp(timestampQuerySet, 0);
+        wgpu::CommandBuffer commands = encoder.Finish();
+
+        wgpu::Queue queue = deviceWithTimestamp.GetDefaultQueue();
+        timestampQuerySet.Destroy();
+        ASSERT_DEVICE_ERROR(queue.Submit(1, &commands));
+    }
+}
+
+// Test write timestamp on compute pass encoder
+TEST_F(TimestampQueryValidationTest, WriteTimestampOnComputePassEncoder) {
+    wgpu::QuerySet timestampQuerySet =
+        CreateQuerySet(deviceWithTimestamp, wgpu::QueryType::Timestamp, 2);
+    wgpu::QuerySet occlusionQuerySet =
+        CreateQuerySet(deviceWithTimestamp, wgpu::QueryType::Occlusion, 2);
+
+    // Success on compute pass encoder
+    {
+        wgpu::CommandEncoder encoder = deviceWithTimestamp.CreateCommandEncoder();
+        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
+        pass.WriteTimestamp(timestampQuerySet, 0);
+        pass.EndPass();
+        encoder.Finish();
+    }
+
+    // Not allow to write timestamp from another device
+    {
+        // Write timestamp from default device
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
+        pass.WriteTimestamp(timestampQuerySet, 0);
+        pass.EndPass();
+        ASSERT_DEVICE_ERROR(encoder.Finish());
+    }
+
+    // Not allow to write timestamp to the query set with other query type
+    {
+        wgpu::CommandEncoder encoder = deviceWithTimestamp.CreateCommandEncoder();
+        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
+        pass.WriteTimestamp(occlusionQuerySet, 0);
+        pass.EndPass();
+        ASSERT_DEVICE_ERROR(encoder.Finish());
+    }
+
+    // Fail to write timestamp to the index which exceeds the number of queries in query set
+    {
+        wgpu::CommandEncoder encoder = deviceWithTimestamp.CreateCommandEncoder();
+        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
+        pass.WriteTimestamp(timestampQuerySet, 2);
+        pass.EndPass();
+        ASSERT_DEVICE_ERROR(encoder.Finish());
+    }
+
+    // Fail to submit timestamp query with a destroyed query set
+    {
+        wgpu::CommandEncoder encoder = deviceWithTimestamp.CreateCommandEncoder();
+        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
+        pass.WriteTimestamp(timestampQuerySet, 0);
+        pass.EndPass();
+        wgpu::CommandBuffer commands = encoder.Finish();
+
+        wgpu::Queue queue = deviceWithTimestamp.GetDefaultQueue();
+        timestampQuerySet.Destroy();
+        ASSERT_DEVICE_ERROR(queue.Submit(1, &commands));
+    }
+}
+
+// Test write timestamp on render pass encoder
+TEST_F(TimestampQueryValidationTest, WriteTimestampOnRenderPassEncoder) {
+    DummyRenderPass renderPass(deviceWithTimestamp);
+
+    wgpu::QuerySet timestampQuerySet =
+        CreateQuerySet(deviceWithTimestamp, wgpu::QueryType::Timestamp, 2);
+    wgpu::QuerySet occlusionQuerySet =
+        CreateQuerySet(deviceWithTimestamp, wgpu::QueryType::Occlusion, 2);
+
+    // Success on render pass encoder
+    {
+        wgpu::CommandEncoder encoder = deviceWithTimestamp.CreateCommandEncoder();
+        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
+        pass.WriteTimestamp(timestampQuerySet, 0);
+        pass.EndPass();
+        encoder.Finish();
+    }
+
+    // Not allow to write timestamp from another device
+    {
+        // Write timestamp from default device
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
+        pass.WriteTimestamp(timestampQuerySet, 0);
+        pass.EndPass();
+        ASSERT_DEVICE_ERROR(encoder.Finish());
+    }
+
+    // Not allow to write timestamp to the query set with other query type
+    {
+        wgpu::CommandEncoder encoder = deviceWithTimestamp.CreateCommandEncoder();
+        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
+        pass.WriteTimestamp(occlusionQuerySet, 0);
+        pass.EndPass();
+        ASSERT_DEVICE_ERROR(encoder.Finish());
+    }
+
+    // Fail to write timestamp to the index which exceeds the number of queries in query set
+    {
+        wgpu::CommandEncoder encoder = deviceWithTimestamp.CreateCommandEncoder();
+        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
+        pass.WriteTimestamp(timestampQuerySet, 2);
+        pass.EndPass();
+        ASSERT_DEVICE_ERROR(encoder.Finish());
+    }
+
+    // Fail to submit timestamp query with a destroyed query set
+    {
+        wgpu::CommandEncoder encoder = deviceWithTimestamp.CreateCommandEncoder();
+        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
+        pass.WriteTimestamp(timestampQuerySet, 0);
+        pass.EndPass();
+        wgpu::CommandBuffer commands = encoder.Finish();
+
+        wgpu::Queue queue = deviceWithTimestamp.GetDefaultQueue();
+        timestampQuerySet.Destroy();
+        ASSERT_DEVICE_ERROR(queue.Submit(1, &commands));
+    }
+}