Remove VK_DEFINE_NON_DISPATCHABLE_HANDLE magic, use explicit VkHandle wrapper

Overriding VK_DEFINE_NON_DISPATCHABLE_HANDLE changes the function
signatures of Vulkan functions, changing their ABI and making us
incompatible with real drivers. This removes that magic, and replaces it
with an explicit wrapper, VkHandle, which has much of the same
functionality as the original VkNonDispatchableHandle.

It adds definitions for dawn_native::vulkan::VkBuffer et al, which
shadow the native ::VkBuffer et al. This retains type safety throughout
the Vulkan backend without changing every single usage.

Notably, the following things had to change:
- An explicit conversion from VkBuffer* to ::VkBuffer* is needed for
  arrays. This is implemented as a reinterpret_cast, which is still
  safe as the new VkHandle still has the same memory layout properties
  as VkNonDispatchableHandle did.
- When pointing to a VkHandle as an output pointer, it's now necessary
  to explicitly get the native ::VkBuffer (via operator*) and point to
  it.

Previously reviewed on:
https://dawn-review.googlesource.com/c/dawn/+/15580

Bug: chromium:1046362
Change-Id: I7d34ec38a805025f92165ea9a7ee07ae5c182076
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/15641
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Kai Ninomiya <kainino@chromium.org>
diff --git a/src/common/vulkan_platform.h b/src/common/vulkan_platform.h
index 35742a7..913f011 100644
--- a/src/common/vulkan_platform.h
+++ b/src/common/vulkan_platform.h
@@ -18,6 +18,9 @@
 #if !defined(DAWN_ENABLE_BACKEND_VULKAN)
 #    error "vulkan_platform.h included without the Vulkan backend enabled"
 #endif
+#if defined(VULKAN_CORE_H_)
+#    error "vulkan.h included before vulkan_platform.h"
+#endif
 
 #include "common/Platform.h"
 
@@ -33,10 +36,9 @@
 // (like vulkan.h on 64 bit) but makes sure the types are different on 32 bit architectures.
 
 #if defined(DAWN_PLATFORM_64_BIT)
-#    define DAWN_DEFINE_NATIVE_NON_DISPATCHABLE_HANDLE(object) \
-        using object##Native = struct object##_T*;
+#    define DAWN_DEFINE_NATIVE_NON_DISPATCHABLE_HANDLE(object) using object = struct object##_T*;
 #elif defined(DAWN_PLATFORM_32_BIT)
-#    define DAWN_DEFINE_NATIVE_NON_DISPATCHABLE_HANDLE(object) using object##Native = uint64_t;
+#    define DAWN_DEFINE_NATIVE_NON_DISPATCHABLE_HANDLE(object) using object = uint64_t;
 #else
 #    error "Unsupported platform"
 #endif
@@ -53,105 +55,106 @@
 // One way to get the alignment inside structures of a type is to look at the alignment of it
 // wrapped in a structure. Hence VkSameHandleNativeWrappe
 
-template <typename T>
-struct WrapperStruct {
-    T member;
-};
+namespace dawn_native { namespace vulkan {
 
-template <typename T>
-static constexpr size_t AlignOfInStruct = alignof(WrapperStruct<T>);
+    namespace detail {
+        template <typename T>
+        struct WrapperStruct {
+            T member;
+        };
 
-static constexpr size_t kNativeVkHandleAlignment = AlignOfInStruct<VkSomeHandleNative>;
-static constexpr size_t kUint64Alignment = AlignOfInStruct<VkSomeHandleNative>;
+        template <typename T>
+        static constexpr size_t AlignOfInStruct = alignof(WrapperStruct<T>);
 
-// Simple handle types that supports "nullptr_t" as a 0 value.
-template <typename Tag, typename HandleType>
-class alignas(kNativeVkHandleAlignment) VkNonDispatchableHandle {
-  public:
-    // Default constructor and assigning of VK_NULL_HANDLE
-    VkNonDispatchableHandle() = default;
-    VkNonDispatchableHandle(std::nullptr_t) : mHandle(0) {
+        static constexpr size_t kNativeVkHandleAlignment = AlignOfInStruct<VkSomeHandle>;
+        static constexpr size_t kUint64Alignment = AlignOfInStruct<uint64_t>;
+
+        // Simple handle types that supports "nullptr_t" as a 0 value.
+        template <typename Tag, typename HandleType>
+        class alignas(detail::kNativeVkHandleAlignment) VkHandle {
+          public:
+            // Default constructor and assigning of VK_NULL_HANDLE
+            VkHandle() = default;
+            VkHandle(std::nullptr_t) {
+            }
+
+            // Use default copy constructor/assignment
+            VkHandle(const VkHandle<Tag, HandleType>& other) = default;
+            VkHandle& operator=(const VkHandle<Tag, HandleType>&) = default;
+
+            // Comparisons between handles
+            bool operator==(VkHandle<Tag, HandleType> other) const {
+                return mHandle == other.mHandle;
+            }
+            bool operator!=(VkHandle<Tag, HandleType> other) const {
+                return mHandle != other.mHandle;
+            }
+
+            // Comparisons between handles and VK_NULL_HANDLE
+            bool operator==(std::nullptr_t) const {
+                return mHandle == 0;
+            }
+            bool operator!=(std::nullptr_t) const {
+                return mHandle != 0;
+            }
+
+            // Implicit conversion to real Vulkan types.
+            operator HandleType() const {
+                return GetHandle();
+            }
+
+            HandleType GetHandle() const {
+                return mHandle;
+            }
+
+            HandleType& operator*() {
+                return mHandle;
+            }
+
+            static VkHandle<Tag, HandleType> CreateFromHandle(HandleType handle) {
+                return VkHandle{handle};
+            }
+
+          private:
+            explicit VkHandle(HandleType handle) : mHandle(handle) {
+            }
+
+            HandleType mHandle = 0;
+        };
+    }  // namespace detail
+
+    static constexpr std::nullptr_t VK_NULL_HANDLE = nullptr;
+
+    template <typename Tag, typename HandleType>
+    HandleType* AsVkArray(detail::VkHandle<Tag, HandleType>* handle) {
+        return reinterpret_cast<HandleType*>(handle);
     }
 
-    // Use default copy constructor/assignment
-    VkNonDispatchableHandle(const VkNonDispatchableHandle<Tag, HandleType>& other) = default;
-    VkNonDispatchableHandle& operator=(const VkNonDispatchableHandle<Tag, HandleType>&) = default;
+}}  // namespace dawn_native::vulkan
 
-    // Comparisons between handles
-    bool operator==(VkNonDispatchableHandle<Tag, HandleType> other) const {
-        return mHandle == other.mHandle;
-    }
-    bool operator!=(VkNonDispatchableHandle<Tag, HandleType> other) const {
-        return mHandle != other.mHandle;
-    }
+#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object)                                   \
+    DAWN_DEFINE_NATIVE_NON_DISPATCHABLE_HANDLE(object)                              \
+    namespace dawn_native { namespace vulkan {                                      \
+            using object = detail::VkHandle<struct VkTag##object, ::object>;        \
+            static_assert(sizeof(object) == sizeof(uint64_t), "");                  \
+            static_assert(alignof(object) == detail::kUint64Alignment, "");         \
+            static_assert(sizeof(object) == sizeof(::object), "");                  \
+            static_assert(alignof(object) == detail::kNativeVkHandleAlignment, ""); \
+        }                                                                           \
+    }  // namespace dawn_native::vulkan
 
-    // Comparisons between handles and VK_NULL_HANDLE
-    bool operator==(std::nullptr_t) const {
-        return mHandle == 0;
-    }
-    bool operator!=(std::nullptr_t) const {
-        return mHandle != 0;
-    }
+#include <vulkan/vulkan.h>
 
-    // The regular Vulkan handle type depends on the pointer width but is always 64 bits wide.
-    //  - On 64bit it is an opaque pointer type, probably to help with type safety
-    //  - On 32bit it is a uint64_t because pointers aren't wide enough (and non dispatchable
-    //    handles can be optimized to not be pointer but contain GPU virtual addresses or the
-    //    data in a packed form).
-    // Because of this we need two types of conversions from our handle type: to uint64_t and to
-    // the "native" Vulkan type that may not be an uint64_t
-
-    static VkNonDispatchableHandle<Tag, HandleType> CreateFromU64(uint64_t handle) {
-        return {handle};
-    }
-
-    uint64_t GetU64() const {
-        return mHandle;
-    }
-
+// Redefine VK_NULL_HANDLE for better type safety where possible.
+#undef VK_NULL_HANDLE
 #if defined(DAWN_PLATFORM_64_BIT)
-    static VkNonDispatchableHandle<Tag, HandleType> CreateFromHandle(HandleType handle) {
-        return CreateFromU64(static_cast<uint64_t>(reinterpret_cast<intptr_t>(handle)));
-    }
-
-    HandleType GetHandle() const {
-        return mHandle;
-    }
+static constexpr nullptr_t VK_NULL_HANDLE = nullptr;
 #elif defined(DAWN_PLATFORM_32_BIT)
-    static VkNonDispatchableHandle<Tag, HandleType> CreateFromHandle(HandleType handle) {
-        return {handle};
-    }
-
-    HandleType GetHandle() const {
-        return mHandle;
-    }
+static constexpr uint64_t VK_NULL_HANDLE = 0;
 #else
 #    error "Unsupported platform"
 #endif
 
-  private:
-    VkNonDispatchableHandle(uint64_t handle) : mHandle(handle) {
-    }
-
-    uint64_t mHandle = 0;
-};
-
-#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object)                          \
-    struct VkTag##object;                                                  \
-    DAWN_DEFINE_NATIVE_NON_DISPATCHABLE_HANDLE(object)                     \
-    using object = VkNonDispatchableHandle<VkTag##object, object##Native>; \
-    static_assert(sizeof(object) == sizeof(uint64_t), "");                 \
-    static_assert(alignof(object) == kUint64Alignment, "");                \
-    static_assert(sizeof(object) == sizeof(object##Native), "");           \
-    static_assert(alignof(object) == kNativeVkHandleAlignment, "");
-
-#    include <vulkan/vulkan.h>
-
-    // VK_NULL_HANDLE is defined to 0 but we don't want our handle type to compare to arbitrary
-    // integers. Redefine VK_NULL_HANDLE to nullptr that has its own type.
-#    undef VK_NULL_HANDLE
-#    define VK_NULL_HANDLE nullptr
-
 // Remove windows.h macros after vulkan_platform's include of windows.h
 #if defined(DAWN_PLATFORM_WINDOWS)
 #    include "common/windows_with_undefs.h"
diff --git a/src/dawn_native/vulkan/BackendVk.cpp b/src/dawn_native/vulkan/BackendVk.cpp
index d2aaa76..d2745cb 100644
--- a/src/dawn_native/vulkan/BackendVk.cpp
+++ b/src/dawn_native/vulkan/BackendVk.cpp
@@ -272,7 +272,7 @@
         createInfo.pUserData = this;
 
         return CheckVkSuccess(mFunctions.CreateDebugReportCallbackEXT(
-                                  mInstance, &createInfo, nullptr, &mDebugReportCallback),
+                                  mInstance, &createInfo, nullptr, &*mDebugReportCallback),
                               "vkCreateDebugReportcallback");
     }
 
diff --git a/src/dawn_native/vulkan/BindGroupLayoutVk.cpp b/src/dawn_native/vulkan/BindGroupLayoutVk.cpp
index 89ce53d..90c8366 100644
--- a/src/dawn_native/vulkan/BindGroupLayoutVk.cpp
+++ b/src/dawn_native/vulkan/BindGroupLayoutVk.cpp
@@ -105,7 +105,7 @@
 
         Device* device = ToBackend(GetDevice());
         DAWN_TRY(CheckVkSuccess(device->fn.CreateDescriptorSetLayout(
-                                    device->GetVkDevice(), &createInfo, nullptr, &mHandle),
+                                    device->GetVkDevice(), &createInfo, nullptr, &*mHandle),
                                 "CreateDescriptorSetLayout"));
 
         // Compute the size of descriptor pools used for this layout.
@@ -171,7 +171,7 @@
 
         VkDescriptorPool descriptorPool;
         DAWN_TRY(CheckVkSuccess(device->fn.CreateDescriptorPool(device->GetVkDevice(), &createInfo,
-                                                                nullptr, &descriptorPool),
+                                                                nullptr, &*descriptorPool),
                                 "CreateDescriptorPool"));
 
         // Allocate our single set.
@@ -180,12 +180,13 @@
         allocateInfo.pNext = nullptr;
         allocateInfo.descriptorPool = descriptorPool;
         allocateInfo.descriptorSetCount = 1;
-        allocateInfo.pSetLayouts = &mHandle;
+        allocateInfo.pSetLayouts = &*mHandle;
 
         VkDescriptorSet descriptorSet;
-        MaybeError result = CheckVkSuccess(
-            device->fn.AllocateDescriptorSets(device->GetVkDevice(), &allocateInfo, &descriptorSet),
-            "AllocateDescriptorSets");
+        MaybeError result =
+            CheckVkSuccess(device->fn.AllocateDescriptorSets(device->GetVkDevice(), &allocateInfo,
+                                                             &*descriptorSet),
+                           "AllocateDescriptorSets");
 
         if (result.IsError()) {
             // On an error we can destroy the pool immediately because no command references it.
diff --git a/src/dawn_native/vulkan/BindGroupVk.h b/src/dawn_native/vulkan/BindGroupVk.h
index 4dd4c21..9fa857b 100644
--- a/src/dawn_native/vulkan/BindGroupVk.h
+++ b/src/dawn_native/vulkan/BindGroupVk.h
@@ -17,6 +17,7 @@
 
 #include "dawn_native/BindGroup.h"
 
+#include "common/vulkan_platform.h"
 #include "dawn_native/vulkan/BindGroupLayoutVk.h"
 
 namespace dawn_native { namespace vulkan {
diff --git a/src/dawn_native/vulkan/BufferVk.cpp b/src/dawn_native/vulkan/BufferVk.cpp
index 8f8b3aa..4a1af60 100644
--- a/src/dawn_native/vulkan/BufferVk.cpp
+++ b/src/dawn_native/vulkan/BufferVk.cpp
@@ -147,7 +147,7 @@
 
         Device* device = ToBackend(GetDevice());
         DAWN_TRY(CheckVkSuccess(
-            device->fn.CreateBuffer(device->GetVkDevice(), &createInfo, nullptr, &mHandle),
+            device->fn.CreateBuffer(device->GetVkDevice(), &createInfo, nullptr, &*mHandle),
             "vkCreateBuffer"));
 
         VkMemoryRequirements requirements;
diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp
index b845e3b..7e12806 100644
--- a/src/dawn_native/vulkan/CommandBufferVk.cpp
+++ b/src/dawn_native/vulkan/CommandBufferVk.cpp
@@ -106,7 +106,7 @@
                                                     ? dynamicOffsets[dirtyIndex].data()
                                                     : nullptr;
                 device->fn.CmdBindDescriptorSets(commands, bindPoint, pipelineLayout, dirtyIndex, 1,
-                                                 &set, dynamicOffsetCounts[dirtyIndex],
+                                                 &*set, dynamicOffsetCounts[dirtyIndex],
                                                  dynamicOffset);
             }
         }
@@ -255,14 +255,14 @@
                 createInfo.flags = 0;
                 createInfo.renderPass = renderPassVK;
                 createInfo.attachmentCount = attachmentCount;
-                createInfo.pAttachments = attachments.data();
+                createInfo.pAttachments = AsVkArray(attachments.data());
                 createInfo.width = renderPass->width;
                 createInfo.height = renderPass->height;
                 createInfo.layers = 1;
 
                 DAWN_TRY(
                     CheckVkSuccess(device->fn.CreateFramebuffer(device->GetVkDevice(), &createInfo,
-                                                                nullptr, &framebuffer),
+                                                                nullptr, &*framebuffer),
                                    "CreateFramebuffer"));
 
                 // We don't reuse VkFramebuffers so mark the framebuffer for deletion as soon as the
@@ -827,7 +827,7 @@
                     VkBuffer buffer = ToBackend(cmd->buffer)->GetHandle();
                     VkDeviceSize offset = static_cast<VkDeviceSize>(cmd->offset);
 
