Add Debug Label Functionality For D3D12/Vk Buffers & Textures

Adds a generic SetDebugName utility for D3D12 and Vulkan. Passes down
debug label set by user for buffers and textures to be labeled by the
appropriate backend method.

Bug: dawn:840
Change-Id: I7158b537a6e37fdf733645e6830dc33833ee683e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/61588
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Brandon Jones (Intel) <brandon1.jones@intel.com>
diff --git a/src/dawn_native/Buffer.cpp b/src/dawn_native/Buffer.cpp
index 3e1b72f..d9ca585 100644
--- a/src/dawn_native/Buffer.cpp
+++ b/src/dawn_native/Buffer.cpp
@@ -128,7 +128,7 @@
     // Buffer
 
     BufferBase::BufferBase(DeviceBase* device, const BufferDescriptor* descriptor)
-        : ObjectBase(device),
+        : ObjectBase(device, descriptor->label),
           mSize(descriptor->size),
           mUsage(descriptor->usage),
           mState(BufferState::Unmapped) {
diff --git a/src/dawn_native/DawnNative.cpp b/src/dawn_native/DawnNative.cpp
index 1ac8d98..15fd697 100644
--- a/src/dawn_native/DawnNative.cpp
+++ b/src/dawn_native/DawnNative.cpp
@@ -225,4 +225,9 @@
     ExternalImageExportInfo::ExternalImageExportInfo(ExternalImageType type) : type(type) {
     }
 
+    const char* GetObjectLabelForTesting(void* objectHandle) {
+        ObjectBase* object = reinterpret_cast<ObjectBase*>(objectHandle);
+        return object->GetLabel().c_str();
+    }
+
 }  // namespace dawn_native
diff --git a/src/dawn_native/ObjectBase.cpp b/src/dawn_native/ObjectBase.cpp
index 8b4731f..05ba33e 100644
--- a/src/dawn_native/ObjectBase.cpp
+++ b/src/dawn_native/ObjectBase.cpp
@@ -22,10 +22,20 @@
     ObjectBase::ObjectBase(DeviceBase* device) : RefCounted(kNotErrorPayload), mDevice(device) {
     }
 
+    ObjectBase::ObjectBase(DeviceBase* device, const char* label) : ObjectBase(device) {
+        if (label) {
+            mLabel = label;
+        }
+    }
+
     ObjectBase::ObjectBase(DeviceBase* device, ErrorTag)
         : RefCounted(kErrorPayload), mDevice(device) {
     }
 
+    const std::string& ObjectBase::GetLabel() {
+        return mLabel;
+    }
+
     DeviceBase* ObjectBase::GetDevice() const {
         return mDevice;
     }
@@ -34,4 +44,12 @@
         return GetRefCountPayload() == kErrorPayload;
     }
 
+    void ObjectBase::APISetLabel(const char* label) {
+        mLabel = label;
+        SetLabelImpl();
+    }
+
+    void ObjectBase::SetLabelImpl() {
+    }
+
 }  // namespace dawn_native
diff --git a/src/dawn_native/ObjectBase.h b/src/dawn_native/ObjectBase.h
index 544ce1a..41a4dcf 100644
--- a/src/dawn_native/ObjectBase.h
+++ b/src/dawn_native/ObjectBase.h
@@ -17,6 +17,8 @@
 
 #include "common/RefCounted.h"
 
+#include <string>
+
 namespace dawn_native {
 
     class DeviceBase;
@@ -27,15 +29,21 @@
         static constexpr ErrorTag kError = {};
 
         ObjectBase(DeviceBase* device);
+        ObjectBase(DeviceBase* device, const char* label);
         ObjectBase(DeviceBase* device, ErrorTag tag);
 
         DeviceBase* GetDevice() const;
+        const std::string& GetLabel();
         bool IsError() const;
 
-      protected:
-        ~ObjectBase() override = default;
+        // Dawn API
+        void APISetLabel(const char* label);
 
       private:
+        virtual void SetLabelImpl();
+
+        // TODO(dawn:840): Optimize memory footprint for objects that don't have labels.
+        std::string mLabel;
         DeviceBase* mDevice;
     };
 
