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/BUILD.gn b/BUILD.gn
index ded94749..7086b65 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -944,6 +944,7 @@
     "src/tests/unittests/validation/CommandBufferValidationTests.cpp",
     "src/tests/unittests/validation/ComputeValidationTests.cpp",
     "src/tests/unittests/validation/CopyCommandsValidationTests.cpp",
+    "src/tests/unittests/validation/DebugMarkerValidationTests.cpp",
     "src/tests/unittests/validation/DynamicStateCommandValidationTests.cpp",
     "src/tests/unittests/validation/FenceValidationTests.cpp",
     "src/tests/unittests/validation/InputStateValidationTests.cpp",
@@ -1002,6 +1003,7 @@
     "src/tests/end2end/ColorStateTests.cpp",
     "src/tests/end2end/ComputeCopyStorageBufferTests.cpp",
     "src/tests/end2end/CopyTests.cpp",
+    "src/tests/end2end/DebugMarkerTests.cpp",
     "src/tests/end2end/DepthStencilStateTests.cpp",
     "src/tests/end2end/DrawIndexedTests.cpp",
     "src/tests/end2end/DrawTests.cpp",
diff --git a/dawn.json b/dawn.json
index b97fcc2..e328f5d 100644
--- a/dawn.json
+++ b/dawn.json
@@ -337,6 +337,22 @@
         "category": "object",
         "methods": [
             {
+                "name": "insert debug marker",
+                "args": [
+                    {"name": "group label", "type": "char", "annotation": "const*", "length": "strlen"}
+                ]
+            },
+            {
+                "name": "pop debug group",
+                "args": []
+            },
+            {
+                "name": "push debug group",
+                "args": [
+                    {"name": "group label", "type": "char", "annotation": "const*", "length": "strlen"}
+                ]
+            },
+            {
                 "name": "set pipeline",
                 "args": [
                     {"name": "pipeline", "type": "compute pipeline"}
@@ -805,6 +821,22 @@
                 ]
             },
             {
+                "name": "insert debug marker",
+                "args": [
+                    {"name": "group label", "type": "char", "annotation": "const*", "length": "strlen"}
+                ]
+            },
+            {
+                "name": "pop debug group",
+                "args": []
+            },
+            {
+                "name": "push debug group",
+                "args": [
+                    {"name": "group label", "type": "char", "annotation": "const*", "length": "strlen"}
+                ]
+            },
+            {
                 "name": "set stencil reference",
                 "args": [
                     {"name": "reference", "type": "uint32_t"}
diff --git a/docs/DebugMarkers.md b/docs/DebugMarkers.md
new file mode 100644
index 0000000..b84024c
--- /dev/null
+++ b/docs/DebugMarkers.md
@@ -0,0 +1,38 @@
+# Debug Markers

+

+Dawn provides debug tooling integration for each backend.

+

+Debugging markers are exposed through this API:

+```

+partial GPUProgrammablePassEncoder {

+    void pushDebugGroup(const char * markerLabel);

+    void popDebugGroup();

+    void insertDebugMarker(const char * markerLabel);

+};

+```

+

+These APIs will result in silent no-ops if they are used without setting up

+the execution environment properly. Each backend has a specific process

+for setting up this environment.

+

+## D3D12

+

+Debug markers are currently unimplemented on D3D12 pending resolution of a licensing issue.

+

+## Vulkan

+

+Debug markers on Vulkan are implemented with [VK_EXT_debug_marker](https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#VK_EXT_debug_marker).

+

+To enable marker functionality, you must launch your application from your debugging tool. Attaching to an already running application is not supported.

+

+Vulkan markers have been tested with [RenderDoc](https://renderdoc.org/).

+

+## Metal

+

+Debug markers on Metal are used with the XCode debugger.

+

+To enable marker functionality, you must launch your application from XCode and use [GPU Frame Capture](https://developer.apple.com/documentation/metal/tools_profiling_and_debugging/metal_gpu_capture).

+

+## OpenGL

+

+Debug markers on OpenGL are not implemented and will result in a silent no-op. This is due to low adoption of the GL_EXT_debug_marker extension in Linux device drivers.
\ No newline at end of file
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