-                    device->fn.CmdBindVertexBuffers(commands, cmd->slot, 1, &buffer, &offset);
+                    device->fn.CmdBindVertexBuffers(commands, cmd->slot, 1, &*buffer, &offset);
                 } break;
 
                 default:
diff --git a/src/dawn_native/vulkan/ComputePipelineVk.cpp b/src/dawn_native/vulkan/ComputePipelineVk.cpp
index 2f37620..16dd8e7 100644
--- a/src/dawn_native/vulkan/ComputePipelineVk.cpp
+++ b/src/dawn_native/vulkan/ComputePipelineVk.cpp
@@ -38,7 +38,7 @@
         createInfo.pNext = nullptr;
         createInfo.flags = 0;
         createInfo.layout = ToBackend(descriptor->layout)->GetHandle();
-        createInfo.basePipelineHandle = VK_NULL_HANDLE;
+        createInfo.basePipelineHandle = ::VK_NULL_HANDLE;
         createInfo.basePipelineIndex = -1;
 
         createInfo.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
@@ -51,8 +51,8 @@
 
         Device* device = ToBackend(GetDevice());
         return CheckVkSuccess(
-            device->fn.CreateComputePipelines(device->GetVkDevice(), VK_NULL_HANDLE, 1, &createInfo,
-                                              nullptr, &mHandle),
+            device->fn.CreateComputePipelines(device->GetVkDevice(), ::VK_NULL_HANDLE, 1,
+                                              &createInfo, nullptr, &*mHandle),
             "CreateComputePipeline");
     }
 
diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp
index bc4179a..e78718e 100644
--- a/src/dawn_native/vulkan/DeviceVk.cpp
+++ b/src/dawn_native/vulkan/DeviceVk.cpp
@@ -272,13 +272,13 @@
         submitInfo.pNext = nullptr;
         submitInfo.waitSemaphoreCount =
             static_cast<uint32_t>(mRecordingContext.waitSemaphores.size());
-        submitInfo.pWaitSemaphores = mRecordingContext.waitSemaphores.data();
+        submitInfo.pWaitSemaphores = AsVkArray(mRecordingContext.waitSemaphores.data());
         submitInfo.pWaitDstStageMask = dstStageMasks.data();
         submitInfo.commandBufferCount = 1;
         submitInfo.pCommandBuffers = &mRecordingContext.commandBuffer;
         submitInfo.signalSemaphoreCount =
             static_cast<uint32_t>(mRecordingContext.signalSemaphores.size());
-        submitInfo.pSignalSemaphores = mRecordingContext.signalSemaphores.data();
+        submitInfo.pSignalSemaphores = AsVkArray(mRecordingContext.signalSemaphores.data());
 
         VkFence fence = VK_NULL_HANDLE;
         DAWN_TRY_ASSIGN(fence, GetUnusedFence());
@@ -474,7 +474,7 @@
     ResultOrError<VkFence> Device::GetUnusedFence() {
         if (!mUnusedFences.empty()) {
             VkFence fence = mUnusedFences.back();
-            DAWN_TRY(CheckVkSuccess(fn.ResetFences(mVkDevice, 1, &fence), "vkResetFences"));
+            DAWN_TRY(CheckVkSuccess(fn.ResetFences(mVkDevice, 1, &*fence), "vkResetFences"));
 
             mUnusedFences.pop_back();
             return fence;
@@ -486,7 +486,7 @@
         createInfo.flags = 0;
 
         VkFence fence = VK_NULL_HANDLE;
-        DAWN_TRY(CheckVkSuccess(fn.CreateFence(mVkDevice, &createInfo, nullptr, &fence),
+        DAWN_TRY(CheckVkSuccess(fn.CreateFence(mVkDevice, &createInfo, nullptr, &*fence),
                                 "vkCreateFence"));
 
         return fence;
@@ -539,7 +539,7 @@
             createInfo.queueFamilyIndex = mQueueFamily;
 
             DAWN_TRY(CheckVkSuccess(fn.CreateCommandPool(mVkDevice, &createInfo, nullptr,
-                                                         &mRecordingContext.commandPool),
+                                                         &*mRecordingContext.commandPool),
                                     "vkCreateCommandPool"));
 
             VkCommandBufferAllocateInfo allocateInfo;
@@ -756,7 +756,7 @@
             VkResult result = VkResult::WrapUnsafe(VK_TIMEOUT);
             do {
                 result = VkResult::WrapUnsafe(
-                    INJECT_ERROR_OR_RUN(fn.WaitForFences(mVkDevice, 1, &fence, true, UINT64_MAX),
+                    INJECT_ERROR_OR_RUN(fn.WaitForFences(mVkDevice, 1, &*fence, true, UINT64_MAX),
                                         VK_ERROR_DEVICE_LOST));
             } while (result == VK_TIMEOUT);
 
diff --git a/src/dawn_native/vulkan/NativeSwapChainImplVk.cpp b/src/dawn_native/vulkan/NativeSwapChainImplVk.cpp
index a330d71..ada090f 100644
--- a/src/dawn_native/vulkan/NativeSwapChainImplVk.cpp
+++ b/src/dawn_native/vulkan/NativeSwapChainImplVk.cpp
@@ -136,7 +136,7 @@
         createInfo.oldSwapchain = oldSwapchain;
 
         if (mDevice->fn.CreateSwapchainKHR(mDevice->GetVkDevice(), &createInfo, nullptr,
-                                           &mSwapChain) != VK_SUCCESS) {
+                                           &*mSwapChain) != VK_SUCCESS) {
             ASSERT(false);
         }
 
@@ -151,7 +151,7 @@
         ASSERT(count >= mConfig.minImageCount);
         mSwapChainImages.resize(count);
         if (mDevice->fn.GetSwapchainImagesKHR(mDevice->GetVkDevice(), mSwapChain, &count,
-                                              mSwapChainImages.data()) != VK_SUCCESS) {
+                                              AsVkArray(mSwapChainImages.data())) != VK_SUCCESS) {
             ASSERT(false);
         }
 
@@ -168,7 +168,7 @@
             barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
             barrier.srcQueueFamilyIndex = 0;
             barrier.dstQueueFamilyIndex = 0;
-            barrier.image = image;
+            barrier.image = *image;
             barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
             barrier.subresourceRange.baseMipLevel = 0;
             barrier.subresourceRange.levelCount = 1;
@@ -197,18 +197,22 @@
             createInfo.pNext = nullptr;
             createInfo.flags = 0;
             if (mDevice->fn.CreateSemaphore(mDevice->GetVkDevice(), &createInfo, nullptr,
-                                            &semaphore) != VK_SUCCESS) {
+                                            &*semaphore) != VK_SUCCESS) {
                 ASSERT(false);
             }
         }
 
         if (mDevice->fn.AcquireNextImageKHR(mDevice->GetVkDevice(), mSwapChain,
                                             std::numeric_limits<uint64_t>::max(), semaphore,
-                                            VK_NULL_HANDLE, &mLastImageIndex) != VK_SUCCESS) {
+                                            VkFence{}, &mLastImageIndex) != VK_SUCCESS) {
             ASSERT(false);
         }
 
-        nextTexture->texture.u64 = mSwapChainImages[mLastImageIndex].GetU64();
+        nextTexture->texture.u64 =
+#if defined(DAWN_PLATFORM_64_BIT)
+            reinterpret_cast<uint64_t>
+#endif
+            (*mSwapChainImages[mLastImageIndex]);
         mDevice->GetPendingRecordingContext()->waitSemaphores.push_back(semaphore);
 
         return DAWN_SWAP_CHAIN_NO_ERROR;
@@ -227,7 +231,7 @@
         presentInfo.waitSemaphoreCount = 0;
         presentInfo.pWaitSemaphores = nullptr;
         presentInfo.swapchainCount = 1;
-        presentInfo.pSwapchains = &mSwapChain;
+        presentInfo.pSwapchains = &*mSwapChain;
         presentInfo.pImageIndices = &mLastImageIndex;
         presentInfo.pResults = nullptr;
 
diff --git a/src/dawn_native/vulkan/PipelineLayoutVk.cpp b/src/dawn_native/vulkan/PipelineLayoutVk.cpp
index dd123af..847ba60 100644
--- a/src/dawn_native/vulkan/PipelineLayoutVk.cpp
+++ b/src/dawn_native/vulkan/PipelineLayoutVk.cpp
@@ -48,13 +48,13 @@
         createInfo.pNext = nullptr;
         createInfo.flags = 0;
         createInfo.setLayoutCount = numSetLayouts;
-        createInfo.pSetLayouts = setLayouts.data();
+        createInfo.pSetLayouts = AsVkArray(setLayouts.data());
         createInfo.pushConstantRangeCount = 0;
         createInfo.pPushConstantRanges = nullptr;
 
         Device* device = ToBackend(GetDevice());
         return CheckVkSuccess(
-            device->fn.CreatePipelineLayout(device->GetVkDevice(), &createInfo, nullptr, &mHandle),
+            device->fn.CreatePipelineLayout(device->GetVkDevice(), &createInfo, nullptr, &*mHandle),
             "CreatePipelineLayout");
     }
 
diff --git a/src/dawn_native/vulkan/RenderPassCache.cpp b/src/dawn_native/vulkan/RenderPassCache.cpp
index 8742458..47330f1 100644
--- a/src/dawn_native/vulkan/RenderPassCache.cpp
+++ b/src/dawn_native/vulkan/RenderPassCache.cpp
@@ -191,9 +191,9 @@
 
         // Create the render pass from the zillion parameters
         VkRenderPass renderPass;
-        DAWN_TRY(CheckVkSuccess(
-            mDevice->fn.CreateRenderPass(mDevice->GetVkDevice(), &createInfo, nullptr, &renderPass),
-            "CreateRenderPass"));
+        DAWN_TRY(CheckVkSuccess(mDevice->fn.CreateRenderPass(mDevice->GetVkDevice(), &createInfo,
+                                                             nullptr, &*renderPass),
+                                "CreateRenderPass"));
         return renderPass;
     }
 
diff --git a/src/dawn_native/vulkan/RenderPipelineVk.cpp b/src/dawn_native/vulkan/RenderPipelineVk.cpp
index 4b770a5..b0ba4fd 100644
--- a/src/dawn_native/vulkan/RenderPipelineVk.cpp
+++ b/src/dawn_native/vulkan/RenderPipelineVk.cpp
@@ -495,12 +495,12 @@
         createInfo.layout = ToBackend(GetLayout())->GetHandle();
         createInfo.renderPass = renderPass;
         createInfo.subpass = 0;
-        createInfo.basePipelineHandle = VK_NULL_HANDLE;
+        createInfo.basePipelineHandle = VkPipeline{};
         createInfo.basePipelineIndex = -1;
 
         return CheckVkSuccess(
-            device->fn.CreateGraphicsPipelines(device->GetVkDevice(), VK_NULL_HANDLE, 1,
-                                               &createInfo, nullptr, &mHandle),
+            device->fn.CreateGraphicsPipelines(device->GetVkDevice(), VkPipelineCache{}, 1,
+                                               &createInfo, nullptr, &*mHandle),
             "CreateGraphicsPipeline");
     }
 
diff --git a/src/dawn_native/vulkan/ResourceMemoryAllocatorVk.cpp b/src/dawn_native/vulkan/ResourceMemoryAllocatorVk.cpp
index 25fa5ee..3d8ded5 100644
--- a/src/dawn_native/vulkan/ResourceMemoryAllocatorVk.cpp
+++ b/src/dawn_native/vulkan/ResourceMemoryAllocatorVk.cpp
@@ -73,7 +73,7 @@
             // First check OOM that we want to surface to the application.
             DAWN_TRY(CheckVkOOMThenSuccess(
                 mDevice->fn.AllocateMemory(mDevice->GetVkDevice(), &allocateInfo, nullptr,
-                                           &allocatedMemory),
+                                           &*allocatedMemory),
                 "vkAllocateMemory"));
 
             ASSERT(allocatedMemory != VK_NULL_HANDLE);
diff --git a/src/dawn_native/vulkan/SamplerVk.cpp b/src/dawn_native/vulkan/SamplerVk.cpp
index 05baf71..67d70f8 100644
--- a/src/dawn_native/vulkan/SamplerVk.cpp
+++ b/src/dawn_native/vulkan/SamplerVk.cpp
@@ -87,7 +87,7 @@
 
         Device* device = ToBackend(GetDevice());
         return CheckVkSuccess(
-            device->fn.CreateSampler(device->GetVkDevice(), &createInfo, nullptr, &mHandle),
+            device->fn.CreateSampler(device->GetVkDevice(), &createInfo, nullptr, &*mHandle),
             "CreateSampler");
     }
 
diff --git a/src/dawn_native/vulkan/ShaderModuleVk.cpp b/src/dawn_native/vulkan/ShaderModuleVk.cpp
index 140abba..a9e717c 100644
--- a/src/dawn_native/vulkan/ShaderModuleVk.cpp
+++ b/src/dawn_native/vulkan/ShaderModuleVk.cpp
@@ -74,7 +74,7 @@
 
         Device* device = ToBackend(GetDevice());
         return CheckVkSuccess(
-            device->fn.CreateShaderModule(device->GetVkDevice(), &createInfo, nullptr, &mHandle),
+            device->fn.CreateShaderModule(device->GetVkDevice(), &createInfo, nullptr, &*mHandle),
             "CreateShaderModule");
     }
 
diff --git a/src/dawn_native/vulkan/StagingBufferVk.cpp b/src/dawn_native/vulkan/StagingBufferVk.cpp
index 4262318..4fa3b7b 100644
--- a/src/dawn_native/vulkan/StagingBufferVk.cpp
+++ b/src/dawn_native/vulkan/StagingBufferVk.cpp
@@ -36,7 +36,7 @@
         createInfo.pQueueFamilyIndices = 0;
 
         DAWN_TRY(CheckVkSuccess(
-            mDevice->fn.CreateBuffer(mDevice->GetVkDevice(), &createInfo, nullptr, &mBuffer),
+            mDevice->fn.CreateBuffer(mDevice->GetVkDevice(), &createInfo, nullptr, &*mBuffer),
             "vkCreateBuffer"));
 
         VkMemoryRequirements requirements;
diff --git a/src/dawn_native/vulkan/SwapChainVk.cpp b/src/dawn_native/vulkan/SwapChainVk.cpp
index 876af8e..e64f5e5 100644
--- a/src/dawn_native/vulkan/SwapChainVk.cpp
+++ b/src/dawn_native/vulkan/SwapChainVk.cpp
@@ -47,7 +47,8 @@
             return nullptr;
         }
 
-        VkImage nativeTexture = VkImage::CreateFromU64(next.texture.u64);
+        VkImage nativeTexture =
+            VkImage::CreateFromHandle(reinterpret_cast<::VkImage>(next.texture.u64));
         return new Texture(ToBackend(GetDevice()), descriptor, nativeTexture);
     }
 
diff --git a/src/dawn_native/vulkan/TextureVk.cpp b/src/dawn_native/vulkan/TextureVk.cpp
index 72a7653..6f2fc9e 100644
--- a/src/dawn_native/vulkan/TextureVk.cpp
+++ b/src/dawn_native/vulkan/TextureVk.cpp
@@ -460,7 +460,7 @@
         createInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
 
         DAWN_TRY(CheckVkSuccess(
-            device->fn.CreateImage(device->GetVkDevice(), &createInfo, nullptr, &mHandle),
+            device->fn.CreateImage(device->GetVkDevice(), &createInfo, nullptr, &*mHandle),
             "CreateImage"));
 
         // Create the image memory and associate it with the container
@@ -806,7 +806,7 @@
         createInfo.subresourceRange.layerCount = descriptor->arrayLayerCount;
 
         return CheckVkSuccess(
-            device->fn.CreateImageView(device->GetVkDevice(), &createInfo, nullptr, &mHandle),
+            device->fn.CreateImageView(device->GetVkDevice(), &createInfo, nullptr, &*mHandle),
             "CreateImageView");
     }
 
diff --git a/src/dawn_native/vulkan/VulkanBackend.cpp b/src/dawn_native/vulkan/VulkanBackend.cpp
index ad99a27..2a9361f 100644
--- a/src/dawn_native/vulkan/VulkanBackend.cpp
+++ b/src/dawn_native/vulkan/VulkanBackend.cpp
@@ -42,7 +42,7 @@
     // Explicitly export this function because it uses the "native" type for surfaces while the
     // header as seen in this file uses the wrapped type.
     DAWN_NATIVE_EXPORT DawnSwapChainImplementation