diff --git a/src/dawn_native/Texture.cpp b/src/dawn_native/Texture.cpp
index bf1ccff..1b9a708 100644
--- a/src/dawn_native/Texture.cpp
+++ b/src/dawn_native/Texture.cpp
@@ -433,7 +433,7 @@
     TextureBase::TextureBase(DeviceBase* device,
                              const TextureDescriptor* descriptor,
                              TextureState state)
-        : ObjectBase(device),
+        : ObjectBase(device, descriptor->label),
           mDimension(descriptor->dimension),
           mFormat(device->GetValidInternalFormat(descriptor->format)),
           mSize(descriptor->size),
diff --git a/src/dawn_native/Toggles.cpp b/src/dawn_native/Toggles.cpp
index efd6519..7c40fe7 100644
--- a/src/dawn_native/Toggles.cpp
+++ b/src/dawn_native/Toggles.cpp
@@ -212,6 +212,11 @@
              {"disable_symbol_renaming",
               "Disables the WGSL symbol renaming so that names are preserved.",
               "https://crbug.com/dawn/1016"}},
+            {Toggle::UseUserDefinedLabelsInBackend,
+             {"use_user_defined_labels_in_backend",
+              "Enables calls to SetLabel to be forwarded to backend-specific APIs that label "
+              "objects.",
+              "https://crbug.com/dawn/840"}},
             // Dummy comment to separate the }} so it is clearer what to copy-paste to add a toggle.
         }};
     }  // anonymous namespace
diff --git a/src/dawn_native/Toggles.h b/src/dawn_native/Toggles.h
index 9c11400..668086f 100644
--- a/src/dawn_native/Toggles.h
+++ b/src/dawn_native/Toggles.h
@@ -58,6 +58,7 @@
         ForceWGSLStep,
         DisableWorkgroupInit,
         DisableSymbolRenaming,
+        UseUserDefinedLabelsInBackend,
 
         EnumCount,
         InvalidEnum = EnumCount,
diff --git a/src/dawn_native/d3d12/BufferD3D12.cpp b/src/dawn_native/d3d12/BufferD3D12.cpp
index 399a02c..67ebc47 100644
--- a/src/dawn_native/d3d12/BufferD3D12.cpp
+++ b/src/dawn_native/d3d12/BufferD3D12.cpp
@@ -24,6 +24,7 @@
 #include "dawn_native/d3d12/DeviceD3D12.h"
 #include "dawn_native/d3d12/HeapD3D12.h"
 #include "dawn_native/d3d12/ResidencyManagerD3D12.h"
+#include "dawn_native/d3d12/UtilsD3D12.h"
 
 namespace dawn_native { namespace d3d12 {
 
@@ -155,7 +156,7 @@
             mResourceAllocation,
             ToBackend(GetDevice())->AllocateMemory(heapType, resourceDescriptor, bufferUsage));
 
-        DAWN_TRY(mResourceAllocation.SetDebugName("Dawn_Buffer"));
+        SetLabelImpl();
 
         // The buffers with mappedAtCreation == true will be initialized in
         // BufferBase::MapAtCreation().
@@ -446,6 +447,11 @@
         return {};
     }
 
+    void Buffer::SetLabelImpl() {
+        SetDebugName(ToBackend(GetDevice()), mResourceAllocation.GetD3D12Resource(), "Dawn_Buffer",
+                     GetLabel());
+    }
+
     MaybeError Buffer::InitializeToZero(CommandRecordingContext* commandContext) {
         ASSERT(GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse));
         ASSERT(!IsDataInitialized());
diff --git a/src/dawn_native/d3d12/BufferD3D12.h b/src/dawn_native/d3d12/BufferD3D12.h
index ed19efa..d6fcbbd 100644
--- a/src/dawn_native/d3d12/BufferD3D12.h
+++ b/src/dawn_native/d3d12/BufferD3D12.h
@@ -48,6 +48,8 @@
                                                       uint64_t size);
         MaybeError EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext,
                                                       const CopyTextureToBufferCmd* copy);
+        // Dawn API
+        void SetLabelImpl() override;
 
       private:
         Buffer(Device* device, const BufferDescriptor* descriptor);
