Implementation of Debug Marker APIs

Introduces pushDebugGroup, popDebugGroup, and insertDebugMarker implementations
for Vulkan and Metal using VK_EXT_debug_marker and XCode, respectively.

Bug: dawn:44
Change-Id: I0ae56c4d67aa832123f27a1fcdddf65746261e57
Reviewed-on: https://dawn-review.googlesource.com/c/4241
Commit-Queue: Brandon Jones <brandon1.jones@intel.com>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp
index 7a5c81d..2498820 100644
--- a/src/dawn_native/CommandEncoder.cpp
+++ b/src/dawn_native/CommandEncoder.cpp
@@ -94,6 +94,29 @@
             return {};
         }
 
+        inline MaybeError PushDebugMarkerStack(unsigned int* counter) {
+            *counter += 1;
+            return {};
+        }
+
+        inline MaybeError PopDebugMarkerStack(unsigned int* counter) {
+            if (*counter == 0) {
+                return DAWN_VALIDATION_ERROR("Pop must be balanced by a corresponding Push.");
+            } else {
+                *counter -= 1;
+            }
+
+            return {};
+        }
+
+        inline MaybeError ValidateDebugGroups(const unsigned int counter) {
+            if (counter != 0) {
+                return DAWN_VALIDATION_ERROR("Each Push must be balanced by a corresponding Pop.");
+            }
+
+            return {};
+        }
+
         MaybeError ComputeTextureCopyBufferSize(const Extent3D& copySize,
                                                 uint32_t rowPitch,
                                                 uint32_t imageHeight,
@@ -635,6 +658,8 @@
                 case Command::EndComputePass: {
                     mIterator.NextCommand<EndComputePassCmd>();
 
+                    DAWN_TRY(ValidateDebugGroups(mDebugGroupStackSize));
+
                     DAWN_TRY(usageTracker.ValidateUsages(PassType::Compute));
                     mResourceUsages.perPass.push_back(usageTracker.AcquireResourceUsage());
                     return {};
@@ -645,6 +670,22 @@
                     DAWN_TRY(persistentState.ValidateCanDispatch());
                 } break;
 
+                case Command::InsertDebugMarker: {
+                    InsertDebugMarkerCmd* cmd = mIterator.NextCommand<InsertDebugMarkerCmd>();
+                    mIterator.NextData<char>(cmd->length + 1);
+                } break;
+
+                case Command::PopDebugGroup: {
+                    mIterator.NextCommand<PopDebugGroupCmd>();
+                    DAWN_TRY(PopDebugMarkerStack(&mDebugGroupStackSize));
+                } break;
+
+                case Command::PushDebugGroup: {
+                    PushDebugGroupCmd* cmd = mIterator.NextCommand<PushDebugGroupCmd>();
+                    mIterator.NextData<char>(cmd->length + 1);
+                    DAWN_TRY(PushDebugMarkerStack(&mDebugGroupStackSize));
+                } break;
+
                 case Command::SetComputePipeline: {
                     SetComputePipelineCmd* cmd = mIterator.NextCommand<SetComputePipelineCmd>();
                     ComputePipelineBase* pipeline = cmd->pipeline.Get();
@@ -701,6 +742,8 @@
                 case Command::EndRenderPass: {
                     mIterator.NextCommand<EndRenderPassCmd>();
 
+                    DAWN_TRY(ValidateDebugGroups(mDebugGroupStackSize));
+
                     DAWN_TRY(usageTracker.ValidateUsages(PassType::Render));
                     mResourceUsages.perPass.push_back(usageTracker.AcquireResourceUsage());
                     return {};
@@ -716,6 +759,22 @@
                     DAWN_TRY(persistentState.ValidateCanDrawIndexed());
                 } break;
 
+                case Command::InsertDebugMarker: {
+                    InsertDebugMarkerCmd* cmd = mIterator.NextCommand<InsertDebugMarkerCmd>();
+                    mIterator.NextData<char>(cmd->length + 1);
+                } break;
+
+                case Command::PopDebugGroup: {
+                    mIterator.NextCommand<PopDebugGroupCmd>();
+                    DAWN_TRY(PopDebugMarkerStack(&mDebugGroupStackSize));
+                } break;
+
+                case Command::PushDebugGroup: {
+                    PushDebugGroupCmd* cmd = mIterator.NextCommand<PushDebugGroupCmd>();
+                    mIterator.NextData<char>(cmd->length + 1);
+                    DAWN_TRY(PushDebugMarkerStack(&mDebugGroupStackSize));
+                } break;
+
                 case Command::SetRenderPipeline: {
                     SetRenderPipelineCmd* cmd = mIterator.NextCommand<SetRenderPipelineCmd>();
                     RenderPipelineBase* pipeline = cmd->pipeline.Get();
diff --git a/src/dawn_native/CommandEncoder.h b/src/dawn_native/CommandEncoder.h
index 7ff5d1d..6935847 100644
--- a/src/dawn_native/CommandEncoder.h
+++ b/src/dawn_native/CommandEncoder.h
@@ -87,6 +87,8 @@
         bool mWereResourceUsagesAcquired = false;
         CommandBufferResourceUsage mResourceUsages;
 
+        unsigned int mDebugGroupStackSize = 0;
+
         bool mGotError = false;
         std::string mErrorMessage;
     };
diff --git a/src/dawn_native/Commands.cpp b/src/dawn_native/Commands.cpp
index 6443fc8..816dd39 100644
--- a/src/dawn_native/Commands.cpp
+++ b/src/dawn_native/Commands.cpp
@@ -69,6 +69,20 @@
                     EndRenderPassCmd* cmd = commands->NextCommand<EndRenderPassCmd>();
                     cmd->~EndRenderPassCmd();
                 } break;
+                case Command::InsertDebugMarker: {
+                    InsertDebugMarkerCmd* cmd = commands->NextCommand<InsertDebugMarkerCmd>();
+                    commands->NextData<char>(cmd->length + 1);
+                    cmd->~InsertDebugMarkerCmd();
+                } break;
+                case Command::PopDebugGroup: {
+                    PopDebugGroupCmd* cmd = commands->NextCommand<PopDebugGroupCmd>();
+                    cmd->~PopDebugGroupCmd();
+                } break;
+                case Command::PushDebugGroup: {
+                    PushDebugGroupCmd* cmd = commands->NextCommand<PushDebugGroupCmd>();
+                    commands->NextData<char>(cmd->length + 1);
+                    cmd->~PushDebugGroupCmd();
+                } break;
                 case Command::SetComputePipeline: {
                     SetComputePipelineCmd* cmd = commands->NextCommand<SetComputePipelineCmd>();
                     cmd->~SetComputePipelineCmd();
@@ -158,6 +172,20 @@
                 commands->NextCommand<EndRenderPassCmd>();
                 break;
 
+            case Command::InsertDebugMarker: {
+                InsertDebugMarkerCmd* cmd = commands->NextCommand<InsertDebugMarkerCmd>();
+                commands->NextData<char>(cmd->length + 1);
+            } break;
+
+            case Command::PopDebugGroup:
+                commands->NextCommand<PopDebugGroupCmd>();
+                break;
+
+            case Command::PushDebugGroup: {
+                PushDebugGroupCmd* cmd = commands->NextCommand<PushDebugGroupCmd>();
+                commands->NextData<char>(cmd->length + 1);
+            } break;
+
             case Command::SetComputePipeline:
                 commands->NextCommand<SetComputePipelineCmd>();
                 break;
diff --git a/src/dawn_native/Commands.h b/src/dawn_native/Commands.h
index afa006d..94e19c5 100644
--- a/src/dawn_native/Commands.h
+++ b/src/dawn_native/Commands.h
@@ -41,6 +41,9 @@
         DrawIndexed,
         EndComputePass,
         EndRenderPass,
+        InsertDebugMarker,
+        PopDebugGroup,
+        PushDebugGroup,
         SetComputePipeline,
         SetRenderPipeline,
         SetPushConstants,
@@ -140,6 +143,16 @@
 
     struct EndRenderPassCmd {};
 
+    struct InsertDebugMarkerCmd {
+        uint32_t length;
+    };
+
+    struct PopDebugGroupCmd {};
+
+    struct PushDebugGroupCmd {
+        uint32_t length;
+    };
+
     struct SetComputePipelineCmd {
         Ref<ComputePipelineBase> pipeline;
     };
diff --git a/src/dawn_native/ProgrammablePassEncoder.cpp b/src/dawn_native/ProgrammablePassEncoder.cpp
index 9f54ce5..0962ba5 100644
--- a/src/dawn_native/ProgrammablePassEncoder.cpp
+++ b/src/dawn_native/ProgrammablePassEncoder.cpp
@@ -38,6 +38,30 @@
         mAllocator = nullptr;
     }
 
+    void ProgrammablePassEncoder::InsertDebugMarker(const char* groupLabel) {
+        InsertDebugMarkerCmd* cmd =
+            mAllocator->Allocate<InsertDebugMarkerCmd>(Command::InsertDebugMarker);
+        new (cmd) InsertDebugMarkerCmd;
+        cmd->length = strlen(groupLabel);
+
+        char* label = mAllocator->AllocateData<char>(cmd->length + 1);
+        memcpy(label, groupLabel, cmd->length + 1);
+    }
+
+    void ProgrammablePassEncoder::PopDebugGroup() {
+        PopDebugGroupCmd* cmd = mAllocator->Allocate<PopDebugGroupCmd>(Command::PopDebugGroup);
+        new (cmd) PopDebugGroupCmd;
+    }
+
+    void ProgrammablePassEncoder::PushDebugGroup(const char* groupLabel) {
+        PushDebugGroupCmd* cmd = mAllocator->Allocate<PushDebugGroupCmd>(Command::PushDebugGroup);
+        new (cmd) PushDebugGroupCmd;
+        cmd->length = strlen(groupLabel);
+
+        char* label = mAllocator->AllocateData<char>(cmd->length + 1);
+        memcpy(label, groupLabel, cmd->length + 1);
+    }
+
     void ProgrammablePassEncoder::SetBindGroup(uint32_t groupIndex, BindGroupBase* group) {
         if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands()) ||
             mTopLevelEncoder->ConsumedError(GetDevice()->ValidateObject(group))) {
diff --git a/src/dawn_native/ProgrammablePassEncoder.h b/src/dawn_native/ProgrammablePassEncoder.h
index c082dc0..4924c9c 100644
--- a/src/dawn_native/ProgrammablePassEncoder.h
+++ b/src/dawn_native/ProgrammablePassEncoder.h
@@ -35,6 +35,10 @@
 
         void EndPass();
 
+        void InsertDebugMarker(const char* groupLabel);
+        void PopDebugGroup();
+        void PushDebugGroup(const char* groupLabel);
+
         void SetBindGroup(uint32_t groupIndex, BindGroupBase* group);
         void SetPushConstants(dawn::ShaderStageBit stages,
                               uint32_t offset,
diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
index d11be87..30bb086 100644
--- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
@@ -696,6 +696,14 @@
                                                       draw->firstInstance);
                 } break;
 