-    CreateNativeSwapChainImpl(WGPUDevice device, VkSurfaceKHRNative surfaceNative) {
+    CreateNativeSwapChainImpl(WGPUDevice device, ::VkSurfaceKHR surfaceNative) {
         Device* backendDevice = reinterpret_cast<Device*>(device);
         VkSurfaceKHR surface = VkSurfaceKHR::CreateFromHandle(surfaceNative);
 
diff --git a/src/dawn_native/vulkan/external_memory/MemoryServiceDmaBuf.cpp b/src/dawn_native/vulkan/external_memory/MemoryServiceDmaBuf.cpp
index 531d068..ea84d5e 100644
--- a/src/dawn_native/vulkan/external_memory/MemoryServiceDmaBuf.cpp
+++ b/src/dawn_native/vulkan/external_memory/MemoryServiceDmaBuf.cpp
@@ -196,7 +196,7 @@
         memoryDedicatedAllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
         memoryDedicatedAllocateInfo.pNext = nullptr;
         memoryDedicatedAllocateInfo.image = image;
-        memoryDedicatedAllocateInfo.buffer = VK_NULL_HANDLE;
+        memoryDedicatedAllocateInfo.buffer = VkBuffer{};
 
         VkImportMemoryFdInfoKHR importMemoryFdInfo;
         importMemoryFdInfo.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR;
@@ -213,7 +213,7 @@
         VkDeviceMemory allocatedMemory = VK_NULL_HANDLE;
         DAWN_TRY(
             CheckVkSuccess(mDevice->fn.AllocateMemory(mDevice->GetVkDevice(), &memoryAllocateInfo,
-                                                      nullptr, &allocatedMemory),
+                                                      nullptr, &*allocatedMemory),
                            "vkAllocateMemory"));
         return allocatedMemory;
     }
@@ -264,7 +264,7 @@
 
         // Create a new VkImage with tiling equal to the DRM format modifier.
         VkImage image;
-        DAWN_TRY(CheckVkSuccess(mDevice->fn.CreateImage(device, &createInfo, nullptr, &image),
+        DAWN_TRY(CheckVkSuccess(mDevice->fn.CreateImage(device, &createInfo, nullptr, &*image),
                                 "CreateImage"));
         return image;
     }
diff --git a/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp b/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp
index a6bb2fa..a8bc181 100644
--- a/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp
+++ b/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp
@@ -130,7 +130,7 @@
 
         VkDeviceMemory allocatedMemory = VK_NULL_HANDLE;
         DAWN_TRY(CheckVkSuccess(mDevice->fn.AllocateMemory(mDevice->GetVkDevice(), &allocateInfo,
-                                                           nullptr, &allocatedMemory),
+                                                           nullptr, &*allocatedMemory),
                                 "vkAllocateMemory"));
         return allocatedMemory;
     }
@@ -146,7 +146,7 @@
 
         VkImage image;
         DAWN_TRY(CheckVkSuccess(
-            mDevice->fn.CreateImage(mDevice->GetVkDevice(), &createInfo, nullptr, &image),
+            mDevice->fn.CreateImage(mDevice->GetVkDevice(), &createInfo, nullptr, &*image),
             "CreateImage"));
         return image;
     }
diff --git a/src/dawn_native/vulkan/external_memory/MemoryServiceZirconHandle.cpp b/src/dawn_native/vulkan/external_memory/MemoryServiceZirconHandle.cpp
index 8c70c67..09a5b71 100644
--- a/src/dawn_native/vulkan/external_memory/MemoryServiceZirconHandle.cpp
+++ b/src/dawn_native/vulkan/external_memory/MemoryServiceZirconHandle.cpp
@@ -130,7 +130,7 @@
 
         VkDeviceMemory allocatedMemory = VK_NULL_HANDLE;
         DAWN_TRY(CheckVkSuccess(mDevice->fn.AllocateMemory(mDevice->GetVkDevice(), &allocateInfo,
-                                                           nullptr, &allocatedMemory),
+                                                           nullptr, &*allocatedMemory),
                                 "vkAllocateMemory"));
         return allocatedMemory;
     }
@@ -146,7 +146,7 @@
 
         VkImage image;
         DAWN_TRY(CheckVkSuccess(
-            mDevice->fn.CreateImage(mDevice->GetVkDevice(), &createInfo, nullptr, &image),
+            mDevice->fn.CreateImage(mDevice->GetVkDevice(), &createInfo, nullptr, &*image),
             "CreateImage"));
         return image;
     }
diff --git a/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceOpaqueFD.cpp b/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceOpaqueFD.cpp
index ea7bf47..3e1d3f0 100644
--- a/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceOpaqueFD.cpp
+++ b/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceOpaqueFD.cpp
@@ -72,7 +72,7 @@
         info.flags = 0;
 
         DAWN_TRY(CheckVkSuccess(
-            mDevice->fn.CreateSemaphore(mDevice->GetVkDevice(), &info, nullptr, &semaphore),
+            mDevice->fn.CreateSemaphore(mDevice->GetVkDevice(), &info, nullptr, &*semaphore),
             "vkCreateSemaphore"));
 
         VkImportSemaphoreFdInfoKHR importSemaphoreFdInfo;
@@ -109,7 +109,7 @@
         VkSemaphore signalSemaphore;
         DAWN_TRY(
             CheckVkSuccess(mDevice->fn.CreateSemaphore(mDevice->GetVkDevice(), &semaphoreCreateInfo,
-                                                       nullptr, &signalSemaphore),
+                                                       nullptr, &*signalSemaphore),
                            "vkCreateSemaphore"));
         return signalSemaphore;
     }
diff --git a/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceZirconHandle.cpp b/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceZirconHandle.cpp
index 81fb9fd..5ea53f7 100644
--- a/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceZirconHandle.cpp
+++ b/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceZirconHandle.cpp
@@ -72,7 +72,7 @@
         info.flags = 0;
 
         DAWN_TRY(CheckVkSuccess(
-            mDevice->fn.CreateSemaphore(mDevice->GetVkDevice(), &info, nullptr, &semaphore),
+            mDevice->fn.CreateSemaphore(mDevice->GetVkDevice(), &info, nullptr, &*semaphore),
             "vkCreateSemaphore"));
 
         VkImportSemaphoreZirconHandleInfoFUCHSIA importSempahoreHandleInfo;
@@ -112,7 +112,7 @@
         VkSemaphore signalSemaphore;
         DAWN_TRY(
             CheckVkSuccess(mDevice->fn.CreateSemaphore(mDevice->GetVkDevice(), &semaphoreCreateInfo,
-                                                       nullptr, &signalSemaphore),
+                                                       nullptr, &*signalSemaphore),
                            "vkCreateSemaphore"));
         return signalSemaphore;
     }
diff --git a/src/include/dawn_native/VulkanBackend.h b/src/include/dawn_native/VulkanBackend.h
index 30dbb05..005a655 100644
--- a/src/include/dawn_native/VulkanBackend.h
+++ b/src/include/dawn_native/VulkanBackend.h
@@ -47,8 +47,8 @@
 
     DAWN_NATIVE_EXPORT PFN_vkVoidFunction GetInstanceProcAddr(WGPUDevice device, const char* pName);
 
-    DAWN_NATIVE_EXPORT DawnSwapChainImplementation CreateNativeSwapChainImpl(WGPUDevice device,
-                                                                             VkSurfaceKHR surface);
+    DAWN_NATIVE_EXPORT DawnSwapChainImplementation
+    CreateNativeSwapChainImpl(WGPUDevice device, ::VkSurfaceKHR surface);
     DAWN_NATIVE_EXPORT WGPUTextureFormat
     GetNativeSwapChainPreferredFormat(const DawnSwapChainImplementation* swapChain);
 
diff --git a/src/tests/DawnTest.h b/src/tests/DawnTest.h
index 677f670..9cdfd3e 100644
--- a/src/tests/DawnTest.h
+++ b/src/tests/DawnTest.h
@@ -32,30 +32,30 @@
 // them.
 #define EXPECT_BUFFER_U32_EQ(expected, buffer, offset)                         \
     AddBufferExpectation(__FILE__, __LINE__, buffer, offset, sizeof(uint32_t), \
-                         new detail::ExpectEq<uint32_t>(expected))
+                         new ::detail::ExpectEq<uint32_t>(expected))
 
 #define EXPECT_BUFFER_U32_RANGE_EQ(expected, buffer, offset, count)                    \
     AddBufferExpectation(__FILE__, __LINE__, buffer, offset, sizeof(uint32_t) * count, \
-                         new detail::ExpectEq<uint32_t>(expected, count))
+                         new ::detail::ExpectEq<uint32_t>(expected, count))
 
 // Test a pixel of the mip level 0 of a 2D texture.
 #define EXPECT_PIXEL_RGBA8_EQ(expected, texture, x, y)                                  \
     AddTextureExpectation(__FILE__, __LINE__, texture, x, y, 1, 1, 0, 0, sizeof(RGBA8), \
-                          new detail::ExpectEq<RGBA8>(expected))
+                          new ::detail::ExpectEq<RGBA8>(expected))
 
 #define EXPECT_TEXTURE_RGBA8_EQ(expected, texture, x, y, width, height, level, slice)     \
     AddTextureExpectation(__FILE__, __LINE__, texture, x, y, width, height, level, slice, \
                           sizeof(RGBA8),                                                  \
-                          new detail::ExpectEq<RGBA8>(expected, (width) * (height)))
+                          new ::detail::ExpectEq<RGBA8>(expected, (width) * (height)))
 
 #define EXPECT_PIXEL_FLOAT_EQ(expected, texture, x, y)                                  \
     AddTextureExpectation(__FILE__, __LINE__, texture, x, y, 1, 1, 0, 0, sizeof(float), \
-                          new detail::ExpectEq<float>(expected))
+                          new ::detail::ExpectEq<float>(expected))
 
 #define EXPECT_TEXTURE_FLOAT_EQ(expected, texture, x, y, width, height, level, slice)     \
     AddTextureExpectation(__FILE__, __LINE__, texture, x, y, width, height, level, slice, \
                           sizeof(float),                                                  \
-                          new detail::ExpectEq<float>(expected, (width) * (height)))
+                          new ::detail::ExpectEq<float>(expected, (width) * (height)))
 
 // Should only be used to test validation of function that can't be tested by regular validation
 // tests;
diff --git a/src/tests/white_box/VulkanImageWrappingTests.cpp b/src/tests/white_box/VulkanImageWrappingTests.cpp
index de7d8fe..1db96439 100644
--- a/src/tests/white_box/VulkanImageWrappingTests.cpp
+++ b/src/tests/white_box/VulkanImageWrappingTests.cpp
@@ -25,993 +25,1008 @@
 #include "utils/SystemUtils.h"
 #include "utils/WGPUHelpers.h"
 