diff --git a/src/dawn_native/d3d12/ResourceHeapAllocationD3D12.cpp b/src/dawn_native/d3d12/ResourceHeapAllocationD3D12.cpp
index 10a43d8..c7f8c68 100644
--- a/src/dawn_native/d3d12/ResourceHeapAllocationD3D12.cpp
+++ b/src/dawn_native/d3d12/ResourceHeapAllocationD3D12.cpp
@@ -40,11 +40,4 @@
     D3D12_GPU_VIRTUAL_ADDRESS ResourceHeapAllocation::GetGPUPointer() const {
         return mResource->GetGPUVirtualAddress();
     }
-
-    MaybeError ResourceHeapAllocation::SetDebugName(const char* name) {
-        DAWN_TRY(CheckHRESULT(
-            mResource->SetPrivateData(WKPDID_D3DDebugObjectName, std::strlen(name), name),
-            "ID3D12Resource::SetName"));
-        return {};
-    }
 }}  // namespace dawn_native::d3d12
diff --git a/src/dawn_native/d3d12/ResourceHeapAllocationD3D12.h b/src/dawn_native/d3d12/ResourceHeapAllocationD3D12.h
index 892a72c..7f1fe0a 100644
--- a/src/dawn_native/d3d12/ResourceHeapAllocationD3D12.h
+++ b/src/dawn_native/d3d12/ResourceHeapAllocationD3D12.h
@@ -35,7 +35,6 @@
         ResourceHeapAllocation& operator=(const ResourceHeapAllocation&) = default;
 
         void Invalidate() override;
-        MaybeError SetDebugName(const char* name);
 
         ID3D12Resource* GetD3D12Resource() const;
         D3D12_GPU_VIRTUAL_ADDRESS GetGPUPointer() const;
diff --git a/src/dawn_native/d3d12/StagingBufferD3D12.cpp b/src/dawn_native/d3d12/StagingBufferD3D12.cpp
index 4c1e90b..d35622e 100644
--- a/src/dawn_native/d3d12/StagingBufferD3D12.cpp
+++ b/src/dawn_native/d3d12/StagingBufferD3D12.cpp
@@ -17,6 +17,7 @@
 #include "dawn_native/d3d12/DeviceD3D12.h"
 #include "dawn_native/d3d12/HeapD3D12.h"
 #include "dawn_native/d3d12/ResidencyManagerD3D12.h"
+#include "dawn_native/d3d12/UtilsD3D12.h"
 
 namespace dawn_native { namespace d3d12 {
 
@@ -47,7 +48,7 @@
         DAWN_TRY(mDevice->GetResidencyManager()->LockAllocation(
             ToBackend(mUploadHeap.GetResourceHeap())));
 
-        DAWN_TRY(mUploadHeap.SetDebugName("Dawn_StagingBuffer"));
+        SetDebugName(mDevice, GetResource(), "Dawn_StagingBuffer");
 
         return CheckHRESULT(GetResource()->Map(0, nullptr, &mMappedPointer), "ID3D12Resource::Map");
     }
diff --git a/src/dawn_native/d3d12/TextureD3D12.cpp b/src/dawn_native/d3d12/TextureD3D12.cpp
index 4968533..fcabcd0 100644
--- a/src/dawn_native/d3d12/TextureD3D12.cpp
+++ b/src/dawn_native/d3d12/TextureD3D12.cpp
@@ -482,7 +482,7 @@
         // memory management.
         mResourceAllocation = {info, 0, std::move(d3d12Texture), nullptr};
 
-        DAWN_TRY(mResourceAllocation.SetDebugName("Dawn_ExternalTexture"));
+        SetLabelHelper("Dawn_ExternalTexture");
 
         return {};
     }
@@ -521,7 +521,7 @@
                             ->AllocateMemory(D3D12_HEAP_TYPE_DEFAULT, resourceDescriptor,
                                              D3D12_RESOURCE_STATE_COMMON));
 
-        DAWN_TRY(mResourceAllocation.SetDebugName("Dawn_InternalTexture"));
+        SetLabelImpl();
 
         Device* device = ToBackend(GetDevice());
 
@@ -544,7 +544,8 @@
         // memory management.
         mResourceAllocation = {info, 0, std::move(d3d12Texture), nullptr};
 