+                case Command::InsertDebugMarker:
+                case Command::PopDebugGroup:
+                case Command::PushDebugGroup: {
+                    // TODO(brandon1.jones@intel.com): Implement debug markers after PIX licensing
+                    // issue is resolved.
+                    SkipCommand(&mCommands, type);
+                } break;
+
                 case Command::SetRenderPipeline: {
                     SetRenderPipelineCmd* cmd = mCommands.NextCommand<SetRenderPipelineCmd>();
                     RenderPipeline* pipeline = ToBackend(cmd->pipeline).Get();
diff --git a/src/dawn_native/metal/CommandBufferMTL.mm b/src/dawn_native/metal/CommandBufferMTL.mm
index f6bc3ee..43090ef 100644
--- a/src/dawn_native/metal/CommandBufferMTL.mm
+++ b/src/dawn_native/metal/CommandBufferMTL.mm
@@ -435,6 +435,30 @@
                     }
                 } break;
 
+                case Command::InsertDebugMarker: {
+                    InsertDebugMarkerCmd* cmd = mCommands.NextCommand<InsertDebugMarkerCmd>();
+                    auto label = mCommands.NextData<char>(cmd->length + 1);
+                    NSString* mtlLabel = [[NSString alloc] initWithUTF8String:label];
+
+                    [encoder insertDebugSignpost:mtlLabel];
+                    [mtlLabel release];
+                } break;
+
+                case Command::PopDebugGroup: {
+                    mCommands.NextCommand<PopDebugGroupCmd>();
+
+                    [encoder popDebugGroup];
+                } break;
+
+                case Command::PushDebugGroup: {
+                    PushDebugGroupCmd* cmd = mCommands.NextCommand<PushDebugGroupCmd>();
+                    auto label = mCommands.NextData<char>(cmd->length + 1);
+                    NSString* mtlLabel = [[NSString alloc] initWithUTF8String:label];
+
+                    [encoder pushDebugGroup:mtlLabel];
+                    [mtlLabel release];
+                } break;
+
                 case Command::SetRenderPipeline: {
                     SetRenderPipelineCmd* cmd = mCommands.NextCommand<SetRenderPipelineCmd>();
                     lastPipeline = ToBackend(cmd->pipeline).Get();
diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp
index d86be64..7acecc7 100644
--- a/src/dawn_native/opengl/CommandBufferGL.cpp
+++ b/src/dawn_native/opengl/CommandBufferGL.cpp
@@ -637,6 +637,14 @@
                     }
                 } break;
 