-namespace {
+namespace dawn_native { namespace vulkan {
 
-    class VulkanImageWrappingTestBase : public DawnTest {
+    namespace {
+
+        class VulkanImageWrappingTestBase : public DawnTest {
+          public:
+            void TestSetUp() override {
+                if (UsesWire()) {
+                    return;
+                }
+
+                deviceVk = reinterpret_cast<dawn_native::vulkan::Device*>(device.Get());
+            }
+
+            // Creates a VkImage with external memory
+            ::VkResult CreateImage(dawn_native::vulkan::Device* deviceVk,
+                                   uint32_t width,
+                                   uint32_t height,
+                                   VkFormat format,
+                                   VkImage* image) {
+                VkExternalMemoryImageCreateInfoKHR externalInfo;
+                externalInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO_KHR;
+                externalInfo.pNext = nullptr;
+                externalInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;
+
+                auto usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
+                             VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+
+                VkImageCreateInfo createInfo;
+                createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+                createInfo.pNext = &externalInfo;
+                createInfo.flags = VK_IMAGE_CREATE_ALIAS_BIT_KHR;
+                createInfo.imageType = VK_IMAGE_TYPE_2D;
+                createInfo.format = format;
+                createInfo.extent = {width, height, 1};
+                createInfo.mipLevels = 1;
+                createInfo.arrayLayers = 1;
+                createInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+                createInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+                createInfo.usage = usage;
+                createInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+                createInfo.queueFamilyIndexCount = 0;
+                createInfo.pQueueFamilyIndices = nullptr;
+                createInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+
+                return deviceVk->fn.CreateImage(deviceVk->GetVkDevice(), &createInfo, nullptr,
+                                                &**image);
+            }
+
+            // Allocates memory for an image
+            ::VkResult AllocateMemory(dawn_native::vulkan::Device* deviceVk,
+                                      VkImage handle,
+                                      VkDeviceMemory* allocation,
+                                      VkDeviceSize* allocationSize,
+                                      uint32_t* memoryTypeIndex) {
+                // Create the image memory and associate it with the container
+                VkMemoryRequirements requirements;
+                deviceVk->fn.GetImageMemoryRequirements(deviceVk->GetVkDevice(), handle,
+                                                        &requirements);
+
+                // Import memory from file descriptor
+                VkExportMemoryAllocateInfoKHR externalInfo;
+                externalInfo.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO_KHR;
+                externalInfo.pNext = nullptr;
+                externalInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;
+
+                int bestType = deviceVk->GetResourceMemoryAllocatorForTesting()->FindBestTypeIndex(
+                    requirements, false);
+                VkMemoryAllocateInfo allocateInfo;
+                allocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+                allocateInfo.pNext = &externalInfo;
+                allocateInfo.allocationSize = requirements.size;
+                allocateInfo.memoryTypeIndex = static_cast<uint32_t>(bestType);
+
+                *allocationSize = allocateInfo.allocationSize;
+                *memoryTypeIndex = allocateInfo.memoryTypeIndex;
+
+                return deviceVk->fn.AllocateMemory(deviceVk->GetVkDevice(), &allocateInfo, nullptr,
+                                                   &**allocation);
+            }
+
+            // Binds memory to an image
+            ::VkResult BindMemory(dawn_native::vulkan::Device* deviceVk,
+                                  VkImage handle,
+                                  VkDeviceMemory* memory) {
+                return deviceVk->fn.BindImageMemory(deviceVk->GetVkDevice(), handle, *memory, 0);
+            }
+
+            // Extracts a file descriptor representing memory on a device
+            int GetMemoryFd(dawn_native::vulkan::Device* deviceVk, VkDeviceMemory memory) {
+                VkMemoryGetFdInfoKHR getFdInfo;
+                getFdInfo.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR;
+                getFdInfo.pNext = nullptr;
+                getFdInfo.memory = memory;
+                getFdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;
+
+                int memoryFd = -1;
+                deviceVk->fn.GetMemoryFdKHR(deviceVk->GetVkDevice(), &getFdInfo, &memoryFd);
+
+                EXPECT_GE(memoryFd, 0) << "Failed to get file descriptor for external memory";
+                return memoryFd;
+            }
+
+            // Prepares and exports memory for an image on a given device
+            void CreateBindExportImage(dawn_native::vulkan::Device* deviceVk,
+                                       uint32_t width,
+                                       uint32_t height,
+                                       VkFormat format,
+                                       VkImage* handle,
+                                       VkDeviceMemory* allocation,
+                                       VkDeviceSize* allocationSize,
+                                       uint32_t* memoryTypeIndex,
+                                       int* memoryFd) {
+                ::VkResult result = CreateImage(deviceVk, width, height, format, handle);
+                EXPECT_EQ(result, VK_SUCCESS) << "Failed to create external image";
+
+                ::VkResult resultBool =
+                    AllocateMemory(deviceVk, *handle, allocation, allocationSize, memoryTypeIndex);
+                EXPECT_EQ(resultBool, VK_SUCCESS) << "Failed to allocate external memory";
+
+                result = BindMemory(deviceVk, *handle, allocation);
+                EXPECT_EQ(result, VK_SUCCESS) << "Failed to bind image memory";
+
+                *memoryFd = GetMemoryFd(deviceVk, *allocation);
+            }
+
+            // Wraps a vulkan image from external memory
+            wgpu::Texture WrapVulkanImage(wgpu::Device device,
+                                          const wgpu::TextureDescriptor* textureDescriptor,
+                                          int memoryFd,
+                                          VkDeviceSize allocationSize,
+                                          uint32_t memoryTypeIndex,
+                                          std::vector<int> waitFDs,
+                                          bool isCleared = true,
+                                          bool expectValid = true) {
+                dawn_native::vulkan::ExternalImageDescriptorOpaqueFD descriptor;
+                descriptor.cTextureDescriptor =
+                    reinterpret_cast<const WGPUTextureDescriptor*>(textureDescriptor);
+                descriptor.isCleared = isCleared;
+                descriptor.allocationSize = allocationSize;
+                descriptor.memoryTypeIndex = memoryTypeIndex;
+                descriptor.memoryFD = memoryFd;
+                descriptor.waitFDs = waitFDs;
+
+                WGPUTexture texture =
+                    dawn_native::vulkan::WrapVulkanImage(device.Get(), &descriptor);
+
+                if (expectValid) {
+                    EXPECT_NE(texture, nullptr) << "Failed to wrap image, are external memory / "
+                                                   "semaphore extensions supported?";
+                } else {
+                    EXPECT_EQ(texture, nullptr);
+                }
+
+                return wgpu::Texture::Acquire(texture);
+            }
+
+            // Exports the signal from a wrapped texture and ignores it
+            // We have to export the signal before destroying the wrapped texture else it's an
+            // assertion failure
+            void IgnoreSignalSemaphore(wgpu::Device device, wgpu::Texture wrappedTexture) {
+                int fd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(device.Get(),
+                                                                            wrappedTexture.Get());
+                ASSERT_NE(fd, -1);
+                close(fd);
+            }
+
+          protected:
+            dawn_native::vulkan::Device* deviceVk;
+        };
+
+    }  // anonymous namespace
+
+    class VulkanImageWrappingValidationTests : public VulkanImageWrappingTestBase {
       public:
         void TestSetUp() override {
+            VulkanImageWrappingTestBase::TestSetUp();
             if (UsesWire()) {
                 return;
             }
 
-            deviceVk = reinterpret_cast<dawn_native::vulkan::Device*>(device.Get());
+            CreateBindExportImage(deviceVk, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, &defaultImage,
+                                  &defaultAllocation, &defaultAllocationSize,
+                                  &defaultMemoryTypeIndex, &defaultFd);
+            defaultDescriptor.dimension = wgpu::TextureDimension::e2D;
+            defaultDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;
+            defaultDescriptor.size = {1, 1, 1};
+            defaultDescriptor.sampleCount = 1;
+            defaultDescriptor.arrayLayerCount = 1;
+            defaultDescriptor.mipLevelCount = 1;
+            defaultDescriptor.usage = wgpu::TextureUsage::OutputAttachment |
+                                      wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
         }
 
-        // Creates a VkImage with external memory
-        VkResult CreateImage(dawn_native::vulkan::Device* deviceVk,
-                             uint32_t width,
-                             uint32_t height,
-                             VkFormat format,
-                             VkImage* image) {
-            VkExternalMemoryImageCreateInfoKHR externalInfo;
-            externalInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO_KHR;
-            externalInfo.pNext = nullptr;
-            externalInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;
-
-            auto usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
-                         VK_IMAGE_USAGE_TRANSFER_DST_BIT;
-
-            VkImageCreateInfo createInfo;
-            createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
-            createInfo.pNext = &externalInfo;
-            createInfo.flags = VK_IMAGE_CREATE_ALIAS_BIT_KHR;
-            createInfo.imageType = VK_IMAGE_TYPE_2D;
-            createInfo.format = format;
-            createInfo.extent = {width, height, 1};
-            createInfo.mipLevels = 1;
-            createInfo.arrayLayers = 1;
-            createInfo.samples = VK_SAMPLE_COUNT_1_BIT;
-            createInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
-            createInfo.usage = usage;
-            createInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
-            createInfo.queueFamilyIndexCount = 0;
-            createInfo.pQueueFamilyIndices = nullptr;
-            createInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
-
-            return deviceVk->fn.CreateImage(deviceVk->GetVkDevice(), &createInfo, nullptr, image);
-        }
-
-        // Allocates memory for an image
-        VkResult AllocateMemory(dawn_native::vulkan::Device* deviceVk,
-                                VkImage handle,
-                                VkDeviceMemory* allocation,
-                                VkDeviceSize* allocationSize,
-                                uint32_t* memoryTypeIndex) {
-            // Create the image memory and associate it with the container
-            VkMemoryRequirements requirements;
-            deviceVk->fn.GetImageMemoryRequirements(deviceVk->GetVkDevice(), handle, &requirements);
-
-            // Import memory from file descriptor
-            VkExportMemoryAllocateInfoKHR externalInfo;
-            externalInfo.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO_KHR;
-            externalInfo.pNext = nullptr;
-            externalInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;
-
-            int bestType = deviceVk->GetResourceMemoryAllocatorForTesting()->FindBestTypeIndex(
-                requirements, false);
-            VkMemoryAllocateInfo allocateInfo;
-            allocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
-            allocateInfo.pNext = &externalInfo;
-            allocateInfo.allocationSize = requirements.size;
-            allocateInfo.memoryTypeIndex = static_cast<uint32_t>(bestType);
-
-            *allocationSize = allocateInfo.allocationSize;
-            *memoryTypeIndex = allocateInfo.memoryTypeIndex;
-
-            return deviceVk->fn.AllocateMemory(deviceVk->GetVkDevice(), &allocateInfo, nullptr,
-                                               allocation);
-        }
-
-        // Binds memory to an image
-        VkResult BindMemory(dawn_native::vulkan::Device* deviceVk,
-                            VkImage handle,
-                            VkDeviceMemory* memory) {
-            return deviceVk->fn.BindImageMemory(deviceVk->GetVkDevice(), handle, *memory, 0);
-        }
-
-        // Extracts a file descriptor representing memory on a device
-        int GetMemoryFd(dawn_native::vulkan::Device* deviceVk, VkDeviceMemory memory) {
-            VkMemoryGetFdInfoKHR getFdInfo;
-            getFdInfo.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR;
-            getFdInfo.pNext = nullptr;
-            getFdInfo.memory = memory;
-            getFdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;
-
-            int memoryFd = -1;
-            deviceVk->fn.GetMemoryFdKHR(deviceVk->GetVkDevice(), &getFdInfo, &memoryFd);
-
-            EXPECT_GE(memoryFd, 0) << "Failed to get file descriptor for external memory";
-            return memoryFd;
-        }
-
-        // Prepares and exports memory for an image on a given device
-        void CreateBindExportImage(dawn_native::vulkan::Device* deviceVk,
-                                   uint32_t width,
-                                   uint32_t height,
-                                   VkFormat format,
-                                   VkImage* handle,
-                                   VkDeviceMemory* allocation,
-                                   VkDeviceSize* allocationSize,
-                                   uint32_t* memoryTypeIndex,
-                                   int* memoryFd) {
-            VkResult result = CreateImage(deviceVk, width, height, format, handle);
-            EXPECT_EQ(result, VK_SUCCESS) << "Failed to create external image";
-
-            VkResult resultBool =
-                AllocateMemory(deviceVk, *handle, allocation, allocationSize, memoryTypeIndex);
-            EXPECT_EQ(resultBool, VK_SUCCESS) << "Failed to allocate external memory";
-
-            result = BindMemory(deviceVk, *handle, allocation);
-            EXPECT_EQ(result, VK_SUCCESS) << "Failed to bind image memory";
-
-            *memoryFd = GetMemoryFd(deviceVk, *allocation);
-        }
-
-        // Wraps a vulkan image from external memory
-        wgpu::Texture WrapVulkanImage(wgpu::Device device,
-                                      const wgpu::TextureDescriptor* textureDescriptor,
-                                      int memoryFd,
-                                      VkDeviceSize allocationSize,
-                                      uint32_t memoryTypeIndex,
-                                      std::vector<int> waitFDs,
-                                      bool isCleared = true,
-                                      bool expectValid = true) {
-            dawn_native::vulkan::ExternalImageDescriptorOpaqueFD descriptor;
-            descriptor.cTextureDescriptor =
-                reinterpret_cast<const WGPUTextureDescriptor*>(textureDescriptor);
-            descriptor.isCleared = isCleared;
-            descriptor.allocationSize = allocationSize;
-            descriptor.memoryTypeIndex = memoryTypeIndex;
-            descriptor.memoryFD = memoryFd;
-            descriptor.waitFDs = waitFDs;
-
-            WGPUTexture texture = dawn_native::vulkan::WrapVulkanImage(device.Get(), &descriptor);
-
-            if (expectValid) {
-                EXPECT_NE(texture, nullptr) << "Failed to wrap image, are external memory / "
-                                               "semaphore extensions supported?";
-            } else {
-                EXPECT_EQ(texture, nullptr);
+        void TearDown() override {
+            if (UsesWire()) {
+                VulkanImageWrappingTestBase::TearDown();
+                return;
             }
 
-            return wgpu::Texture::Acquire(texture);
-        }
-
-        // Exports the signal from a wrapped texture and ignores it
-        // We have to export the signal before destroying the wrapped texture else it's an assertion
-        // failure
-        void IgnoreSignalSemaphore(wgpu::Device device, wgpu::Texture wrappedTexture) {
-            int fd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(device.Get(),
-                                                                        wrappedTexture.Get());
-            ASSERT_NE(fd, -1);
-            close(fd);
+            deviceVk->GetFencedDeleter()->DeleteWhenUnused(defaultImage);
+            deviceVk->GetFencedDeleter()->DeleteWhenUnused(defaultAllocation);
+            VulkanImageWrappingTestBase::TearDown();
         }
 
       protected:
-        dawn_native::vulkan::Device* deviceVk;
+        wgpu::TextureDescriptor defaultDescriptor;
+        VkImage defaultImage;
+        VkDeviceMemory defaultAllocation;
+        VkDeviceSize defaultAllocationSize;
+        uint32_t defaultMemoryTypeIndex;
+        int defaultFd;
     };
 
-}  // anonymous namespace
-
-class VulkanImageWrappingValidationTests : public VulkanImageWrappingTestBase {
-  public:
-    void TestSetUp() override {
-        VulkanImageWrappingTestBase::TestSetUp();
-        if (UsesWire()) {
-            return;
-        }
-
-        CreateBindExportImage(deviceVk, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, &defaultImage,
-                              &defaultAllocation, &defaultAllocationSize, &defaultMemoryTypeIndex,
-                              &defaultFd);
-        defaultDescriptor.dimension = wgpu::TextureDimension::e2D;
-        defaultDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;
-        defaultDescriptor.size = {1, 1, 1};
-        defaultDescriptor.sampleCount = 1;
-        defaultDescriptor.arrayLayerCount = 1;
-        defaultDescriptor.mipLevelCount = 1;
-        defaultDescriptor.usage = wgpu::TextureUsage::OutputAttachment |
-                                  wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
+    // Test no error occurs if the import is valid
+    TEST_P(VulkanImageWrappingValidationTests, SuccessfulImport) {
+        DAWN_SKIP_TEST_IF(UsesWire());
+        wgpu::Texture texture =
+            WrapVulkanImage(device, &defaultDescriptor, defaultFd, defaultAllocationSize,
+                            defaultMemoryTypeIndex, {}, true, true);
+        EXPECT_NE(texture.Get(), nullptr);
+        IgnoreSignalSemaphore(device, texture);
     }
 
-    void TearDown() override {
-        if (UsesWire()) {
+    // Test an error occurs if the texture descriptor is missing
+    TEST_P(VulkanImageWrappingValidationTests, MissingTextureDescriptor) {
+        DAWN_SKIP_TEST_IF(UsesWire());
+        ASSERT_DEVICE_ERROR(wgpu::Texture texture =
+                                WrapVulkanImage(device, nullptr, defaultFd, defaultAllocationSize,
+                                                defaultMemoryTypeIndex, {}, true, false));
+        EXPECT_EQ(texture.Get(), nullptr);
+    }
+
+    // Test an error occurs if the texture descriptor is invalid
+    TEST_P(VulkanImageWrappingValidationTests, InvalidTextureDescriptor) {
+        DAWN_SKIP_TEST_IF(UsesWire());
+        wgpu::ChainedStruct chainedDescriptor;
+        defaultDescriptor.nextInChain = &chainedDescriptor;
+
+        ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(
+                                device, &defaultDescriptor, defaultFd, defaultAllocationSize,
+                                defaultMemoryTypeIndex, {}, true, false));
+        EXPECT_EQ(texture.Get(), nullptr);
+    }
+
+    // Test an error occurs if the descriptor dimension isn't 2D
+    TEST_P(VulkanImageWrappingValidationTests, InvalidTextureDimension) {
+        DAWN_SKIP_TEST_IF(UsesWire());
+        defaultDescriptor.dimension = wgpu::TextureDimension::e1D;
+
+        ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(
+                                device, &defaultDescriptor, defaultFd, defaultAllocationSize,
+                                defaultMemoryTypeIndex, {}, true, false));
+        EXPECT_EQ(texture.Get(), nullptr);
+    }
+
+    // Test an error occurs if the descriptor mip level count isn't 1
+    TEST_P(VulkanImageWrappingValidationTests, InvalidMipLevelCount) {
+        DAWN_SKIP_TEST_IF(UsesWire());
+        defaultDescriptor.mipLevelCount = 2;
+
+        ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(
+                                device, &defaultDescriptor, defaultFd, defaultAllocationSize,
+                                defaultMemoryTypeIndex, {}, true, false));
+        EXPECT_EQ(texture.Get(), nullptr);
+    }
+
+    // Test an error occurs if the descriptor array layer count isn't 1
+    TEST_P(VulkanImageWrappingValidationTests, InvalidArrayLayerCount) {
+        DAWN_SKIP_TEST_IF(UsesWire());
+        defaultDescriptor.arrayLayerCount = 2;
+
+        ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(
+                                device, &defaultDescriptor, defaultFd, defaultAllocationSize,
+                                defaultMemoryTypeIndex, {}, true, false));
+        EXPECT_EQ(texture.Get(), nullptr);
+    }
+
+    // Test an error occurs if the descriptor sample count isn't 1
+    TEST_P(VulkanImageWrappingValidationTests, InvalidSampleCount) {
+        DAWN_SKIP_TEST_IF(UsesWire());
+        defaultDescriptor.sampleCount = 4;
+
+        ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(
+                                device, &defaultDescriptor, defaultFd, defaultAllocationSize,
+                                defaultMemoryTypeIndex, {}, true, false));
+        EXPECT_EQ(texture.Get(), nullptr);
+    }
+
+    // Test an error occurs if we try to export the signal semaphore twice
+    TEST_P(VulkanImageWrappingValidationTests, DoubleSignalSemaphoreExport) {
+        DAWN_SKIP_TEST_IF(UsesWire());
+        wgpu::Texture texture =
+            WrapVulkanImage(device, &defaultDescriptor, defaultFd, defaultAllocationSize,
+                            defaultMemoryTypeIndex, {}, true, true);
+        ASSERT_NE(texture.Get(), nullptr);
+        IgnoreSignalSemaphore(device, texture);
+        ASSERT_DEVICE_ERROR(int fd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(
+                                device.Get(), texture.Get()));
+        ASSERT_EQ(fd, -1);
+    }
+
+    // Test an error occurs if we try to export the signal semaphore from a normal texture
+    TEST_P(VulkanImageWrappingValidationTests, NormalTextureSignalSemaphoreExport) {
+        DAWN_SKIP_TEST_IF(UsesWire());
+        wgpu::Texture texture = device.CreateTexture(&defaultDescriptor);
+        ASSERT_NE(texture.Get(), nullptr);
+        ASSERT_DEVICE_ERROR(int fd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(
+                                device.Get(), texture.Get()));
+        ASSERT_EQ(fd, -1);
+    }
+
+    // Test an error occurs if we try to export the signal semaphore from a destroyed texture
+    TEST_P(VulkanImageWrappingValidationTests, DestroyedTextureSignalSemaphoreExport) {
+        DAWN_SKIP_TEST_IF(UsesWire());
+        wgpu::Texture texture = device.CreateTexture(&defaultDescriptor);
+        ASSERT_NE(texture.Get(), nullptr);
+        texture.Destroy();
+        ASSERT_DEVICE_ERROR(int fd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(
+                                device.Get(), texture.Get()));
+        ASSERT_EQ(fd, -1);
+    }
+
+    // Fixture to test using external memory textures through different usages.
+    // These tests are skipped if the harness is using the wire.
+    class VulkanImageWrappingUsageTests : public VulkanImageWrappingTestBase {
+      public:
+        void TestSetUp() override {
+            VulkanImageWrappingTestBase::TestSetUp();
+            if (UsesWire()) {
+                return;
+            }
+
+            // Create another device based on the original
+            backendAdapter =
+                reinterpret_cast<dawn_native::vulkan::Adapter*>(deviceVk->GetAdapter());
+            deviceDescriptor.forceEnabledToggles = GetParam().forceEnabledWorkarounds;
+            deviceDescriptor.forceDisabledToggles = GetParam().forceDisabledWorkarounds;
+
+            secondDeviceVk = reinterpret_cast<dawn_native::vulkan::Device*>(
+                backendAdapter->CreateDevice(&deviceDescriptor));
+            secondDevice = wgpu::Device::Acquire(reinterpret_cast<WGPUDevice>(secondDeviceVk));
+
+            CreateBindExportImage(deviceVk, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, &defaultImage,
+                                  &defaultAllocation, &defaultAllocationSize,
+                                  &defaultMemoryTypeIndex, &defaultFd);
+            defaultDescriptor.dimension = wgpu::TextureDimension::e2D;
+            defaultDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;
+            defaultDescriptor.size = {1, 1, 1};
+            defaultDescriptor.sampleCount = 1;
+            defaultDescriptor.arrayLayerCount = 1;
+            defaultDescriptor.mipLevelCount = 1;
+            defaultDescriptor.usage = wgpu::TextureUsage::OutputAttachment |
+                                      wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
+        }
+
+        void TearDown() override {
+            if (UsesWire()) {
+                VulkanImageWrappingTestBase::TearDown();
+                return;
+            }
+
+            deviceVk->GetFencedDeleter()->DeleteWhenUnused(defaultImage);
+            deviceVk->GetFencedDeleter()->DeleteWhenUnused(defaultAllocation);
             VulkanImageWrappingTestBase::TearDown();
-            return;
         }
 
-        deviceVk->GetFencedDeleter()->DeleteWhenUnused(defaultImage);
-        deviceVk->GetFencedDeleter()->DeleteWhenUnused(defaultAllocation);
-        VulkanImageWrappingTestBase::TearDown();
-    }
+      protected:
+        wgpu::Device secondDevice;
+        dawn_native::vulkan::Device* secondDeviceVk;
 
-  protected:
-    wgpu::TextureDescriptor defaultDescriptor;
-    VkImage defaultImage;
-    VkDeviceMemory defaultAllocation;
-    VkDeviceSize defaultAllocationSize;
-    uint32_t defaultMemoryTypeIndex;
-    int defaultFd;
-};
+        dawn_native::vulkan::Adapter* backendAdapter;
+        dawn_native::DeviceDescriptor deviceDescriptor;
 
-// Test no error occurs if the import is valid
-TEST_P(VulkanImageWrappingValidationTests, SuccessfulImport) {
-    DAWN_SKIP_TEST_IF(UsesWire());
-    wgpu::Texture texture =
-        WrapVulkanImage(device, &defaultDescriptor, defaultFd, defaultAllocationSize,
-                        defaultMemoryTypeIndex, {}, true, true);
-    EXPECT_NE(texture.Get(), nullptr);
-    IgnoreSignalSemaphore(device, texture);
-}
+        wgpu::TextureDescriptor defaultDescriptor;
+        VkImage defaultImage;
+        VkDeviceMemory defaultAllocation;
+        VkDeviceSize defaultAllocationSize;
+        uint32_t defaultMemoryTypeIndex;
+        int defaultFd;
 
-// Test an error occurs if the texture descriptor is missing
-TEST_P(VulkanImageWrappingValidationTests, MissingTextureDescriptor) {
-    DAWN_SKIP_TEST_IF(UsesWire());
-    ASSERT_DEVICE_ERROR(wgpu::Texture texture =
-                            WrapVulkanImage(device, nullptr, defaultFd, defaultAllocationSize,
-                                            defaultMemoryTypeIndex, {}, true, false));
-    EXPECT_EQ(texture.Get(), nullptr);
-}
+        // Clear a texture on a given device
+        void ClearImage(wgpu::Device device, wgpu::Texture wrappedTexture, wgpu::Color clearColor) {
+            wgpu::TextureView wrappedView = wrappedTexture.CreateView();
 
-// Test an error occurs if the texture descriptor is invalid
-TEST_P(VulkanImageWrappingValidationTests, InvalidTextureDescriptor) {
-    DAWN_SKIP_TEST_IF(UsesWire());
-    wgpu::ChainedStruct chainedDescriptor;
-    defaultDescriptor.nextInChain = &chainedDescriptor;
+            // Submit a clear operation
+            utils::ComboRenderPassDescriptor renderPassDescriptor({wrappedView}, {});
+            renderPassDescriptor.cColorAttachments[0].clearColor = clearColor;
 
-    ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(
-                            device, &defaultDescriptor, defaultFd, defaultAllocationSize,
-                            defaultMemoryTypeIndex, {}, true, false));
-    EXPECT_EQ(texture.Get(), nullptr);
-}
+            wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+            wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDescriptor);
+            pass.EndPass();
 
-// Test an error occurs if the descriptor dimension isn't 2D
-TEST_P(VulkanImageWrappingValidationTests, InvalidTextureDimension) {
-    DAWN_SKIP_TEST_IF(UsesWire());
-    defaultDescriptor.dimension = wgpu::TextureDimension::e1D;
+            wgpu::CommandBuffer commands = encoder.Finish();
 
-    ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(
-                            device, &defaultDescriptor, defaultFd, defaultAllocationSize,
-                            defaultMemoryTypeIndex, {}, true, false));
-    EXPECT_EQ(texture.Get(), nullptr);
-}
-
-// Test an error occurs if the descriptor mip level count isn't 1
-TEST_P(VulkanImageWrappingValidationTests, InvalidMipLevelCount) {
-    DAWN_SKIP_TEST_IF(UsesWire());
-    defaultDescriptor.mipLevelCount = 2;
-
-    ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(
-                            device, &defaultDescriptor, defaultFd, defaultAllocationSize,
-                            defaultMemoryTypeIndex, {}, true, false));
-    EXPECT_EQ(texture.Get(), nullptr);
-}
-
-// Test an error occurs if the descriptor array layer count isn't 1
-TEST_P(VulkanImageWrappingValidationTests, InvalidArrayLayerCount) {
-    DAWN_SKIP_TEST_IF(UsesWire());
-    defaultDescriptor.arrayLayerCount = 2;
-
-    ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(
-                            device, &defaultDescriptor, defaultFd, defaultAllocationSize,
-                            defaultMemoryTypeIndex, {}, true, false));
-    EXPECT_EQ(texture.Get(), nullptr);
-}
-
-// Test an error occurs if the descriptor sample count isn't 1
-TEST_P(VulkanImageWrappingValidationTests, InvalidSampleCount) {
-    DAWN_SKIP_TEST_IF(UsesWire());
-    defaultDescriptor.sampleCount = 4;
-
-    ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(
-                            device, &defaultDescriptor, defaultFd, defaultAllocationSize,
-                            defaultMemoryTypeIndex, {}, true, false));
-    EXPECT_EQ(texture.Get(), nullptr);
-}
-
-// Test an error occurs if we try to export the signal semaphore twice
-TEST_P(VulkanImageWrappingValidationTests, DoubleSignalSemaphoreExport) {
-    DAWN_SKIP_TEST_IF(UsesWire());
-    wgpu::Texture texture =
-        WrapVulkanImage(device, &defaultDescriptor, defaultFd, defaultAllocationSize,
-                        defaultMemoryTypeIndex, {}, true, true);
-    ASSERT_NE(texture.Get(), nullptr);
-    IgnoreSignalSemaphore(device, texture);
-    ASSERT_DEVICE_ERROR(
-        int fd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(device.Get(), texture.Get()));
-    ASSERT_EQ(fd, -1);
-}
-
-// Test an error occurs if we try to export the signal semaphore from a normal texture
-TEST_P(VulkanImageWrappingValidationTests, NormalTextureSignalSemaphoreExport) {
-    DAWN_SKIP_TEST_IF(UsesWire());
-    wgpu::Texture texture = device.CreateTexture(&defaultDescriptor);
-    ASSERT_NE(texture.Get(), nullptr);
-    ASSERT_DEVICE_ERROR(
-        int fd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(device.Get(), texture.Get()));
-    ASSERT_EQ(fd, -1);
-}
-
-// Test an error occurs if we try to export the signal semaphore from a destroyed texture
-TEST_P(VulkanImageWrappingValidationTests, DestroyedTextureSignalSemaphoreExport) {
-    DAWN_SKIP_TEST_IF(UsesWire());
-    wgpu::Texture texture = device.CreateTexture(&defaultDescriptor);
-    ASSERT_NE(texture.Get(), nullptr);
-    texture.Destroy();
-    ASSERT_DEVICE_ERROR(
-        int fd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(device.Get(), texture.Get()));
-    ASSERT_EQ(fd, -1);
-}
-
-// Fixture to test using external memory textures through different usages.
-// These tests are skipped if the harness is using the wire.
-class VulkanImageWrappingUsageTests : public VulkanImageWrappingTestBase {
-  public:
-    void TestSetUp() override {
-        VulkanImageWrappingTestBase::TestSetUp();
-        if (UsesWire()) {
-            return;
+            wgpu::Queue queue = device.CreateQueue();
+            queue.Submit(1, &commands);
         }
 
-        // Create another device based on the original
-        backendAdapter = reinterpret_cast<dawn_native::vulkan::Adapter*>(deviceVk->GetAdapter());
-        deviceDescriptor.forceEnabledToggles = GetParam().forceEnabledWorkarounds;
-        deviceDescriptor.forceDisabledToggles = GetParam().forceDisabledWorkarounds;
+        // Submits a 1x1x1 copy from source to destination
+        void SimpleCopyTextureToTexture(wgpu::Device device,
+                                        wgpu::Queue queue,
+                                        wgpu::Texture source,
+                                        wgpu::Texture destination) {
+            wgpu::TextureCopyView copySrc;
+            copySrc.texture = source;
+            copySrc.mipLevel = 0;
+            copySrc.arrayLayer = 0;
+            copySrc.origin = {0, 0, 0};
 
-        secondDeviceVk = reinterpret_cast<dawn_native::vulkan::Device*>(
-            backendAdapter->CreateDevice(&deviceDescriptor));
-        secondDevice = wgpu::Device::Acquire(reinterpret_cast<WGPUDevice>(secondDeviceVk));
+            wgpu::TextureCopyView copyDst;
+            copyDst.texture = destination;
+            copyDst.mipLevel = 0;
+            copyDst.arrayLayer = 0;
+            copyDst.origin = {0, 0, 0};
 
-        CreateBindExportImage(deviceVk, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, &defaultImage,
-                              &defaultAllocation, &defaultAllocationSize, &defaultMemoryTypeIndex,
-                              &defaultFd);
-        defaultDescriptor.dimension = wgpu::TextureDimension::e2D;
-        defaultDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;
-        defaultDescriptor.size = {1, 1, 1};
-        defaultDescriptor.sampleCount = 1;
-        defaultDescriptor.arrayLayerCount = 1;
-        defaultDescriptor.mipLevelCount = 1;
-        defaultDescriptor.usage = wgpu::TextureUsage::OutputAttachment |
-                                  wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
-    }
+            wgpu::Extent3D copySize = {1, 1, 1};
 
-    void TearDown() override {
-        if (UsesWire()) {
-            VulkanImageWrappingTestBase::TearDown();
-            return;
+            wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+            encoder.CopyTextureToTexture(&copySrc, &copyDst, &copySize);
+            wgpu::CommandBuffer commands = encoder.Finish();
+
+            queue.Submit(1, &commands);
         }
+    };
 
-        deviceVk->GetFencedDeleter()->DeleteWhenUnused(defaultImage);
-        deviceVk->GetFencedDeleter()->DeleteWhenUnused(defaultAllocation);
-        VulkanImageWrappingTestBase::TearDown();
+    // Clear an image in |secondDevice|
+    // Verify clear color is visible in |device|
+    TEST_P(VulkanImageWrappingUsageTests, ClearImageAcrossDevices) {
+        DAWN_SKIP_TEST_IF(UsesWire());
+
+        // Import the image on |secondDevice|
+        wgpu::Texture wrappedTexture =
+            WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
+                            defaultMemoryTypeIndex, {});
+
+        // Clear |wrappedTexture| on |secondDevice|
+        ClearImage(secondDevice, wrappedTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
+
+        int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
+                                                                          wrappedTexture.Get());
+
+        // Import the image to |device|, making sure we wait on signalFd
+        int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
+        wgpu::Texture nextWrappedTexture =
+            WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
+                            defaultMemoryTypeIndex, {signalFd});
+
+        // Verify |device| sees the changes from |secondDevice|
+        EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), nextWrappedTexture, 0, 0);
+
+        IgnoreSignalSemaphore(device, nextWrappedTexture);
     }
 