-        DAWN_TRY(mResourceAllocation.SetDebugName("Dawn_SwapChainTexture"));
+        SetLabelHelper("Dawn_SwapChainTexture");
+
         return {};
     }
 
@@ -1026,6 +1027,15 @@
         return {};
     }
 
+    void Texture::SetLabelHelper(const char* prefix) {
+        SetDebugName(ToBackend(GetDevice()), mResourceAllocation.GetD3D12Resource(), prefix,
+                     GetLabel());
+    }
+
+    void Texture::SetLabelImpl() {
+        SetLabelHelper("Dawn_InternalTexture");
+    }
+
     void Texture::EnsureSubresourceContentInitialized(CommandRecordingContext* commandContext,
                                                       const SubresourceRange& range) {
         if (!ToBackend(GetDevice())->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) {
diff --git a/src/dawn_native/d3d12/TextureD3D12.h b/src/dawn_native/d3d12/TextureD3D12.h
index 0c1bb2e..c414a8a 100644
--- a/src/dawn_native/d3d12/TextureD3D12.h
+++ b/src/dawn_native/d3d12/TextureD3D12.h
@@ -98,8 +98,12 @@
                                                bool isSwapChainTexture);
         MaybeError InitializeAsSwapChainTexture(ComPtr<ID3D12Resource> d3d12Texture);
 
+        void SetLabelHelper(const char* prefix);
+
         // Dawn API
+        void SetLabelImpl() override;
         void DestroyImpl() override;
+
         MaybeError ClearTexture(CommandRecordingContext* commandContext,
                                 const SubresourceRange& range,
                                 TextureBase::ClearValue clearValue);
diff --git a/src/dawn_native/d3d12/UtilsD3D12.cpp b/src/dawn_native/d3d12/UtilsD3D12.cpp
index b52dc0b..98f056b 100644
--- a/src/dawn_native/d3d12/UtilsD3D12.cpp
+++ b/src/dawn_native/d3d12/UtilsD3D12.cpp
@@ -18,6 +18,8 @@
 #include "dawn_native/Format.h"
 #include "dawn_native/d3d12/BufferD3D12.h"
 #include "dawn_native/d3d12/CommandRecordingContext.h"
+#include "dawn_native/d3d12/D3D12Error.h"
+#include "dawn_native/d3d12/DeviceD3D12.h"
 
 #include <stringapiset.h>
 
@@ -370,4 +372,16 @@
         }
     }
 
+    void SetDebugName(Device* device, ID3D12Object* object, const char* prefix, std::string label) {
+        if (label.empty() || !device->IsToggleEnabled(Toggle::UseUserDefinedLabelsInBackend)) {
+            object->SetPrivateData(WKPDID_D3DDebugObjectName, strlen(prefix), prefix);
+            return;
+        }
+
+        std::string objectName = prefix;
+        objectName += "_";
+        objectName += label;
+        object->SetPrivateData(WKPDID_D3DDebugObjectName, objectName.length(), objectName.c_str());
+    }
+
 }}  // namespace dawn_native::d3d12
diff --git a/src/dawn_native/d3d12/UtilsD3D12.h b/src/dawn_native/d3d12/UtilsD3D12.h
index fea27d5..2a3f3d5 100644
--- a/src/dawn_native/d3d12/UtilsD3D12.h
+++ b/src/dawn_native/d3d12/UtilsD3D12.h
@@ -81,6 +81,11 @@
                                    Buffer* buffer,
                                    const Extent3D& copySize);
 
+    void SetDebugName(Device* device,
+                      ID3D12Object* object,
+                      const char* prefix,
+                      std::string label = "");
+
 }}  // namespace dawn_native::d3d12
 
 #endif  // DAWNNATIVE_D3D12_UTILSD3D12_H_
diff --git a/src/dawn_native/vulkan/BufferVk.cpp b/src/dawn_native/vulkan/BufferVk.cpp
index 8139b29..75a32fd 100644
--- a/src/dawn_native/vulkan/BufferVk.cpp
+++ b/src/dawn_native/vulkan/BufferVk.cpp
@@ -19,6 +19,7 @@
 #include "dawn_native/vulkan/FencedDeleter.h"
 #include "dawn_native/vulkan/ResourceHeapVk.h"
 #include "dawn_native/vulkan/ResourceMemoryAllocatorVk.h"