+                case Command::InsertDebugMarker:
+                case Command::PopDebugGroup:
+                case Command::PushDebugGroup: {
+                    // Due to lack of linux driver support for GL_EXT_debug_marker
+                    // extension these functions are skipped.
+                    SkipCommand(&mCommands, type);
+                } break;
+
                 case Command::SetRenderPipeline: {
                     SetRenderPipelineCmd* cmd = mCommands.NextCommand<SetRenderPipelineCmd>();
                     lastPipeline = ToBackend(cmd->pipeline).Get();
diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp
index 13107dd..92cc724 100644
--- a/src/dawn_native/vulkan/CommandBufferVk.cpp
+++ b/src/dawn_native/vulkan/CommandBufferVk.cpp
@@ -432,6 +432,53 @@
                                               draw->firstInstance);
                 } break;
 
+                case Command::InsertDebugMarker: {
+                    if (device->GetDeviceInfo().debugMarker) {
+                        InsertDebugMarkerCmd* cmd = mCommands.NextCommand<InsertDebugMarkerCmd>();
+                        const char* label = mCommands.NextData<char>(cmd->length + 1);
+                        VkDebugMarkerMarkerInfoEXT markerInfo;
+                        markerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
+                        markerInfo.pNext = nullptr;
+                        markerInfo.pMarkerName = label;
+                        // Default color to black
+                        markerInfo.color[0] = 0.0;
+                        markerInfo.color[1] = 0.0;
+                        markerInfo.color[2] = 0.0;
+                        markerInfo.color[3] = 1.0;
+                        device->fn.CmdDebugMarkerInsertEXT(commands, &markerInfo);
+                    } else {
+                        SkipCommand(&mCommands, Command::InsertDebugMarker);
+                    }
+                } break;
+
+                case Command::PopDebugGroup: {
+                    if (device->GetDeviceInfo().debugMarker) {
+                        mCommands.NextCommand<PopDebugGroupCmd>();
+                        device->fn.CmdDebugMarkerEndEXT(commands);
+                    } else {
+                        SkipCommand(&mCommands, Command::PopDebugGroup);
+                    }
+                } break;
+
+                case Command::PushDebugGroup: {
+                    if (device->GetDeviceInfo().debugMarker) {
+                        PushDebugGroupCmd* cmd = mCommands.NextCommand<PushDebugGroupCmd>();
+                        const char* label = mCommands.NextData<char>(cmd->length + 1);
+                        VkDebugMarkerMarkerInfoEXT markerInfo;
+                        markerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
+                        markerInfo.pNext = nullptr;
+                        markerInfo.pMarkerName = label;
+                        // Default color to black
+                        markerInfo.color[0] = 0.0;
+                        markerInfo.color[1] = 0.0;
+                        markerInfo.color[2] = 0.0;
+                        markerInfo.color[3] = 1.0;
+                        device->fn.CmdDebugMarkerBeginEXT(commands, &markerInfo);
+                    } else {
+                        SkipCommand(&mCommands, Command::PushDebugGroup);
+                    }
+                } break;
+
                 case Command::SetBindGroup: {
                     SetBindGroupCmd* cmd = mCommands.NextCommand<SetBindGroupCmd>();
                     VkDescriptorSet set = ToBackend(cmd->group.Get())->GetHandle();
diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp
index 0b6ff31..c6dae49 100644
--- a/src/dawn_native/vulkan/DeviceVk.cpp
+++ b/src/dawn_native/vulkan/DeviceVk.cpp
@@ -331,6 +331,11 @@
         std::vector<const char*> extensionsToRequest;
         std::vector<VkDeviceQueueCreateInfo> queuesToRequest;
 
+        if (mDeviceInfo.debugMarker) {
+            extensionsToRequest.push_back(kExtensionNameExtDebugMarker);
+            usedKnobs.debugMarker = true;
+        }
+
         if (mDeviceInfo.swapchain) {
             extensionsToRequest.push_back(kExtensionNameKhrSwapchain);
             usedKnobs.swapchain = true;
diff --git a/src/dawn_native/vulkan/VulkanFunctions.cpp b/src/dawn_native/vulkan/VulkanFunctions.cpp
index 39f3a19..771c5d5 100644
--- a/src/dawn_native/vulkan/VulkanFunctions.cpp
+++ b/src/dawn_native/vulkan/VulkanFunctions.cpp
@@ -208,6 +208,12 @@
         GET_DEVICE_PROC(UpdateDescriptorSets);
         GET_DEVICE_PROC(WaitForFences);
 
+        if (usedKnobs.debugMarker) {
+            GET_DEVICE_PROC(CmdDebugMarkerBeginEXT);
+            GET_DEVICE_PROC(CmdDebugMarkerEndEXT);
+            GET_DEVICE_PROC(CmdDebugMarkerInsertEXT);
+        }
+
         if (usedKnobs.swapchain) {
             GET_DEVICE_PROC(CreateSwapchainKHR);
             GET_DEVICE_PROC(DestroySwapchainKHR);
diff --git a/src/dawn_native/vulkan/VulkanFunctions.h b/src/dawn_native/vulkan/VulkanFunctions.h
index e229cb0..6dcfe7e 100644
--- a/src/dawn_native/vulkan/VulkanFunctions.h
+++ b/src/dawn_native/vulkan/VulkanFunctions.h
@@ -204,6 +204,11 @@
         PFN_vkUpdateDescriptorSets UpdateDescriptorSets = nullptr;
         PFN_vkWaitForFences WaitForFences = nullptr;
 
+        // VK_EXT_debug_marker
+        PFN_vkCmdDebugMarkerBeginEXT CmdDebugMarkerBeginEXT = nullptr;
+        PFN_vkCmdDebugMarkerEndEXT CmdDebugMarkerEndEXT = nullptr;
+        PFN_vkCmdDebugMarkerInsertEXT CmdDebugMarkerInsertEXT = nullptr;
+
         // VK_KHR_swapchain
         PFN_vkCreateSwapchainKHR CreateSwapchainKHR = nullptr;
         PFN_vkDestroySwapchainKHR DestroySwapchainKHR = nullptr;
diff --git a/src/dawn_native/vulkan/VulkanInfo.cpp b/src/dawn_native/vulkan/VulkanInfo.cpp
index 4d7a30e..747202a 100644
--- a/src/dawn_native/vulkan/VulkanInfo.cpp
+++ b/src/dawn_native/vulkan/VulkanInfo.cpp
@@ -35,6 +35,7 @@
     const char kLayerNameLunargVKTrace[] = "VK_LAYER_LUNARG_vktrace";
     const char kLayerNameRenderDocCapture[] = "VK_LAYER_RENDERDOC_Capture";
 
+    const char kExtensionNameExtDebugMarker[] = "VK_EXT_debug_marker";
     const char kExtensionNameExtDebugReport[] = "VK_EXT_debug_report";
     const char kExtensionNameMvkMacosSurface[] = "VK_MVK_macos_surface";
     const char kExtensionNameKhrSurface[] = "VK_KHR_surface";
@@ -207,6 +208,10 @@
             }
 
             for (const auto& extension : info.extensions) {
+                if (IsExtensionName(extension, kExtensionNameExtDebugMarker)) {
+                    info.debugMarker = true;
+                }
+
                 if (IsExtensionName(extension, kExtensionNameKhrSwapchain)) {
                     info.swapchain = true;
                 }
diff --git a/src/dawn_native/vulkan/VulkanInfo.h b/src/dawn_native/vulkan/VulkanInfo.h
index 77971dc..4425de5 100644
--- a/src/dawn_native/vulkan/VulkanInfo.h
+++ b/src/dawn_native/vulkan/VulkanInfo.h
@@ -29,6 +29,7 @@
     extern const char kLayerNameLunargVKTrace[];
     extern const char kLayerNameRenderDocCapture[];
 
+    extern const char kExtensionNameExtDebugMarker[];
     extern const char kExtensionNameExtDebugReport[];
     extern const char kExtensionNameMvkMacosSurface[];
     extern const char kExtensionNameKhrSurface[];
@@ -66,6 +67,7 @@
         VkPhysicalDeviceFeatures features;
 
         // Extensions
+        bool debugMarker = false;
         bool swapchain = false;
     };
 
diff --git a/src/tests/end2end/DebugMarkerTests.cpp b/src/tests/end2end/DebugMarkerTests.cpp
new file mode 100644
index 0000000..1a7f6e4
--- /dev/null
+++ b/src/tests/end2end/DebugMarkerTests.cpp
@@ -0,0 +1,38 @@
+// Copyright 2019 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "tests/DawnTest.h"
+
+#include "utils/DawnHelpers.h"
+
+class DebugMarkerTests : public DawnTest {};
+
+// Make sure that calling a marker API without a debugging tool attached doesn't cause a failure.
+TEST_P(DebugMarkerTests, NoFailureWithoutDebugToolAttached) {
+    utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 4, 4);
+
+    dawn::CommandEncoder encoder = device.CreateCommandEncoder();
+    {
+        dawn::RenderPassEncoder pass = encoder.BeginRenderPass(renderPass.renderPassInfo);
+        pass.PushDebugGroup("Event Start");
+        pass.InsertDebugMarker("Marker");
+        pass.PopDebugGroup();
+        pass.EndPass();
+    }
+
+    dawn::CommandBuffer commands = encoder.Finish();
+    queue.Submit(1, &commands);
+}
+
+DAWN_INSTANTIATE_TEST(DebugMarkerTests, D3D12Backend, MetalBackend, OpenGLBackend, VulkanBackend)
diff --git a/src/tests/unittests/validation/DebugMarkerValidationTests.cpp b/src/tests/unittests/validation/DebugMarkerValidationTests.cpp
new file mode 100644
index 0000000..7c0633a
--- /dev/null
+++ b/src/tests/unittests/validation/DebugMarkerValidationTests.cpp
@@ -0,0 +1,117 @@
+// Copyright 2019 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "tests/unittests/validation/ValidationTest.h"
+
+#include "utils/DawnHelpers.h"
+
+class DebugMarkerValidationTest : public ValidationTest {};
+
+// Correct usage of debug markers should succeed in render pass.
+TEST_F(DebugMarkerValidationTest, RenderSuccess) {
+    utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 4, 4);
+
+    dawn::CommandEncoder encoder = device.CreateCommandEncoder();
+    {
+        dawn::RenderPassEncoder pass = encoder.BeginRenderPass(renderPass.renderPassInfo);
+        pass.PushDebugGroup("Event Start");
+        pass.PushDebugGroup("Event Start");
+        pass.InsertDebugMarker("Marker");
+        pass.PopDebugGroup();
+        pass.PopDebugGroup();
+        pass.EndPass();
+    }
+
+    encoder.Finish();
+}
+
+// A PushDebugGroup call without a following PopDebugGroup produces an error in render pass.
+TEST_F(DebugMarkerValidationTest, RenderUnbalancedPush) {
+    utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 4, 4);
+
+    dawn::CommandEncoder encoder = device.CreateCommandEncoder();
+    {
+        dawn::RenderPassEncoder pass = encoder.BeginRenderPass(renderPass.renderPassInfo);
+        pass.PushDebugGroup("Event Start");
+        pass.PushDebugGroup("Event Start");
+        pass.InsertDebugMarker("Marker");
+        pass.PopDebugGroup();
+        pass.EndPass();
+    }
+
+    ASSERT_DEVICE_ERROR(encoder.Finish());
+}
+
+// A PopDebugGroup call without a preceding PushDebugGroup produces an error in render pass.
+TEST_F(DebugMarkerValidationTest, RenderUnbalancedPop) {
+    utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 4, 4);
+
+    dawn::CommandEncoder encoder = device.CreateCommandEncoder();
+    {
+        dawn::RenderPassEncoder pass = encoder.BeginRenderPass(renderPass.renderPassInfo);
+        pass.PushDebugGroup("Event Start");
+        pass.InsertDebugMarker("Marker");
+        pass.PopDebugGroup();
+        pass.PopDebugGroup();
+        pass.EndPass();
+    }
+
+    ASSERT_DEVICE_ERROR(encoder.Finish());
+}
+
+// Correct usage of debug markers should succeed in compute pass.
+TEST_F(DebugMarkerValidationTest, ComputeSuccess) {
+    dawn::CommandEncoder encoder = device.CreateCommandEncoder();
+    {
+        dawn::ComputePassEncoder pass = encoder.BeginComputePass();
+        pass.PushDebugGroup("Event Start");
+        pass.PushDebugGroup("Event Start");
+        pass.InsertDebugMarker("Marker");
+        pass.PopDebugGroup();
+        pass.PopDebugGroup();
+        pass.EndPass();
+    }
+
+    encoder.Finish();
+}
+
+// A PushDebugGroup call without a following PopDebugGroup produces an error in compute pass.
+TEST_F(DebugMarkerValidationTest, ComputeUnbalancedPush) {
+    dawn::CommandEncoder encoder = device.CreateCommandEncoder();
+    {
+        dawn::ComputePassEncoder pass = encoder.BeginComputePass();
+        pass.PushDebugGroup("Event Start");
+        pass.PushDebugGroup("Event Start");
+        pass.InsertDebugMarker("Marker");
+        pass.PopDebugGroup();
+        pass.EndPass();
+    }
+
+    ASSERT_DEVICE_ERROR(encoder.Finish());
+}
+
+// A PopDebugGroup call without a preceding PushDebugGroup produces an error in compute pass.
+TEST_F(DebugMarkerValidationTest, ComputeUnbalancedPop) {
+    dawn::CommandEncoder encoder = device.CreateCommandEncoder();
+    {
+        dawn::ComputePassEncoder pass = encoder.BeginComputePass();
+        pass.PushDebugGroup("Event Start");
+        pass.InsertDebugMarker("Marker");
+        pass.PopDebugGroup();
+        pass.PopDebugGroup();
+        pass.EndPass();
+    }
+
+    ASSERT_DEVICE_ERROR(encoder.Finish());
+}
\ No newline at end of file