-  protected:
-    wgpu::Device secondDevice;
-    dawn_native::vulkan::Device* secondDeviceVk;
+    // Import texture to |device| and |secondDevice|
+    // Clear image in |secondDevice|
+    // Verify clear color is visible in |device|
+    // Verify the very first import into |device| also sees the change, since it should
+    // alias the same memory
+    TEST_P(VulkanImageWrappingUsageTests, ClearImageAcrossDevicesAliased) {
+        DAWN_SKIP_TEST_IF(UsesWire());
+        // Import the image on |device
+        wgpu::Texture wrappedTextureAlias =
+            WrapVulkanImage(device, &defaultDescriptor, defaultFd, defaultAllocationSize,
+                            defaultMemoryTypeIndex, {});
 
-    dawn_native::vulkan::Adapter* backendAdapter;
-    dawn_native::DeviceDescriptor deviceDescriptor;
+        int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
 
-    wgpu::TextureDescriptor defaultDescriptor;
-    VkImage defaultImage;
-    VkDeviceMemory defaultAllocation;
-    VkDeviceSize defaultAllocationSize;
-    uint32_t defaultMemoryTypeIndex;
-    int defaultFd;
+        // Import the image on |secondDevice|
+        wgpu::Texture wrappedTexture =
+            WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
+                            defaultMemoryTypeIndex, {});
 
-    // Clear a texture on a given device
-    void ClearImage(wgpu::Device device, wgpu::Texture wrappedTexture, wgpu::Color clearColor) {
-        wgpu::TextureView wrappedView = wrappedTexture.CreateView();
+        // Clear |wrappedTexture| on |secondDevice|
+        ClearImage(secondDevice, wrappedTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
 
-        // Submit a clear operation
-        utils::ComboRenderPassDescriptor renderPassDescriptor({wrappedView}, {});
-        renderPassDescriptor.cColorAttachments[0].clearColor = clearColor;
+        int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
+                                                                          wrappedTexture.Get());
 
-        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
-        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDescriptor);
-        pass.EndPass();
+        // Import the image to |device|, making sure we wait on signalFd
+        memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
+        wgpu::Texture nextWrappedTexture =
+            WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
+                            defaultMemoryTypeIndex, {signalFd});
 
-        wgpu::CommandBuffer commands = encoder.Finish();
+        // Verify |device| sees the changes from |secondDevice| (waits)
+        EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), nextWrappedTexture, 0, 0);
 
-        wgpu::Queue queue = device.CreateQueue();
-        queue.Submit(1, &commands);
+        // Verify aliased texture sees changes from |secondDevice| (without waiting!)
+        EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), wrappedTextureAlias, 0, 0);
+
+        IgnoreSignalSemaphore(device, nextWrappedTexture);
+        IgnoreSignalSemaphore(device, wrappedTextureAlias);
     }
 