+#include "dawn_native/vulkan/UtilsVulkan.h"
 #include "dawn_native/vulkan/VulkanError.h"
 
 #include <cstring>
@@ -229,6 +230,9 @@
                 ClearBuffer(recordingContext, 0, clearOffset, clearSize);
             }
         }
+
+        SetLabelImpl();
+
         return {};
     }
 
@@ -374,6 +378,11 @@
         }
     }
 
+    void Buffer::SetLabelImpl() {
+        SetDebugName(ToBackend(GetDevice()), VK_OBJECT_TYPE_BUFFER,
+                     reinterpret_cast<uint64_t&>(mHandle), "Dawn_Buffer", GetLabel());
+    }
+
     void Buffer::InitializeToZero(CommandRecordingContext* recordingContext) {
         ASSERT(GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse));
         ASSERT(!IsDataInitialized());
diff --git a/src/dawn_native/vulkan/BufferVk.h b/src/dawn_native/vulkan/BufferVk.h
index 1c40140..3fcab60 100644
--- a/src/dawn_native/vulkan/BufferVk.h
+++ b/src/dawn_native/vulkan/BufferVk.h
@@ -49,6 +49,9 @@
         void EnsureDataInitializedAsDestination(CommandRecordingContext* recordingContext,
                                                 const CopyTextureToBufferCmd* copy);
 
+        // Dawn API
+        void SetLabelImpl() override;
+
       private:
         ~Buffer() override;
         using BufferBase::BufferBase;
diff --git a/src/dawn_native/vulkan/StagingBufferVk.cpp b/src/dawn_native/vulkan/StagingBufferVk.cpp
index cf3129c..c78f0fc 100644
--- a/src/dawn_native/vulkan/StagingBufferVk.cpp
+++ b/src/dawn_native/vulkan/StagingBufferVk.cpp
@@ -17,6 +17,7 @@
 #include "dawn_native/vulkan/FencedDeleter.h"
 #include "dawn_native/vulkan/ResourceHeapVk.h"
 #include "dawn_native/vulkan/ResourceMemoryAllocatorVk.h"
+#include "dawn_native/vulkan/UtilsVulkan.h"
 #include "dawn_native/vulkan/VulkanError.h"
 
 namespace dawn_native { namespace vulkan {
@@ -57,6 +58,9 @@
             return DAWN_INTERNAL_ERROR("Unable to map staging buffer.");
         }
 
+        SetDebugName(mDevice, VK_OBJECT_TYPE_BUFFER, reinterpret_cast<uint64_t&>(mBuffer),
+                     "Dawn_StagingBuffer");
+
         return {};
     }
 
diff --git a/src/dawn_native/vulkan/TextureVk.cpp b/src/dawn_native/vulkan/TextureVk.cpp
index cbb2f2ce..f4be49a 100644
--- a/src/dawn_native/vulkan/TextureVk.cpp
+++ b/src/dawn_native/vulkan/TextureVk.cpp
@@ -577,6 +577,8 @@
                                   GetAllSubresources(), TextureBase::ClearValue::NonZero));
         }
 
+        SetLabelImpl();
+
         return {};
     }
 
@@ -611,11 +613,15 @@
         baseCreateInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
 
         DAWN_TRY_ASSIGN(mHandle, externalMemoryService->CreateImage(descriptor, baseCreateInfo));
+
+        SetLabelHelper("Dawn_ExternalTexture");
+
         return {};
     }
 
     void Texture::InitializeForSwapChain(VkImage nativeImage) {
         mHandle = nativeImage;
+        SetLabelHelper("Dawn_SwapChainTexture");
     }
 
     MaybeError Texture::BindExternalMemory(const ExternalImageDescriptorVk* descriptor,
@@ -720,6 +726,15 @@
         DestroyInternal();
     }
 
+    void Texture::SetLabelHelper(const char* prefix) {
+        SetDebugName(ToBackend(GetDevice()), VK_OBJECT_TYPE_IMAGE,
+                     reinterpret_cast<uint64_t&>(mHandle), prefix, GetLabel());
+    }
+
+    void Texture::SetLabelImpl() {
+        SetLabelHelper("Dawn_InternalTexture");
+    }
+
     void Texture::DestroyImpl() {
         if (GetTextureState() == TextureState::OwnedInternal) {
             Device* device = ToBackend(GetDevice());
diff --git a/src/dawn_native/vulkan/TextureVk.h b/src/dawn_native/vulkan/TextureVk.h
index e985c55..779d51b 100644
--- a/src/dawn_native/vulkan/TextureVk.h
+++ b/src/dawn_native/vulkan/TextureVk.h
@@ -93,6 +93,11 @@
                                          VkImageLayout* releasedOldLayout,
                                          VkImageLayout* releasedNewLayout);
 
+        void SetLabelHelper(const char* prefix);
+
+        // Dawn API
+        void SetLabelImpl() override;
+
       private:
         ~Texture() override;
         Texture(Device* device, const TextureDescriptor* descriptor, TextureState state);
diff --git a/src/dawn_native/vulkan/UtilsVulkan.cpp b/src/dawn_native/vulkan/UtilsVulkan.cpp
index c6952d4..eb46180 100644
--- a/src/dawn_native/vulkan/UtilsVulkan.cpp
+++ b/src/dawn_native/vulkan/UtilsVulkan.cpp
@@ -17,8 +17,10 @@
 #include "common/Assert.h"
 #include "dawn_native/EnumMaskIterator.h"
 #include "dawn_native/Format.h"
+#include "dawn_native/vulkan/DeviceVk.h"
 #include "dawn_native/vulkan/Forward.h"
 #include "dawn_native/vulkan/TextureVk.h"
+#include "dawn_native/vulkan/VulkanError.h"
 
 namespace dawn_native { namespace vulkan {
 
@@ -162,4 +164,30 @@
 
         return region;
     }
+
+    void SetDebugName(Device* device,
+                      VkObjectType objectType,
+                      uint64_t objectHandle,
+                      const char* prefix,
+                      std::string label) {
+        if (device->GetGlobalInfo().HasExt(InstanceExt::DebugUtils)) {
+            VkDebugUtilsObjectNameInfoEXT objectNameInfo;
+            objectNameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT;
+            objectNameInfo.pNext = nullptr;
+            objectNameInfo.objectType = objectType;
+            objectNameInfo.objectHandle = objectHandle;
+
+            if (label.empty() || !device->IsToggleEnabled(Toggle::UseUserDefinedLabelsInBackend)) {
+                objectNameInfo.pObjectName = prefix;
+                device->fn.SetDebugUtilsObjectNameEXT(device->GetVkDevice(), &objectNameInfo);
+                return;
+            }
+
+            std::string objectName = prefix;
+            objectName += "_";
+            objectName += label;
+            objectNameInfo.pObjectName = objectName.c_str();
+            device->fn.SetDebugUtilsObjectNameEXT(device->GetVkDevice(), &objectNameInfo);
+        }
+    }
 }}  // namespace dawn_native::vulkan
diff --git a/src/dawn_native/vulkan/UtilsVulkan.h b/src/dawn_native/vulkan/UtilsVulkan.h
index e57e3f4..23c36a9 100644
--- a/src/dawn_native/vulkan/UtilsVulkan.h
+++ b/src/dawn_native/vulkan/UtilsVulkan.h
@@ -21,6 +21,8 @@
 
 namespace dawn_native { namespace vulkan {
 
+    class Device;
+
     // A Helper type used to build a pNext chain of extension structs.
     // Usage is:
     //   1) Create instance, passing the address of the first struct in the
@@ -99,6 +101,12 @@
                                                    const TextureCopy& textureCopy,
                                                    const Extent3D& copySize);
 
+    void SetDebugName(Device* device,
+                      VkObjectType objectType,
+                      uint64_t objectHandle,
+                      const char* prefix,
+                      std::string label = "");
+
 }}  // namespace dawn_native::vulkan
 
 #endif  // DAWNNATIVE_VULKAN_UTILSVULKAN_H_
diff --git a/src/include/dawn_native/DawnNative.h b/src/include/dawn_native/DawnNative.h
index 9baf128..a0a6229 100644
--- a/src/include/dawn_native/DawnNative.h
+++ b/src/include/dawn_native/DawnNative.h
@@ -248,6 +248,8 @@
         ExternalImageExportInfo(ExternalImageType type);
     };
 