-    // Submits a 1x1x1 copy from source to destination
-    void SimpleCopyTextureToTexture(wgpu::Device device,
-                                    wgpu::Queue queue,
-                                    wgpu::Texture source,
-                                    wgpu::Texture destination) {
+    // Clear an image in |secondDevice|
+    // Verify clear color is not visible in |device| if we import the texture as not cleared
+    TEST_P(VulkanImageWrappingUsageTests, UnclearedTextureIsCleared) {
+        DAWN_SKIP_TEST_IF(UsesWire());
+
+        // Import the image on |secondDevice|
+        wgpu::Texture wrappedTexture =
+            WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
+                            defaultMemoryTypeIndex, {});
+
+        // Clear |wrappedTexture| on |secondDevice|
+        ClearImage(secondDevice, wrappedTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
+
+        int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
+                                                                          wrappedTexture.Get());
+
+        // Import the image to |device|, making sure we wait on signalFd
+        int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
+        wgpu::Texture nextWrappedTexture =
+            WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
+                            defaultMemoryTypeIndex, {signalFd}, false);
+
+        // Verify |device| doesn't see the changes from |secondDevice|
+        EXPECT_PIXEL_RGBA8_EQ(RGBA8(0, 0, 0, 0), nextWrappedTexture, 0, 0);
+
+        IgnoreSignalSemaphore(device, nextWrappedTexture);
+    }
+
+    // Import a texture into |secondDevice|
+    // Issue a copy of the imported texture inside |device| to |copyDstTexture|
+    // Verify the clear color from |secondDevice| is visible in |copyDstTexture|
+    TEST_P(VulkanImageWrappingUsageTests, CopyTextureToTextureSrcSync) {
+        DAWN_SKIP_TEST_IF(UsesWire());
+
+        // Import the image on |secondDevice|
+        wgpu::Texture wrappedTexture =
+            WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
+                            defaultMemoryTypeIndex, {});
+
+        // Clear |wrappedTexture| on |secondDevice|
+        ClearImage(secondDevice, wrappedTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
+
+        int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
+                                                                          wrappedTexture.Get());
+
+        // Import the image to |device|, making sure we wait on |signalFd|
+        int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
+        wgpu::Texture deviceWrappedTexture =
+            WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
+                            defaultMemoryTypeIndex, {signalFd});
+
+        // Create a second texture on |device|
+        wgpu::Texture copyDstTexture = device.CreateTexture(&defaultDescriptor);
+
+        // Copy |deviceWrappedTexture| into |copyDstTexture|
+        SimpleCopyTextureToTexture(device, queue, deviceWrappedTexture, copyDstTexture);
+
+        // Verify |copyDstTexture| sees changes from |secondDevice|
+        EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), copyDstTexture, 0, 0);
+
+        IgnoreSignalSemaphore(device, deviceWrappedTexture);
+    }
+
+    // Import a texture into |device|
+    // Copy color A into texture on |device|
+    // Import same texture into |secondDevice|, waiting on the copy signal
+    // Copy color B using Texture to Texture copy on |secondDevice|
+    // Import texture back into |device|, waiting on color B signal
+    // Verify texture contains color B
+    // If texture destination isn't synchronized, |secondDevice| could copy color B
+    // into the texture first, then |device| writes color A
+    TEST_P(VulkanImageWrappingUsageTests, CopyTextureToTextureDstSync) {
+        DAWN_SKIP_TEST_IF(UsesWire());
+
+        // Import the image on |device|
+        wgpu::Texture wrappedTexture =
+            WrapVulkanImage(device, &defaultDescriptor, defaultFd, defaultAllocationSize,
+                            defaultMemoryTypeIndex, {});
+
+        // Clear |wrappedTexture| on |device|
+        ClearImage(device, wrappedTexture, {5 / 255.0f, 6 / 255.0f, 7 / 255.0f, 8 / 255.0f});
+
+        int signalFd =
+            dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(device.Get(), wrappedTexture.Get());
+
+        // Import the image to |secondDevice|, making sure we wait on |signalFd|
+        int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
+        wgpu::Texture secondDeviceWrappedTexture =
+            WrapVulkanImage(secondDevice, &defaultDescriptor, memoryFd, defaultAllocationSize,
+                            defaultMemoryTypeIndex, {signalFd});
+
+        // Create a texture with color B on |secondDevice|
+        wgpu::Texture copySrcTexture = secondDevice.CreateTexture(&defaultDescriptor);
+        ClearImage(secondDevice, copySrcTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
+
+        // Copy color B on |secondDevice|
+        wgpu::Queue secondDeviceQueue = secondDevice.CreateQueue();
+        SimpleCopyTextureToTexture(secondDevice, secondDeviceQueue, copySrcTexture,
+                                   secondDeviceWrappedTexture);
+
+        // Re-import back into |device|, waiting on |secondDevice|'s signal
+        signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(
+            secondDevice.Get(), secondDeviceWrappedTexture.Get());
+        memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
+
+        wgpu::Texture nextWrappedTexture =
+            WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
+                            defaultMemoryTypeIndex, {signalFd});
+
+        // Verify |nextWrappedTexture| contains the color from our copy
+        EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), nextWrappedTexture, 0, 0);
+
+        IgnoreSignalSemaphore(device, nextWrappedTexture);
+    }
+
+    // Import a texture from |secondDevice|
+    // Issue a copy of the imported texture inside |device| to |copyDstBuffer|
+    // Verify the clear color from |secondDevice| is visible in |copyDstBuffer|
+    TEST_P(VulkanImageWrappingUsageTests, CopyTextureToBufferSrcSync) {
+        DAWN_SKIP_TEST_IF(UsesWire());
+
+        // Import the image on |secondDevice|
+        wgpu::Texture wrappedTexture =
+            WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
+                            defaultMemoryTypeIndex, {});
+
+        // Clear |wrappedTexture| on |secondDevice|
+        ClearImage(secondDevice, wrappedTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
+
+        int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
+                                                                          wrappedTexture.Get());
+
+        // Import the image to |device|, making sure we wait on |signalFd|
+        int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
+        wgpu::Texture deviceWrappedTexture =
+            WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
+                            defaultMemoryTypeIndex, {signalFd});
+
+        // Create a destination buffer on |device|
+        wgpu::BufferDescriptor bufferDesc;
+        bufferDesc.size = 4;
+        bufferDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc;
+        wgpu::Buffer copyDstBuffer = device.CreateBuffer(&bufferDesc);
+
+        // Copy |deviceWrappedTexture| into |copyDstBuffer|
         wgpu::TextureCopyView copySrc;
-        copySrc.texture = source;
+        copySrc.texture = deviceWrappedTexture;
         copySrc.mipLevel = 0;
         copySrc.arrayLayer = 0;
         copySrc.origin = {0, 0, 0};
 
+        wgpu::BufferCopyView copyDst;
+        copyDst.buffer = copyDstBuffer;
+        copyDst.offset = 0;
+        copyDst.rowPitch = 256;
+        copyDst.imageHeight = 0;
+
+        wgpu::Extent3D copySize = {1, 1, 1};
+
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        encoder.CopyTextureToBuffer(&copySrc, &copyDst, &copySize);
+        wgpu::CommandBuffer commands = encoder.Finish();
+        queue.Submit(1, &commands);
+
+        // Verify |copyDstBuffer| sees changes from |secondDevice|
+        uint32_t expected = 0x04030201;
+        EXPECT_BUFFER_U32_EQ(expected, copyDstBuffer, 0);
+
+        IgnoreSignalSemaphore(device, deviceWrappedTexture);
+    }
+
+    // Import a texture into |device|
+    // Copy color A into texture on |device|
+    // Import same texture into |secondDevice|, waiting on the copy signal
+    // Copy color B using Buffer to Texture copy on |secondDevice|
+    // Import texture back into |device|, waiting on color B signal
+    // Verify texture contains color B
+    // If texture destination isn't synchronized, |secondDevice| could copy color B
+    // into the texture first, then |device| writes color A
+    TEST_P(VulkanImageWrappingUsageTests, CopyBufferToTextureDstSync) {
+        DAWN_SKIP_TEST_IF(UsesWire());
+
+        // Import the image on |device|
+        wgpu::Texture wrappedTexture =
+            WrapVulkanImage(device, &defaultDescriptor, defaultFd, defaultAllocationSize,
+                            defaultMemoryTypeIndex, {});
+
+        // Clear |wrappedTexture| on |device|
+        ClearImage(device, wrappedTexture, {5 / 255.0f, 6 / 255.0f, 7 / 255.0f, 8 / 255.0f});
+
+        int signalFd =
+            dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(device.Get(), wrappedTexture.Get());
+
+        // Import the image to |secondDevice|, making sure we wait on |signalFd|
+        int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
+        wgpu::Texture secondDeviceWrappedTexture =
+            WrapVulkanImage(secondDevice, &defaultDescriptor, memoryFd, defaultAllocationSize,
+                            defaultMemoryTypeIndex, {signalFd});
+
+        // Copy color B on |secondDevice|
+        wgpu::Queue secondDeviceQueue = secondDevice.CreateQueue();
+
+        // Create a buffer on |secondDevice|
+        wgpu::Buffer copySrcBuffer =
+            utils::CreateBufferFromData(secondDevice, wgpu::BufferUsage::CopySrc, {0x04030201});
+
+        // Copy |copySrcBuffer| into |secondDeviceWrappedTexture|
+        wgpu::BufferCopyView copySrc;
+        copySrc.buffer = copySrcBuffer;
+        copySrc.offset = 0;
+        copySrc.rowPitch = 256;
+        copySrc.imageHeight = 0;
+
         wgpu::TextureCopyView copyDst;
-        copyDst.texture = destination;
+        copyDst.texture = secondDeviceWrappedTexture;
         copyDst.mipLevel = 0;
         copyDst.arrayLayer = 0;
         copyDst.origin = {0, 0, 0};
 
         wgpu::Extent3D copySize = {1, 1, 1};
 
-        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
-        encoder.CopyTextureToTexture(&copySrc, &copyDst, &copySize);
-        wgpu::CommandBuffer commands = encoder.Finish();
-
-        queue.Submit(1, &commands);
-    }
-};
-
-// Clear an image in |secondDevice|
-// Verify clear color is visible in |device|
-TEST_P(VulkanImageWrappingUsageTests, ClearImageAcrossDevices) {
-    DAWN_SKIP_TEST_IF(UsesWire());
-
-    // Import the image on |secondDevice|
-    wgpu::Texture wrappedTexture =
-        WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
-                        defaultMemoryTypeIndex, {});
-
-    // Clear |wrappedTexture| on |secondDevice|
-    ClearImage(secondDevice, wrappedTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
-
-    int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
-                                                                      wrappedTexture.Get());
-
-    // Import the image to |device|, making sure we wait on signalFd
-    int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
-    wgpu::Texture nextWrappedTexture =
-        WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
-                        defaultMemoryTypeIndex, {signalFd});
-
-    // Verify |device| sees the changes from |secondDevice|
-    EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), nextWrappedTexture, 0, 0);
-
-    IgnoreSignalSemaphore(device, nextWrappedTexture);
-}
-
-// Import texture to |device| and |secondDevice|
-// Clear image in |secondDevice|
-// Verify clear color is visible in |device|
-// Verify the very first import into |device| also sees the change, since it should
-// alias the same memory
-TEST_P(VulkanImageWrappingUsageTests, ClearImageAcrossDevicesAliased) {
-    DAWN_SKIP_TEST_IF(UsesWire());
-    // Import the image on |device
-    wgpu::Texture wrappedTextureAlias = WrapVulkanImage(
-        device, &defaultDescriptor, defaultFd, defaultAllocationSize, defaultMemoryTypeIndex, {});
-
-    int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
-
-    // Import the image on |secondDevice|
-    wgpu::Texture wrappedTexture =
-        WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
-                        defaultMemoryTypeIndex, {});
-
-    // Clear |wrappedTexture| on |secondDevice|
-    ClearImage(secondDevice, wrappedTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
-
-    int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
-                                                                      wrappedTexture.Get());
-
-    // Import the image to |device|, making sure we wait on signalFd
-    memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
-    wgpu::Texture nextWrappedTexture =
-        WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
-                        defaultMemoryTypeIndex, {signalFd});
-
-    // Verify |device| sees the changes from |secondDevice| (waits)
-    EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), nextWrappedTexture, 0, 0);
-
-    // Verify aliased texture sees changes from |secondDevice| (without waiting!)
-    EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), wrappedTextureAlias, 0, 0);
-
-    IgnoreSignalSemaphore(device, nextWrappedTexture);
-    IgnoreSignalSemaphore(device, wrappedTextureAlias);
-}
-
-// Clear an image in |secondDevice|
-// Verify clear color is not visible in |device| if we import the texture as not cleared
-TEST_P(VulkanImageWrappingUsageTests, UnclearedTextureIsCleared) {
-    DAWN_SKIP_TEST_IF(UsesWire());
-
-    // Import the image on |secondDevice|
-    wgpu::Texture wrappedTexture =
-        WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
-                        defaultMemoryTypeIndex, {});
-
-    // Clear |wrappedTexture| on |secondDevice|
-    ClearImage(secondDevice, wrappedTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
-
-    int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
-                                                                      wrappedTexture.Get());
-
-    // Import the image to |device|, making sure we wait on signalFd
-    int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
-    wgpu::Texture nextWrappedTexture =
-        WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
-                        defaultMemoryTypeIndex, {signalFd}, false);
-
-    // Verify |device| doesn't see the changes from |secondDevice|
-    EXPECT_PIXEL_RGBA8_EQ(RGBA8(0, 0, 0, 0), nextWrappedTexture, 0, 0);
-
-    IgnoreSignalSemaphore(device, nextWrappedTexture);
-}
-
-// Import a texture into |secondDevice|
-// Issue a copy of the imported texture inside |device| to |copyDstTexture|
-// Verify the clear color from |secondDevice| is visible in |copyDstTexture|
-TEST_P(VulkanImageWrappingUsageTests, CopyTextureToTextureSrcSync) {
-    DAWN_SKIP_TEST_IF(UsesWire());
-
-    // Import the image on |secondDevice|
-    wgpu::Texture wrappedTexture =
-        WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
-                        defaultMemoryTypeIndex, {});
-
-    // Clear |wrappedTexture| on |secondDevice|
-    ClearImage(secondDevice, wrappedTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
-
-    int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
-                                                                      wrappedTexture.Get());
-
-    // Import the image to |device|, making sure we wait on |signalFd|
-    int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
-    wgpu::Texture deviceWrappedTexture =
-        WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
-                        defaultMemoryTypeIndex, {signalFd});
-
-    // Create a second texture on |device|
-    wgpu::Texture copyDstTexture = device.CreateTexture(&defaultDescriptor);
-
-    // Copy |deviceWrappedTexture| into |copyDstTexture|
-    SimpleCopyTextureToTexture(device, queue, deviceWrappedTexture, copyDstTexture);
-
-    // Verify |copyDstTexture| sees changes from |secondDevice|
-    EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), copyDstTexture, 0, 0);
-
-    IgnoreSignalSemaphore(device, deviceWrappedTexture);
-}
-
-// Import a texture into |device|
-// Copy color A into texture on |device|
-// Import same texture into |secondDevice|, waiting on the copy signal
-// Copy color B using Texture to Texture copy on |secondDevice|
-// Import texture back into |device|, waiting on color B signal
-// Verify texture contains color B
-// If texture destination isn't synchronized, |secondDevice| could copy color B
-// into the texture first, then |device| writes color A
-TEST_P(VulkanImageWrappingUsageTests, CopyTextureToTextureDstSync) {
-    DAWN_SKIP_TEST_IF(UsesWire());
-
-    // Import the image on |device|
-    wgpu::Texture wrappedTexture = WrapVulkanImage(
-        device, &defaultDescriptor, defaultFd, defaultAllocationSize, defaultMemoryTypeIndex, {});
-
-    // Clear |wrappedTexture| on |device|
-    ClearImage(device, wrappedTexture, {5 / 255.0f, 6 / 255.0f, 7 / 255.0f, 8 / 255.0f});
-
-    int signalFd =
-        dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(device.Get(), wrappedTexture.Get());
-
-    // Import the image to |secondDevice|, making sure we wait on |signalFd|
-    int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
-    wgpu::Texture secondDeviceWrappedTexture =
-        WrapVulkanImage(secondDevice, &defaultDescriptor, memoryFd, defaultAllocationSize,
-                        defaultMemoryTypeIndex, {signalFd});
-
-    // Create a texture with color B on |secondDevice|
-    wgpu::Texture copySrcTexture = secondDevice.CreateTexture(&defaultDescriptor);
-    ClearImage(secondDevice, copySrcTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
-
-    // Copy color B on |secondDevice|
-    wgpu::Queue secondDeviceQueue = secondDevice.CreateQueue();
-    SimpleCopyTextureToTexture(secondDevice, secondDeviceQueue, copySrcTexture,
-                               secondDeviceWrappedTexture);
-
-    // Re-import back into |device|, waiting on |secondDevice|'s signal
-    signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
-                                                                  secondDeviceWrappedTexture.Get());
-    memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
-
-    wgpu::Texture nextWrappedTexture =
-        WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
-                        defaultMemoryTypeIndex, {signalFd});
-
-    // Verify |nextWrappedTexture| contains the color from our copy
-    EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), nextWrappedTexture, 0, 0);
-
-    IgnoreSignalSemaphore(device, nextWrappedTexture);
-}
-
-// Import a texture from |secondDevice|
-// Issue a copy of the imported texture inside |device| to |copyDstBuffer|
-// Verify the clear color from |secondDevice| is visible in |copyDstBuffer|
-TEST_P(VulkanImageWrappingUsageTests, CopyTextureToBufferSrcSync) {
-    DAWN_SKIP_TEST_IF(UsesWire());
-
-    // Import the image on |secondDevice|
-    wgpu::Texture wrappedTexture =
-        WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
-                        defaultMemoryTypeIndex, {});
-
-    // Clear |wrappedTexture| on |secondDevice|
-    ClearImage(secondDevice, wrappedTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
-
-    int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
-                                                                      wrappedTexture.Get());
-
-    // Import the image to |device|, making sure we wait on |signalFd|
-    int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
-    wgpu::Texture deviceWrappedTexture =
-        WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
-                        defaultMemoryTypeIndex, {signalFd});
-
-    // Create a destination buffer on |device|
-    wgpu::BufferDescriptor bufferDesc;
-    bufferDesc.size = 4;
-    bufferDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc;
-    wgpu::Buffer copyDstBuffer = device.CreateBuffer(&bufferDesc);
-
-    // Copy |deviceWrappedTexture| into |copyDstBuffer|
-    wgpu::TextureCopyView copySrc;
-    copySrc.texture = deviceWrappedTexture;
-    copySrc.mipLevel = 0;
-    copySrc.arrayLayer = 0;
-    copySrc.origin = {0, 0, 0};
-
-    wgpu::BufferCopyView copyDst;
-    copyDst.buffer = copyDstBuffer;
-    copyDst.offset = 0;
-    copyDst.rowPitch = 256;
-    copyDst.imageHeight = 0;
-
-    wgpu::Extent3D copySize = {1, 1, 1};
-
-    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
-    encoder.CopyTextureToBuffer(&copySrc, &copyDst, &copySize);
-    wgpu::CommandBuffer commands = encoder.Finish();
-    queue.Submit(1, &commands);
-
-    // Verify |copyDstBuffer| sees changes from |secondDevice|
-    uint32_t expected = 0x04030201;
-    EXPECT_BUFFER_U32_EQ(expected, copyDstBuffer, 0);
-
-    IgnoreSignalSemaphore(device, deviceWrappedTexture);
-}
-
-// Import a texture into |device|
-// Copy color A into texture on |device|
-// Import same texture into |secondDevice|, waiting on the copy signal
-// Copy color B using Buffer to Texture copy on |secondDevice|
-// Import texture back into |device|, waiting on color B signal
-// Verify texture contains color B
-// If texture destination isn't synchronized, |secondDevice| could copy color B
-// into the texture first, then |device| writes color A
-TEST_P(VulkanImageWrappingUsageTests, CopyBufferToTextureDstSync) {
-    DAWN_SKIP_TEST_IF(UsesWire());
-
-    // Import the image on |device|
-    wgpu::Texture wrappedTexture = WrapVulkanImage(
-        device, &defaultDescriptor, defaultFd, defaultAllocationSize, defaultMemoryTypeIndex, {});
-
-    // Clear |wrappedTexture| on |device|
-    ClearImage(device, wrappedTexture, {5 / 255.0f, 6 / 255.0f, 7 / 255.0f, 8 / 255.0f});
-
-    int signalFd =
-        dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(device.Get(), wrappedTexture.Get());
-
-    // Import the image to |secondDevice|, making sure we wait on |signalFd|
-    int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
-    wgpu::Texture secondDeviceWrappedTexture =
-        WrapVulkanImage(secondDevice, &defaultDescriptor, memoryFd, defaultAllocationSize,
-                        defaultMemoryTypeIndex, {signalFd});
-
-    // Copy color B on |secondDevice|
-    wgpu::Queue secondDeviceQueue = secondDevice.CreateQueue();
-
-    // Create a buffer on |secondDevice|
-    wgpu::Buffer copySrcBuffer =
-        utils::CreateBufferFromData(secondDevice, wgpu::BufferUsage::CopySrc, {0x04030201});
-
-    // Copy |copySrcBuffer| into |secondDeviceWrappedTexture|
-    wgpu::BufferCopyView copySrc;
-    copySrc.buffer = copySrcBuffer;
-    copySrc.offset = 0;
-    copySrc.rowPitch = 256;
-    copySrc.imageHeight = 0;
-
-    wgpu::TextureCopyView copyDst;
-    copyDst.texture = secondDeviceWrappedTexture;
-    copyDst.mipLevel = 0;
-    copyDst.arrayLayer = 0;
-    copyDst.origin = {0, 0, 0};
-
-    wgpu::Extent3D copySize = {1, 1, 1};
-
-    wgpu::CommandEncoder encoder = secondDevice.CreateCommandEncoder();
-    encoder.CopyBufferToTexture(&copySrc, &copyDst, &copySize);
-    wgpu::CommandBuffer commands = encoder.Finish();
-    secondDeviceQueue.Submit(1, &commands);
-
-    // Re-import back into |device|, waiting on |secondDevice|'s signal
-    signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
-                                                                  secondDeviceWrappedTexture.Get());
-    memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
-
-    wgpu::Texture nextWrappedTexture =
-        WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
-                        defaultMemoryTypeIndex, {signalFd});
-
-    // Verify |nextWrappedTexture| contains the color from our copy
-    EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), nextWrappedTexture, 0, 0);
-
-    IgnoreSignalSemaphore(device, nextWrappedTexture);
-}
-
-// Import a texture from |secondDevice|
-// Issue a copy of the imported texture inside |device| to |copyDstTexture|
-// Issue second copy to |secondCopyDstTexture|
-// Verify the clear color from |secondDevice| is visible in both copies
-TEST_P(VulkanImageWrappingUsageTests, DoubleTextureUsage) {
-    DAWN_SKIP_TEST_IF(UsesWire());
-
-    // Import the image on |secondDevice|
-    wgpu::Texture wrappedTexture =
-        WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
-                        defaultMemoryTypeIndex, {});
-
-    // Clear |wrappedTexture| on |secondDevice|
-    ClearImage(secondDevice, wrappedTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
-
-    int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
-                                                                      wrappedTexture.Get());
-
-    // Import the image to |device|, making sure we wait on |signalFd|
-    int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
-    wgpu::Texture deviceWrappedTexture =
-        WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
-                        defaultMemoryTypeIndex, {signalFd});
-
-    // Create a second texture on |device|
-    wgpu::Texture copyDstTexture = device.CreateTexture(&defaultDescriptor);
-
-    // Create a third texture on |device|
-    wgpu::Texture secondCopyDstTexture = device.CreateTexture(&defaultDescriptor);
-
-    // Copy |deviceWrappedTexture| into |copyDstTexture|
-    SimpleCopyTextureToTexture(device, queue, deviceWrappedTexture, copyDstTexture);
-
-    // Copy |deviceWrappedTexture| into |secondCopyDstTexture|
-    SimpleCopyTextureToTexture(device, queue, deviceWrappedTexture, secondCopyDstTexture);
-
-    // Verify |copyDstTexture| sees changes from |secondDevice|
-    EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), copyDstTexture, 0, 0);
-
-    // Verify |secondCopyDstTexture| sees changes from |secondDevice|
-    EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), secondCopyDstTexture, 0, 0);
-
-    IgnoreSignalSemaphore(device, deviceWrappedTexture);
-}
-
-// Tex A on device 3 (external export)
-// Tex B on device 2 (external export)
-// Tex C on device 1 (external export)
-// Clear color for A on device 3
-// Copy A->B on device 3
-// Copy B->C on device 2 (wait on B from previous op)
-// Copy C->D on device 1 (wait on C from previous op)
-// Verify D has same color as A
-TEST_P(VulkanImageWrappingUsageTests, ChainTextureCopy) {
-    DAWN_SKIP_TEST_IF(UsesWire());
-
-    // Close |defaultFd| since this test doesn't import it anywhere
-    close(defaultFd);
-
-    // device 1 = |device|
-    // device 2 = |secondDevice|
-    // Create device 3
-    dawn_native::vulkan::Device* thirdDeviceVk = reinterpret_cast<dawn_native::vulkan::Device*>(
-        backendAdapter->CreateDevice(&deviceDescriptor));
-    wgpu::Device thirdDevice = wgpu::Device::Acquire(reinterpret_cast<WGPUDevice>(thirdDeviceVk));
-
-    // Make queue for device 2 and 3
-    wgpu::Queue secondDeviceQueue = secondDevice.CreateQueue();
-    wgpu::Queue thirdDeviceQueue = thirdDevice.CreateQueue();
-
-    // Allocate memory for A, B, C
-    VkImage imageA;
-    VkDeviceMemory allocationA;
-    int memoryFdA;
-    VkDeviceSize allocationSizeA;
-    uint32_t memoryTypeIndexA;
-    CreateBindExportImage(thirdDeviceVk, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, &imageA, &allocationA,
-                          &allocationSizeA, &memoryTypeIndexA, &memoryFdA);
-
-    VkImage imageB;
-    VkDeviceMemory allocationB;
-    int memoryFdB;
-    VkDeviceSize allocationSizeB;
-    uint32_t memoryTypeIndexB;
-    CreateBindExportImage(secondDeviceVk, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, &imageB, &allocationB,
-                          &allocationSizeB, &memoryTypeIndexB, &memoryFdB);
-
-    VkImage imageC;
-    VkDeviceMemory allocationC;
-    int memoryFdC;
-    VkDeviceSize allocationSizeC;
-    uint32_t memoryTypeIndexC;
-    CreateBindExportImage(deviceVk, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, &imageC, &allocationC,
-                          &allocationSizeC, &memoryTypeIndexC, &memoryFdC);
-
-    // Import TexA, TexB on device 3
-    wgpu::Texture wrappedTexADevice3 = WrapVulkanImage(thirdDevice, &defaultDescriptor, memoryFdA,
-                                                       allocationSizeA, memoryTypeIndexA, {});
-
-    wgpu::Texture wrappedTexBDevice3 = WrapVulkanImage(thirdDevice, &defaultDescriptor, memoryFdB,
-                                                       allocationSizeB, memoryTypeIndexB, {});
-
-    // Clear TexA
-    ClearImage(thirdDevice, wrappedTexADevice3, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
-
-    // Copy A->B
-    SimpleCopyTextureToTexture(thirdDevice, thirdDeviceQueue, wrappedTexADevice3,
-                               wrappedTexBDevice3);
-
-    int signalFdTexBDevice3 = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(
-        thirdDevice.Get(), wrappedTexBDevice3.Get());
-    IgnoreSignalSemaphore(thirdDevice, wrappedTexADevice3);
-
-    // Import TexB, TexC on device 2
-    memoryFdB = GetMemoryFd(secondDeviceVk, allocationB);
-    wgpu::Texture wrappedTexBDevice2 =
-        WrapVulkanImage(secondDevice, &defaultDescriptor, memoryFdB, allocationSizeB,
-                        memoryTypeIndexB, {signalFdTexBDevice3});
-
-    wgpu::Texture wrappedTexCDevice2 = WrapVulkanImage(secondDevice, &defaultDescriptor, memoryFdC,
-                                                       allocationSizeC, memoryTypeIndexC, {});
-
-    // Copy B->C on device 2
-    SimpleCopyTextureToTexture(secondDevice, secondDeviceQueue, wrappedTexBDevice2,
-                               wrappedTexCDevice2);
-
-    int signalFdTexCDevice2 = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(
-        secondDevice.Get(), wrappedTexCDevice2.Get());
-    IgnoreSignalSemaphore(secondDevice, wrappedTexBDevice2);
-
-    // Import TexC on device 1
-    memoryFdC = GetMemoryFd(deviceVk, allocationC);
-    wgpu::Texture wrappedTexCDevice1 =
-        WrapVulkanImage(device, &defaultDescriptor, memoryFdC, allocationSizeC, memoryTypeIndexC,
-                        {signalFdTexCDevice2});
-
-    // Create TexD on device 1
-    wgpu::Texture texD = device.CreateTexture(&defaultDescriptor);
-
-    // Copy C->D on device 1
-    SimpleCopyTextureToTexture(device, queue, wrappedTexCDevice1, texD);
-
-    // Verify D matches clear color
-    EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), texD, 0, 0);
-
-    thirdDeviceVk->GetFencedDeleter()->DeleteWhenUnused(imageA);
-    thirdDeviceVk->GetFencedDeleter()->DeleteWhenUnused(allocationA);
-    secondDeviceVk->GetFencedDeleter()->DeleteWhenUnused(imageB);
-    secondDeviceVk->GetFencedDeleter()->DeleteWhenUnused(allocationB);
-    deviceVk->GetFencedDeleter()->DeleteWhenUnused(imageC);
-    deviceVk->GetFencedDeleter()->DeleteWhenUnused(allocationC);
-
-    IgnoreSignalSemaphore(device, wrappedTexCDevice1);
-}
-
-// Tests a larger image is preserved when importing
-// TODO(http://crbug.com/dawn/206): This fails on AMD
-TEST_P(VulkanImageWrappingUsageTests, LargerImage) {
-    DAWN_SKIP_TEST_IF(UsesWire() || IsAMD());
-
-    close(defaultFd);
-
-    wgpu::TextureDescriptor descriptor;
-    descriptor.dimension = wgpu::TextureDimension::e2D;
-    descriptor.size.width = 640;
-    descriptor.size.height = 480;
-    descriptor.size.depth = 1;
-    descriptor.arrayLayerCount = 1;
-    descriptor.sampleCount = 1;
-    descriptor.format = wgpu::TextureFormat::BGRA8Unorm;
-    descriptor.mipLevelCount = 1;
-    descriptor.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc;
-
-    // Fill memory with textures to trigger layout issues on AMD
-    std::vector<wgpu::Texture> textures;
-    for (int i = 0; i < 20; i++) {
-        textures.push_back(device.CreateTexture(&descriptor));
-    }
-
-    wgpu::Queue secondDeviceQueue = secondDevice.CreateQueue();
-
-    // Make an image on |secondDevice|
-    VkImage imageA;
-    VkDeviceMemory allocationA;
-    int memoryFdA;
-    VkDeviceSize allocationSizeA;
-    uint32_t memoryTypeIndexA;
-    CreateBindExportImage(secondDeviceVk, 640, 480, VK_FORMAT_R8G8B8A8_UNORM, &imageA, &allocationA,
-                          &allocationSizeA, &memoryTypeIndexA, &memoryFdA);
-
-    // Import the image on |secondDevice|
-    wgpu::Texture wrappedTexture = WrapVulkanImage(secondDevice, &descriptor, memoryFdA,
-                                                   allocationSizeA, memoryTypeIndexA, {});
-
-    // Draw a non-trivial picture
-    int width = 640, height = 480, pixelSize = 4;
-    uint32_t rowPitch = Align(width * pixelSize, kTextureRowPitchAlignment);
-    uint32_t size = rowPitch * (height - 1) + width * pixelSize;
-    unsigned char data[size];
-    for (int row = 0; row < height; row++) {
-        for (int col = 0; col < width; col++) {
-            float normRow = static_cast<float>(row) / height;
-            float normCol = static_cast<float>(col) / width;
-            float dist = sqrt(normRow * normRow + normCol * normCol) * 3;
-            dist = dist - static_cast<int>(dist);
-            data[4 * (row * width + col)] = static_cast<unsigned char>(dist * 255);
-            data[4 * (row * width + col) + 1] = static_cast<unsigned char>(dist * 255);
-            data[4 * (row * width + col) + 2] = static_cast<unsigned char>(dist * 255);
-            data[4 * (row * width + col) + 3] = 255;
-        }
-    }
-
-    // Write the picture
-    {
-        wgpu::Buffer copySrcBuffer =
-            utils::CreateBufferFromData(secondDevice, data, size, wgpu::BufferUsage::CopySrc);
-        wgpu::BufferCopyView copySrc = utils::CreateBufferCopyView(copySrcBuffer, 0, rowPitch, 0);
-        wgpu::TextureCopyView copyDst =
-            utils::CreateTextureCopyView(wrappedTexture, 0, 0, {0, 0, 0});
-        wgpu::Extent3D copySize = {width, height, 1};
-
         wgpu::CommandEncoder encoder = secondDevice.CreateCommandEncoder();
         encoder.CopyBufferToTexture(&copySrc, &copyDst, &copySize);
         wgpu::CommandBuffer commands = encoder.Finish();
         secondDeviceQueue.Submit(1, &commands);
+
+        // Re-import back into |device|, waiting on |secondDevice|'s signal
+        signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(
+            secondDevice.Get(), secondDeviceWrappedTexture.Get());
+        memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
+
+        wgpu::Texture nextWrappedTexture =
+            WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
+                            defaultMemoryTypeIndex, {signalFd});
+
+        // Verify |nextWrappedTexture| contains the color from our copy
+        EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), nextWrappedTexture, 0, 0);
+
+        IgnoreSignalSemaphore(device, nextWrappedTexture);
     }
 