+    DAWN_NATIVE_EXPORT const char* GetObjectLabelForTesting(void* objectHandle);
+
 }  // namespace dawn_native
 
 #endif  // DAWNNATIVE_DAWNNATIVE_H_
diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn
index 3ff1698..8aa80f8 100644
--- a/src/tests/BUILD.gn
+++ b/src/tests/BUILD.gn
@@ -203,6 +203,7 @@
     "unittests/validation/GetBindGroupLayoutValidationTests.cpp",
     "unittests/validation/IndexBufferValidationTests.cpp",
     "unittests/validation/InternalUsageValidationTests.cpp",
+    "unittests/validation/LabelTests.cpp",
     "unittests/validation/MinimumBufferSizeValidationTests.cpp",
     "unittests/validation/MultipleDeviceTests.cpp",
     "unittests/validation/QueryValidationTests.cpp",
diff --git a/src/tests/unittests/validation/LabelTests.cpp b/src/tests/unittests/validation/LabelTests.cpp
new file mode 100644
index 0000000..4cb2a24
--- /dev/null
+++ b/src/tests/unittests/validation/LabelTests.cpp
@@ -0,0 +1,86 @@
+// Copyright 2021 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 WvecANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string>
+#include "tests/unittests/validation/ValidationTest.h"
+
+class LabelTest : public ValidationTest {};
+
+TEST_F(LabelTest, Buffer) {
+    DAWN_SKIP_TEST_IF(UsesWire());
+    std::string label = "test";
+    wgpu::BufferDescriptor descriptor;
+    descriptor.size = 4;
+    descriptor.usage = wgpu::BufferUsage::Uniform;
+
+    // The label should be empty if one was not set.
+    {
+        wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
+        std::string readbackLabel = dawn_native::GetObjectLabelForTesting(buffer.Get());
+        ASSERT_TRUE(readbackLabel.empty());
+    }
+
+    // Test setting a label through API
+    {
+        wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
+        buffer.SetLabel(label.c_str());
+        std::string readbackLabel = dawn_native::GetObjectLabelForTesting(buffer.Get());
+        ASSERT_EQ(label, readbackLabel);
+    }
+
+    // Test setting a label through the descriptor.
+    {
+        descriptor.label = label.c_str();
+        wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
+        std::string readbackLabel = dawn_native::GetObjectLabelForTesting(buffer.Get());
+        ASSERT_EQ(label, readbackLabel);
+    }
+}
+
+TEST_F(LabelTest, Texture) {
+    DAWN_SKIP_TEST_IF(UsesWire());
+    std::string label = "test";
+    wgpu::TextureDescriptor descriptor;
+    descriptor.size.width = 1;
+    descriptor.size.height = 1;
+    descriptor.size.depthOrArrayLayers = 1;
+    descriptor.mipLevelCount = 1;
+    descriptor.sampleCount = 1;
+    descriptor.dimension = wgpu::TextureDimension::e2D;
+    descriptor.format = wgpu::TextureFormat::RGBA8Uint;
+    descriptor.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding;
+
+    // The label should be empty if one was not set.
+    {
+        wgpu::Texture texture = device.CreateTexture(&descriptor);
+        std::string readbackLabel = dawn_native::GetObjectLabelForTesting(texture.Get());
+        ASSERT_TRUE(readbackLabel.empty());
+    }
+
+    // Test setting a label through API
+    {
+        wgpu::Texture texture = device.CreateTexture(&descriptor);
+        texture.SetLabel(label.c_str());
+        std::string readbackLabel = dawn_native::GetObjectLabelForTesting(texture.Get());
+        ASSERT_EQ(label, readbackLabel);
+    }
+
+    // Test setting a label through the descriptor.
+    {
+        descriptor.label = label.c_str();
+        wgpu::Texture texture = device.CreateTexture(&descriptor);
+        std::string readbackLabel = dawn_native::GetObjectLabelForTesting(texture.Get());
+        ASSERT_EQ(label, readbackLabel);
+    }
+}
\ No newline at end of file