-    int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
-                                                                      wrappedTexture.Get());
-    int memoryFd = GetMemoryFd(secondDeviceVk, allocationA);
+    // Import a texture from |secondDevice|
+    // Issue a copy of the imported texture inside |device| to |copyDstTexture|
+    // Issue second copy to |secondCopyDstTexture|
+    // Verify the clear color from |secondDevice| is visible in both copies
+    TEST_P(VulkanImageWrappingUsageTests, DoubleTextureUsage) {
+        DAWN_SKIP_TEST_IF(UsesWire());
 
-    // Import the image on |device|
-    wgpu::Texture nextWrappedTexture = WrapVulkanImage(
-        device, &descriptor, memoryFd, allocationSizeA, memoryTypeIndexA, {signalFd});
+        // Import the image on |secondDevice|
+        wgpu::Texture wrappedTexture =
+            WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
+                            defaultMemoryTypeIndex, {});
 
-    // Copy the image into a buffer for comparison
-    wgpu::BufferDescriptor copyDesc;
-    copyDesc.size = size;
-    copyDesc.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
-    wgpu::Buffer copyDstBuffer = device.CreateBuffer(&copyDesc);
-    {
-        wgpu::TextureCopyView copySrc =
-            utils::CreateTextureCopyView(nextWrappedTexture, 0, 0, {0, 0, 0});
-        wgpu::BufferCopyView copyDst = utils::CreateBufferCopyView(copyDstBuffer, 0, rowPitch, 0);
+        // Clear |wrappedTexture| on |secondDevice|
+        ClearImage(secondDevice, wrappedTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
 
-        wgpu::Extent3D copySize = {width, height, 1};
+        int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
+                                                                          wrappedTexture.Get());
 
-        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
-        encoder.CopyTextureToBuffer(&copySrc, &copyDst, &copySize);
-        wgpu::CommandBuffer commands = encoder.Finish();
-        queue.Submit(1, &commands);
+        // Import the image to |device|, making sure we wait on |signalFd|
+        int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
+        wgpu::Texture deviceWrappedTexture =
+            WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
+                            defaultMemoryTypeIndex, {signalFd});
+
+        // Create a second texture on |device|
+        wgpu::Texture copyDstTexture = device.CreateTexture(&defaultDescriptor);
+
+        // Create a third texture on |device|
+        wgpu::Texture secondCopyDstTexture = device.CreateTexture(&defaultDescriptor);
+
+        // Copy |deviceWrappedTexture| into |copyDstTexture|
+        SimpleCopyTextureToTexture(device, queue, deviceWrappedTexture, copyDstTexture);
+
+        // Copy |deviceWrappedTexture| into |secondCopyDstTexture|
+        SimpleCopyTextureToTexture(device, queue, deviceWrappedTexture, secondCopyDstTexture);
+
+        // Verify |copyDstTexture| sees changes from |secondDevice|
+        EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), copyDstTexture, 0, 0);
+
+        // Verify |secondCopyDstTexture| sees changes from |secondDevice|
+        EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), secondCopyDstTexture, 0, 0);
+
+        IgnoreSignalSemaphore(device, deviceWrappedTexture);
     }
 
-    // Check the image is not corrupted on |device|
-    EXPECT_BUFFER_U32_RANGE_EQ(reinterpret_cast<uint32_t*>(data), copyDstBuffer, 0, size / 4);
+    // Tex A on device 3 (external export)
+    // Tex B on device 2 (external export)
+    // Tex C on device 1 (external export)
+    // Clear color for A on device 3
+    // Copy A->B on device 3
+    // Copy B->C on device 2 (wait on B from previous op)
+    // Copy C->D on device 1 (wait on C from previous op)
+    // Verify D has same color as A
+    TEST_P(VulkanImageWrappingUsageTests, ChainTextureCopy) {
+        DAWN_SKIP_TEST_IF(UsesWire());
 
-    IgnoreSignalSemaphore(device, nextWrappedTexture);
-    secondDeviceVk->GetFencedDeleter()->DeleteWhenUnused(imageA);
-    secondDeviceVk->GetFencedDeleter()->DeleteWhenUnused(allocationA);
-}
+        // Close |defaultFd| since this test doesn't import it anywhere
+        close(defaultFd);
 
-DAWN_INSTANTIATE_TEST(VulkanImageWrappingValidationTests, VulkanBackend);
-DAWN_INSTANTIATE_TEST(VulkanImageWrappingUsageTests, VulkanBackend);
+        // device 1 = |device|
+        // device 2 = |secondDevice|
+        // Create device 3
+        dawn_native::vulkan::Device* thirdDeviceVk = reinterpret_cast<dawn_native::vulkan::Device*>(
+            backendAdapter->CreateDevice(&deviceDescriptor));
+        wgpu::Device thirdDevice =
+            wgpu::Device::Acquire(reinterpret_cast<WGPUDevice>(thirdDeviceVk));
+
+        // Make queue for device 2 and 3
+        wgpu::Queue secondDeviceQueue = secondDevice.CreateQueue();
+        wgpu::Queue thirdDeviceQueue = thirdDevice.CreateQueue();
+
+        // Allocate memory for A, B, C
+        VkImage imageA;
+        VkDeviceMemory allocationA;
+        int memoryFdA;
+        VkDeviceSize allocationSizeA;
+        uint32_t memoryTypeIndexA;
+        CreateBindExportImage(thirdDeviceVk, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, &imageA, &allocationA,
+                              &allocationSizeA, &memoryTypeIndexA, &memoryFdA);
+
+        VkImage imageB;
+        VkDeviceMemory allocationB;
+        int memoryFdB;
+        VkDeviceSize allocationSizeB;
+        uint32_t memoryTypeIndexB;
+        CreateBindExportImage(secondDeviceVk, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, &imageB, &allocationB,
+                              &allocationSizeB, &memoryTypeIndexB, &memoryFdB);
+
+        VkImage imageC;
+        VkDeviceMemory allocationC;
+        int memoryFdC;
+        VkDeviceSize allocationSizeC;
+        uint32_t memoryTypeIndexC;
+        CreateBindExportImage(deviceVk, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, &imageC, &allocationC,
+                              &allocationSizeC, &memoryTypeIndexC, &memoryFdC);
+
+        // Import TexA, TexB on device 3
+        wgpu::Texture wrappedTexADevice3 = WrapVulkanImage(
+            thirdDevice, &defaultDescriptor, memoryFdA, allocationSizeA, memoryTypeIndexA, {});
+
+        wgpu::Texture wrappedTexBDevice3 = WrapVulkanImage(
+            thirdDevice, &defaultDescriptor, memoryFdB, allocationSizeB, memoryTypeIndexB, {});
+
+        // Clear TexA
+        ClearImage(thirdDevice, wrappedTexADevice3,
+                   {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
+
+        // Copy A->B
+        SimpleCopyTextureToTexture(thirdDevice, thirdDeviceQueue, wrappedTexADevice3,
+                                   wrappedTexBDevice3);
+
+        int signalFdTexBDevice3 = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(
+            thirdDevice.Get(), wrappedTexBDevice3.Get());
+        IgnoreSignalSemaphore(thirdDevice, wrappedTexADevice3);
+
+        // Import TexB, TexC on device 2
+        memoryFdB = GetMemoryFd(secondDeviceVk, allocationB);
+        wgpu::Texture wrappedTexBDevice2 =
+            WrapVulkanImage(secondDevice, &defaultDescriptor, memoryFdB, allocationSizeB,
+                            memoryTypeIndexB, {signalFdTexBDevice3});
+
+        wgpu::Texture wrappedTexCDevice2 = WrapVulkanImage(
+            secondDevice, &defaultDescriptor, memoryFdC, allocationSizeC, memoryTypeIndexC, {});
+
+        // Copy B->C on device 2
+        SimpleCopyTextureToTexture(secondDevice, secondDeviceQueue, wrappedTexBDevice2,
+                                   wrappedTexCDevice2);
+
+        int signalFdTexCDevice2 = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(
+            secondDevice.Get(), wrappedTexCDevice2.Get());
+        IgnoreSignalSemaphore(secondDevice, wrappedTexBDevice2);
+
+        // Import TexC on device 1
+        memoryFdC = GetMemoryFd(deviceVk, allocationC);
+        wgpu::Texture wrappedTexCDevice1 =
+            WrapVulkanImage(device, &defaultDescriptor, memoryFdC, allocationSizeC,
+                            memoryTypeIndexC, {signalFdTexCDevice2});
+
+        // Create TexD on device 1
+        wgpu::Texture texD = device.CreateTexture(&defaultDescriptor);
+
+        // Copy C->D on device 1
+        SimpleCopyTextureToTexture(device, queue, wrappedTexCDevice1, texD);
+
+        // Verify D matches clear color
+        EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), texD, 0, 0);
+
+        thirdDeviceVk->GetFencedDeleter()->DeleteWhenUnused(imageA);
+        thirdDeviceVk->GetFencedDeleter()->DeleteWhenUnused(allocationA);
+        secondDeviceVk->GetFencedDeleter()->DeleteWhenUnused(imageB);
+        secondDeviceVk->GetFencedDeleter()->DeleteWhenUnused(allocationB);
+        deviceVk->GetFencedDeleter()->DeleteWhenUnused(imageC);
+        deviceVk->GetFencedDeleter()->DeleteWhenUnused(allocationC);
+
+        IgnoreSignalSemaphore(device, wrappedTexCDevice1);
+    }
+
+    // Tests a larger image is preserved when importing
+    // TODO(http://crbug.com/dawn/206): This fails on AMD
+    TEST_P(VulkanImageWrappingUsageTests, LargerImage) {
+        DAWN_SKIP_TEST_IF(UsesWire() || IsAMD());
+
+        close(defaultFd);
+
+        wgpu::TextureDescriptor descriptor;
+        descriptor.dimension = wgpu::TextureDimension::e2D;
+        descriptor.size.width = 640;
+        descriptor.size.height = 480;
+        descriptor.size.depth = 1;
+        descriptor.arrayLayerCount = 1;
+        descriptor.sampleCount = 1;
+        descriptor.format = wgpu::TextureFormat::BGRA8Unorm;
+        descriptor.mipLevelCount = 1;
+        descriptor.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc;
+
+        // Fill memory with textures to trigger layout issues on AMD
+        std::vector<wgpu::Texture> textures;
+        for (int i = 0; i < 20; i++) {
+            textures.push_back(device.CreateTexture(&descriptor));
+        }
+
+        wgpu::Queue secondDeviceQueue = secondDevice.CreateQueue();
+
+        // Make an image on |secondDevice|
+        VkImage imageA;
+        VkDeviceMemory allocationA;
+        int memoryFdA;
+        VkDeviceSize allocationSizeA;
+        uint32_t memoryTypeIndexA;
+        CreateBindExportImage(secondDeviceVk, 640, 480, VK_FORMAT_R8G8B8A8_UNORM, &imageA,
+                              &allocationA, &allocationSizeA, &memoryTypeIndexA, &memoryFdA);
+
+        // Import the image on |secondDevice|
+        wgpu::Texture wrappedTexture = WrapVulkanImage(secondDevice, &descriptor, memoryFdA,
+                                                       allocationSizeA, memoryTypeIndexA, {});
+
+        // Draw a non-trivial picture
+        int width = 640, height = 480, pixelSize = 4;
+        uint32_t rowPitch = Align(width * pixelSize, kTextureRowPitchAlignment);
+        uint32_t size = rowPitch * (height - 1) + width * pixelSize;
+        unsigned char data[size];
+        for (int row = 0; row < height; row++) {
+            for (int col = 0; col < width; col++) {
+                float normRow = static_cast<float>(row) / height;
+                float normCol = static_cast<float>(col) / width;
+                float dist = sqrt(normRow * normRow + normCol * normCol) * 3;
+                dist = dist - static_cast<int>(dist);
+                data[4 * (row * width + col)] = static_cast<unsigned char>(dist * 255);
+                data[4 * (row * width + col) + 1] = static_cast<unsigned char>(dist * 255);
+                data[4 * (row * width + col) + 2] = static_cast<unsigned char>(dist * 255);
+                data[4 * (row * width + col) + 3] = 255;
+            }
+        }
+
+        // Write the picture
+        {
+            wgpu::Buffer copySrcBuffer =
+                utils::CreateBufferFromData(secondDevice, data, size, wgpu::BufferUsage::CopySrc);
+            wgpu::BufferCopyView copySrc =
+                utils::CreateBufferCopyView(copySrcBuffer, 0, rowPitch, 0);
+            wgpu::TextureCopyView copyDst =
+                utils::CreateTextureCopyView(wrappedTexture, 0, 0, {0, 0, 0});
+            wgpu::Extent3D copySize = {width, height, 1};
+
+            wgpu::CommandEncoder encoder = secondDevice.CreateCommandEncoder();
+            encoder.CopyBufferToTexture(&copySrc, &copyDst, &copySize);
+            wgpu::CommandBuffer commands = encoder.Finish();
+            secondDeviceQueue.Submit(1, &commands);
+        }
+
+        int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
+                                                                          wrappedTexture.Get());
+        int memoryFd = GetMemoryFd(secondDeviceVk, allocationA);
+
+        // Import the image on |device|
+        wgpu::Texture nextWrappedTexture = WrapVulkanImage(
+            device, &descriptor, memoryFd, allocationSizeA, memoryTypeIndexA, {signalFd});
+
+        // Copy the image into a buffer for comparison
+        wgpu::BufferDescriptor copyDesc;
+        copyDesc.size = size;
+        copyDesc.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
+        wgpu::Buffer copyDstBuffer = device.CreateBuffer(&copyDesc);
+        {
+            wgpu::TextureCopyView copySrc =
+                utils::CreateTextureCopyView(nextWrappedTexture, 0, 0, {0, 0, 0});
+            wgpu::BufferCopyView copyDst =
+                utils::CreateBufferCopyView(copyDstBuffer, 0, rowPitch, 0);
+
+            wgpu::Extent3D copySize = {width, height, 1};
+
+            wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+            encoder.CopyTextureToBuffer(&copySrc, &copyDst, &copySize);
+            wgpu::CommandBuffer commands = encoder.Finish();
+            queue.Submit(1, &commands);
+        }
+
+        // Check the image is not corrupted on |device|
+        EXPECT_BUFFER_U32_RANGE_EQ(reinterpret_cast<uint32_t*>(data), copyDstBuffer, 0, size / 4);
+
+        IgnoreSignalSemaphore(device, nextWrappedTexture);
+        secondDeviceVk->GetFencedDeleter()->DeleteWhenUnused(imageA);
+        secondDeviceVk->GetFencedDeleter()->DeleteWhenUnused(allocationA);
+    }
+
+    DAWN_INSTANTIATE_TEST(VulkanImageWrappingValidationTests, VulkanBackend);
+    DAWN_INSTANTIATE_TEST(VulkanImageWrappingUsageTests, VulkanBackend);
+
+}}  // namespace dawn_native::vulkan