Pass the old/new VkImageLayouts to Vulkan image import/export

Returning the layouts from an export operation and then using
them in a subsequent import operation allows the import to preserve
the texture contents.

This fixes Vukan image wrapping on some AMD/NVIDIA devices.

Bug: dawn:200
Change-Id: Icbb6e759856d410bb69724b9f439bc3088756d19
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/28380
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
diff --git a/src/dawn_native/DawnNative.cpp b/src/dawn_native/DawnNative.cpp
index b7bb94b..22b3dde 100644
--- a/src/dawn_native/DawnNative.cpp
+++ b/src/dawn_native/DawnNative.cpp
@@ -195,8 +195,14 @@
         return GetProcMapNamesForTestingInternal();
     }
 
-    ExternalImageDescriptor::ExternalImageDescriptor(ExternalImageDescriptorType type)
-        : type(type) {
+    // ExternalImageDescriptor
+
+    ExternalImageDescriptor::ExternalImageDescriptor(ExternalImageType type) : type(type) {
+    }
+
+    // ExternalImageExportInfo
+
+    ExternalImageExportInfo::ExternalImageExportInfo(ExternalImageType type) : type(type) {
     }
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/d3d12/D3D12Backend.cpp b/src/dawn_native/d3d12/D3D12Backend.cpp
index 7196599..73175fa 100644
--- a/src/dawn_native/d3d12/D3D12Backend.cpp
+++ b/src/dawn_native/d3d12/D3D12Backend.cpp
@@ -48,7 +48,7 @@
     }
 
     ExternalImageDescriptorDXGISharedHandle::ExternalImageDescriptorDXGISharedHandle()
-        : ExternalImageDescriptor(ExternalImageDescriptorType::DXGISharedHandle) {
+        : ExternalImageDescriptor(ExternalImageType::DXGISharedHandle) {
     }
 
     uint64_t SetExternalMemoryReservation(WGPUDevice device,
diff --git a/src/dawn_native/d3d12/TextureD3D12.cpp b/src/dawn_native/d3d12/TextureD3D12.cpp
index e30534a..d9ef627 100644
--- a/src/dawn_native/d3d12/TextureD3D12.cpp
+++ b/src/dawn_native/d3d12/TextureD3D12.cpp
@@ -396,7 +396,7 @@
             AcquireRef(new Texture(device, textureDescriptor, TextureState::OwnedExternal));
         DAWN_TRY(dawnTexture->InitializeAsExternalTexture(textureDescriptor, sharedHandle,
                                                           acquireMutexKey, isSwapChainTexture));
-        dawnTexture->SetIsSubresourceContentInitialized(descriptor->isCleared,
+        dawnTexture->SetIsSubresourceContentInitialized(descriptor->isInitialized,
                                                         dawnTexture->GetAllSubresources());
         return std::move(dawnTexture);
     }
diff --git a/src/dawn_native/metal/MetalBackend.mm b/src/dawn_native/metal/MetalBackend.mm
index 24c4481..74265a23 100644
--- a/src/dawn_native/metal/MetalBackend.mm
+++ b/src/dawn_native/metal/MetalBackend.mm
@@ -28,7 +28,7 @@
     }
 
     ExternalImageDescriptorIOSurface::ExternalImageDescriptorIOSurface()
-        : ExternalImageDescriptor(ExternalImageDescriptorType::IOSurface) {
+        : ExternalImageDescriptor(ExternalImageType::IOSurface) {
     }
 
     WGPUTexture WrapIOSurface(WGPUDevice cDevice,
diff --git a/src/dawn_native/metal/TextureMTL.mm b/src/dawn_native/metal/TextureMTL.mm
index 22931fd..c1a6abf 100644
--- a/src/dawn_native/metal/TextureMTL.mm
+++ b/src/dawn_native/metal/TextureMTL.mm
@@ -355,7 +355,7 @@
                                                                  plane:plane];
         [mtlDesc release];
 
-        SetIsSubresourceContentInitialized(descriptor->isCleared, GetAllSubresources());
+        SetIsSubresourceContentInitialized(descriptor->isInitialized, GetAllSubresources());
     }
 
     Texture::~Texture() {
diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp
index b838858..c96cede 100644
--- a/src/dawn_native/vulkan/DeviceVk.cpp
+++ b/src/dawn_native/vulkan/DeviceVk.cpp
@@ -658,7 +658,7 @@
         return {};
     }
 
-    MaybeError Device::ImportExternalImage(const ExternalImageDescriptor* descriptor,
+    MaybeError Device::ImportExternalImage(const ExternalImageDescriptorVk* descriptor,
                                            ExternalMemoryHandle memoryHandle,
                                            VkImage image,
                                            const std::vector<ExternalSemaphoreHandle>& waitHandles,
@@ -702,22 +702,35 @@
         return {};
     }
 
-    MaybeError Device::SignalAndExportExternalTexture(Texture* texture,
-                                                      ExternalSemaphoreHandle* outHandle) {
-        DAWN_TRY(ValidateObject(texture));
+    bool Device::SignalAndExportExternalTexture(
+        Texture* texture,
+        VkImageLayout desiredLayout,
+        ExternalImageExportInfoVk* info,
+        std::vector<ExternalSemaphoreHandle>* semaphoreHandles) {
+        return !ConsumedError([&]() -> MaybeError {
+            DAWN_TRY(ValidateObject(texture));
 
-        VkSemaphore outSignalSemaphore;
-        DAWN_TRY(texture->SignalAndDestroy(&outSignalSemaphore));
+            VkSemaphore signalSemaphore;
+            VkImageLayout releasedOldLayout;
+            VkImageLayout releasedNewLayout;
+            DAWN_TRY(texture->ExportExternalTexture(desiredLayout, &signalSemaphore,
+                                                    &releasedOldLayout, &releasedNewLayout));
 
-        // This has to happen right after SignalAndDestroy, since the semaphore will be
-        // deleted when the fenced deleter runs after the queue submission
-        DAWN_TRY_ASSIGN(*outHandle, mExternalSemaphoreService->ExportSemaphore(outSignalSemaphore));
+            ExternalSemaphoreHandle semaphoreHandle;
+            DAWN_TRY_ASSIGN(semaphoreHandle,
+                            mExternalSemaphoreService->ExportSemaphore(signalSemaphore));
+            semaphoreHandles->push_back(semaphoreHandle);
+            info->releasedOldLayout = releasedOldLayout;
+            info->releasedNewLayout = releasedNewLayout;
+            info->isInitialized =
+                texture->IsSubresourceContentInitialized(texture->GetAllSubresources());
 
-        return {};
+            return {};
+        }());
     }
 
     TextureBase* Device::CreateTextureWrappingVulkanImage(
-        const ExternalImageDescriptor* descriptor,
+        const ExternalImageDescriptorVk* descriptor,
         ExternalMemoryHandle memoryHandle,
         const std::vector<ExternalSemaphoreHandle>& waitHandles) {
         const TextureDescriptor* textureDescriptor =
diff --git a/src/dawn_native/vulkan/DeviceVk.h b/src/dawn_native/vulkan/DeviceVk.h
index 9adf3ee..1b44bb7 100644
--- a/src/dawn_native/vulkan/DeviceVk.h
+++ b/src/dawn_native/vulkan/DeviceVk.h
@@ -69,12 +69,13 @@
         // Dawn Native API
 
         TextureBase* CreateTextureWrappingVulkanImage(
-            const ExternalImageDescriptor* descriptor,
+            const ExternalImageDescriptorVk* descriptor,
             ExternalMemoryHandle memoryHandle,
             const std::vector<ExternalSemaphoreHandle>& waitHandles);
-
-        MaybeError SignalAndExportExternalTexture(Texture* texture,
-                                                  ExternalSemaphoreHandle* outHandle);
+        bool SignalAndExportExternalTexture(Texture* texture,
+                                            VkImageLayout desiredLayout,
+                                            ExternalImageExportInfoVk* info,
+                                            std::vector<ExternalSemaphoreHandle>* semaphoreHandle);
 
         // Dawn API
         CommandBufferBase* CreateCommandBuffer(CommandEncoder* encoder,
@@ -192,7 +193,7 @@
         // There is always a valid recording context stored in mRecordingContext
         CommandRecordingContext mRecordingContext;
 
-        MaybeError ImportExternalImage(const ExternalImageDescriptor* descriptor,
+        MaybeError ImportExternalImage(const ExternalImageDescriptorVk* descriptor,
                                        ExternalMemoryHandle memoryHandle,
                                        VkImage image,
                                        const std::vector<ExternalSemaphoreHandle>& waitHandles,
@@ -203,4 +204,4 @@
 
 }}  // namespace dawn_native::vulkan
 
-#endif  // DAWNNATIVE_VULKAN_DEVICEVK_H_
\ No newline at end of file
+#endif  // DAWNNATIVE_VULKAN_DEVICEVK_H_
diff --git a/src/dawn_native/vulkan/TextureVk.cpp b/src/dawn_native/vulkan/TextureVk.cpp
index 5112a4c..7627282 100644
--- a/src/dawn_native/vulkan/TextureVk.cpp
+++ b/src/dawn_native/vulkan/TextureVk.cpp
@@ -462,7 +462,7 @@
     // static
     ResultOrError<Texture*> Texture::CreateFromExternal(
         Device* device,
-        const ExternalImageDescriptor* descriptor,
+        const ExternalImageDescriptorVk* descriptor,
         const TextureDescriptor* textureDescriptor,
         external_memory::Service* externalMemoryService) {
         Ref<Texture> texture =
@@ -537,7 +537,7 @@
     }
 
     // Internally managed, but imported from external handle
-    MaybeError Texture::InitializeFromExternal(const ExternalImageDescriptor* descriptor,
+    MaybeError Texture::InitializeFromExternal(const ExternalImageDescriptorVk* descriptor,
                                                external_memory::Service* externalMemoryService) {
         VkFormat format = VulkanImageFormat(ToBackend(GetDevice()), GetFormat().format);
         VkImageUsageFlags usage = VulkanImageUsage(GetUsage(), GetFormat());
@@ -547,6 +547,9 @@
 
         mExternalState = ExternalState::PendingAcquire;
 
+        mPendingAcquireOldLayout = descriptor->releasedOldLayout;
+        mPendingAcquireNewLayout = descriptor->releasedNewLayout;
+
         VkImageCreateInfo baseCreateInfo = {};
         FillVulkanCreateInfoSizesAndType(*this, &baseCreateInfo);
 
@@ -571,7 +574,7 @@
         mHandle = nativeImage;
     }
 
-    MaybeError Texture::BindExternalMemory(const ExternalImageDescriptor* descriptor,
+    MaybeError Texture::BindExternalMemory(const ExternalImageDescriptorVk* descriptor,
                                            VkSemaphore signalSemaphore,
                                            VkDeviceMemory externalMemoryAllocation,
                                            std::vector<VkSemaphore> waitSemaphores) {
@@ -580,8 +583,8 @@
             device->fn.BindImageMemory(device->GetVkDevice(), mHandle, externalMemoryAllocation, 0),
             "BindImageMemory (external)"));
 
-        // Don't clear imported texture if already cleared
-        if (descriptor->isCleared) {
+        // Don't clear imported texture if already initialized
+        if (descriptor->isInitialized) {
             SetIsSubresourceContentInitialized(true, GetAllSubresources());
         }
 
@@ -592,7 +595,10 @@
         return {};
     }
 
-    MaybeError Texture::SignalAndDestroy(VkSemaphore* outSignalSemaphore) {
+    MaybeError Texture::ExportExternalTexture(VkImageLayout desiredLayout,
+                                              VkSemaphore* signalSemaphore,
+                                              VkImageLayout* releasedOldLayout,
+                                              VkImageLayout* releasedNewLayout) {
         Device* device = ToBackend(GetDevice());
 
         if (mExternalState == ExternalState::Released) {
@@ -605,17 +611,60 @@
         }
 
         ASSERT(mSignalSemaphore != VK_NULL_HANDLE);
+        ASSERT(GetNumMipLevels() == 1 && GetArrayLayers() == 1);
 
         // Release the texture
-        mExternalState = ExternalState::PendingRelease;
-        TransitionFullUsage(device->GetPendingRecordingContext(), wgpu::TextureUsage::None);
+        mExternalState = ExternalState::Released;
+
+        wgpu::TextureUsage usage = mSubresourceLastUsages[0];
+
+        VkImageMemoryBarrier barrier;
+        barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+        barrier.pNext = nullptr;
+        barrier.image = GetHandle();
+        barrier.subresourceRange.aspectMask = VulkanAspectMask(GetFormat().aspects);
+        barrier.subresourceRange.baseMipLevel = 0;
+        barrier.subresourceRange.levelCount = 1;
+        barrier.subresourceRange.baseArrayLayer = 0;
+        barrier.subresourceRange.layerCount = 1;
+
+        barrier.srcAccessMask = VulkanAccessFlags(usage, GetFormat());
+        barrier.dstAccessMask = 0;  // The barrier must be paired with another barrier that will
+                                    // specify the dst access mask on the importing queue.
+
+        barrier.oldLayout = VulkanImageLayout(usage, GetFormat());
+        if (desiredLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
+            // VK_IMAGE_LAYOUT_UNDEFINED is invalid here. We use it as a
+            // special value to indicate no layout transition should be done.
+            barrier.newLayout = barrier.oldLayout;
+        } else {
+            barrier.newLayout = desiredLayout;
+        }
+
+        barrier.srcQueueFamilyIndex = device->GetGraphicsQueueFamily();
+        barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR;
+
+        VkPipelineStageFlags srcStages = VulkanPipelineStage(usage, GetFormat());
+        VkPipelineStageFlags dstStages =
+            VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;  // We don't know when the importing queue will need
+                                                // the texture, so pass
+                                                // VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT to ensure
+                                                // the barrier happens-before any usage in the
+                                                // importing queue.
+
+        CommandRecordingContext* recordingContext = device->GetPendingRecordingContext();
+        device->fn.CmdPipelineBarrier(recordingContext->commandBuffer, srcStages, dstStages, 0, 0,
+                                      nullptr, 0, nullptr, 1, &barrier);
 
         // Queue submit to signal we are done with the texture
-        device->GetPendingRecordingContext()->signalSemaphores.push_back(mSignalSemaphore);
+        recordingContext->signalSemaphores.push_back(mSignalSemaphore);
         DAWN_TRY(device->SubmitPendingCommands());
 
-        // Write out the signal semaphore
-        *outSignalSemaphore = mSignalSemaphore;
+        // Write out the layouts and signal semaphore
+        *releasedOldLayout = barrier.oldLayout;
+        *releasedNewLayout = barrier.newLayout;
+        *signalSemaphore = mSignalSemaphore;
+
         mSignalSemaphore = VK_NULL_HANDLE;
 
         // Destroy the texture so it can't be used again
@@ -688,26 +737,58 @@
                     SubresourceRange::SingleMipAndLayer(0, 0, GetFormat().aspects)));
             }
 
+            VkImageMemoryBarrier* barrier = &(*barriers)[transitionBarrierStart];
             // Transfer texture from external queue to graphics queue
-            (*barriers)[transitionBarrierStart].srcQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR;
-            (*barriers)[transitionBarrierStart].dstQueueFamilyIndex =
-                ToBackend(GetDevice())->GetGraphicsQueueFamily();
-            // Don't override oldLayout to leave it as VK_IMAGE_LAYOUT_UNDEFINED
-            // TODO(http://crbug.com/dawn/200)
-            mExternalState = ExternalState::Acquired;
-        } else if (mExternalState == ExternalState::PendingRelease) {
-            if (barriers->size() == transitionBarrierStart) {
-                barriers->push_back(BuildMemoryBarrier(
-                    GetFormat(), mHandle, wgpu::TextureUsage::None, wgpu::TextureUsage::None,
-                    SubresourceRange::SingleMipAndLayer(0, 0, GetFormat().aspects)));
+            barrier->srcQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR;
+            barrier->dstQueueFamilyIndex = ToBackend(GetDevice())->GetGraphicsQueueFamily();
+
+            // srcAccessMask means nothing when importing. Queue transfers require a barrier on
+            // both the importing and exporting queues. The exporting queue should have specified
+            // this.
+            barrier->srcAccessMask = 0;
+
+            // This should be the first barrier after import.
+            ASSERT(barrier->oldLayout == VK_IMAGE_LAYOUT_UNDEFINED);
+
+            // Save the desired layout. We may need to transition through an intermediate
+            // |mPendingAcquireLayout| first.
+            VkImageLayout desiredLayout = barrier->newLayout;
+
+            bool isInitialized = IsSubresourceContentInitialized(GetAllSubresources());
+
+            // We don't care about the pending old layout if the texture is uninitialized. The
+            // driver is free to discard it. Likewise, we don't care about the pending new layout if
+            // the texture is uninitialized. We can skip the layout transition.
+            if (!isInitialized) {
+                barrier->oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+                barrier->newLayout = desiredLayout;
+            } else {
+                barrier->oldLayout = mPendingAcquireOldLayout;
+                barrier->newLayout = mPendingAcquireNewLayout;
             }
 
-            // Transfer texture from graphics queue to external queue
-            (*barriers)[transitionBarrierStart].srcQueueFamilyIndex =
-                ToBackend(GetDevice())->GetGraphicsQueueFamily();
-            (*barriers)[transitionBarrierStart].dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR;
-            (*barriers)[transitionBarrierStart].newLayout = VK_IMAGE_LAYOUT_GENERAL;
-            mExternalState = ExternalState::Released;
+            // If these are unequal, we need an another barrier to transition the layout.
+            if (barrier->newLayout != desiredLayout) {
+                VkImageMemoryBarrier layoutBarrier;
+                layoutBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+                layoutBarrier.pNext = nullptr;
+                layoutBarrier.image = GetHandle();
+                layoutBarrier.subresourceRange = barrier->subresourceRange;
+
+                // Transition from the acquired new layout to the desired layout.
+                layoutBarrier.oldLayout = barrier->newLayout;
+                layoutBarrier.newLayout = desiredLayout;
+
+                // We already transitioned these.
+                layoutBarrier.srcAccessMask = 0;
+                layoutBarrier.dstAccessMask = 0;
+                layoutBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+                layoutBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+
+                barriers->push_back(layoutBarrier);
+            }
+
+            mExternalState = ExternalState::Acquired;
         }
 
         mLastExternalState = mExternalState;
diff --git a/src/dawn_native/vulkan/TextureVk.h b/src/dawn_native/vulkan/TextureVk.h
index 6cf52f5..1eafb0e 100644
--- a/src/dawn_native/vulkan/TextureVk.h
+++ b/src/dawn_native/vulkan/TextureVk.h
@@ -49,7 +49,7 @@
         // image must be bound via Texture::BindExternalMemory.
         static ResultOrError<Texture*> CreateFromExternal(
             Device* device,
-            const ExternalImageDescriptor* descriptor,
+            const ExternalImageDescriptorVk* descriptor,
             const TextureDescriptor* textureDescriptor,
             external_memory::Service* externalMemoryService);
 
@@ -84,20 +84,24 @@
         void EnsureSubresourceContentInitialized(CommandRecordingContext* recordingContext,
                                                  const SubresourceRange& range);
 
-        MaybeError SignalAndDestroy(VkSemaphore* outSignalSemaphore);
         // Binds externally allocated memory to the VkImage and on success, takes ownership of
         // semaphores.
-        MaybeError BindExternalMemory(const ExternalImageDescriptor* descriptor,
+        MaybeError BindExternalMemory(const ExternalImageDescriptorVk* descriptor,
                                       VkSemaphore signalSemaphore,
                                       VkDeviceMemory externalMemoryAllocation,
                                       std::vector<VkSemaphore> waitSemaphores);
 
+        MaybeError ExportExternalTexture(VkImageLayout desiredLayout,
+                                         VkSemaphore* signalSemaphore,
+                                         VkImageLayout* releasedOldLayout,
+                                         VkImageLayout* releasedNewLayout);
+
       private:
         ~Texture() override;
         using TextureBase::TextureBase;
 
         MaybeError InitializeAsInternalTexture();
-        MaybeError InitializeFromExternal(const ExternalImageDescriptor* descriptor,
+        MaybeError InitializeFromExternal(const ExternalImageDescriptorVk* descriptor,
                                           external_memory::Service* externalMemoryService);
         void InitializeForSwapChain(VkImage nativeImage);
 
@@ -119,12 +123,14 @@
             InternalOnly,
             PendingAcquire,
             Acquired,
-            PendingRelease,
             Released
         };
         ExternalState mExternalState = ExternalState::InternalOnly;
         ExternalState mLastExternalState = ExternalState::InternalOnly;
 
+        VkImageLayout mPendingAcquireOldLayout;
+        VkImageLayout mPendingAcquireNewLayout;
+
         VkSemaphore mSignalSemaphore = VK_NULL_HANDLE;
         std::vector<VkSemaphore> mWaitRequirements;
 
diff --git a/src/dawn_native/vulkan/VulkanBackend.cpp b/src/dawn_native/vulkan/VulkanBackend.cpp
index c1458a0..faf17f8 100644
--- a/src/dawn_native/vulkan/VulkanBackend.cpp
+++ b/src/dawn_native/vulkan/VulkanBackend.cpp
@@ -59,51 +59,75 @@
         return static_cast<WGPUTextureFormat>(impl->GetPreferredFormat());
     }
 
-#ifdef DAWN_PLATFORM_LINUX
-    ExternalImageDescriptorFD::ExternalImageDescriptorFD(ExternalImageDescriptorType descType)
-        : ExternalImageDescriptor(descType) {
-    }
-
+#if defined(DAWN_PLATFORM_LINUX)
     ExternalImageDescriptorOpaqueFD::ExternalImageDescriptorOpaqueFD()
-        : ExternalImageDescriptorFD(ExternalImageDescriptorType::OpaqueFD) {
+        : ExternalImageDescriptorFD(ExternalImageType::OpaqueFD) {
     }
 
     ExternalImageDescriptorDmaBuf::ExternalImageDescriptorDmaBuf()
-        : ExternalImageDescriptorFD(ExternalImageDescriptorType::DmaBuf) {
+        : ExternalImageDescriptorFD(ExternalImageType::DmaBuf) {
+    }
+
+    ExternalImageExportInfoOpaqueFD::ExternalImageExportInfoOpaqueFD()
+        : ExternalImageExportInfoFD(ExternalImageType::OpaqueFD) {
+    }
+
+    ExternalImageExportInfoDmaBuf::ExternalImageExportInfoDmaBuf()
+        : ExternalImageExportInfoFD(ExternalImageType::DmaBuf) {
     }
 
     int ExportSignalSemaphoreOpaqueFD(WGPUDevice cDevice, WGPUTexture cTexture) {
+        // Doesn't actually matter if we use OpaqueFD or DmaBuf since these paths are the same right
+        // now. This function will be removed.
         Device* device = reinterpret_cast<Device*>(cDevice);
-        Texture* texture = reinterpret_cast<Texture*>(cTexture);
-
-        if (!texture) {
+        device->EmitDeprecationWarning(
+            "ExportSignalSemaphoreOpaqueFD is deprecated. Please use ExportVulkanImage instead.");
+        ExternalImageExportInfoOpaqueFD info;
+        if (!ExportVulkanImage(cTexture, VK_IMAGE_LAYOUT_GENERAL, &info)) {
             return -1;
         }
-
-        ExternalSemaphoreHandle outHandle;
-        if (device->ConsumedError(device->SignalAndExportExternalTexture(texture, &outHandle))) {
-            return -1;
-        }
-
-        return outHandle;
+        return info.semaphoreHandles[0];
     }
+#endif  // DAWN_PLATFORM_LINUX
 
-    WGPUTexture WrapVulkanImage(WGPUDevice cDevice, const ExternalImageDescriptor* descriptor) {
-        Device* device = reinterpret_cast<Device*>(cDevice);
-
+    WGPUTexture WrapVulkanImage(WGPUDevice cDevice, const ExternalImageDescriptorVk* descriptor) {
         switch (descriptor->type) {
-            case ExternalImageDescriptorType::OpaqueFD:
-            case ExternalImageDescriptorType::DmaBuf: {
+#if defined(DAWN_PLATFORM_LINUX)
+            case ExternalImageType::OpaqueFD:
+            case ExternalImageType::DmaBuf: {
                 const ExternalImageDescriptorFD* fdDescriptor =
                     static_cast<const ExternalImageDescriptorFD*>(descriptor);
+                Device* device = reinterpret_cast<Device*>(cDevice);
                 TextureBase* texture = device->CreateTextureWrappingVulkanImage(
-                    descriptor, fdDescriptor->memoryFD, fdDescriptor->waitFDs);
+                    fdDescriptor, fdDescriptor->memoryFD, fdDescriptor->waitFDs);
                 return reinterpret_cast<WGPUTexture>(texture);
             }
+#endif  // DAWN_PLATFORM_LINUX
             default:
                 return nullptr;
         }
     }
-#endif
+
+    bool ExportVulkanImage(WGPUTexture cTexture,
+                           VkImageLayout desiredLayout,
+                           ExternalImageExportInfoVk* info) {
+        if (cTexture == nullptr) {
+            return false;
+        }
+        switch (info->type) {
+#if defined(DAWN_PLATFORM_LINUX)
+            case ExternalImageType::OpaqueFD:
+            case ExternalImageType::DmaBuf: {
+                Texture* texture = reinterpret_cast<Texture*>(cTexture);
+                Device* device = ToBackend(texture->GetDevice());
+                ExternalImageExportInfoFD* fdInfo = static_cast<ExternalImageExportInfoFD*>(info);
+                return device->SignalAndExportExternalTexture(texture, desiredLayout, fdInfo,
+                                                              &fdInfo->semaphoreHandles);
+            }
+#endif  // DAWN_PLATFORM_LINUX
+            default:
+                return false;
+        }
+    }
 
 }}  // namespace dawn_native::vulkan
diff --git a/src/dawn_native/vulkan/external_memory/MemoryServiceDmaBuf.cpp b/src/dawn_native/vulkan/external_memory/MemoryServiceDmaBuf.cpp
index 4129745..f173bab 100644
--- a/src/dawn_native/vulkan/external_memory/MemoryServiceDmaBuf.cpp
+++ b/src/dawn_native/vulkan/external_memory/MemoryServiceDmaBuf.cpp
@@ -83,7 +83,7 @@
         if (!mSupported) {
             return false;
         }
-        if (descriptor->type != ExternalImageDescriptorType::DmaBuf) {
+        if (descriptor->type != ExternalImageType::DmaBuf) {
             return false;
         }
         const ExternalImageDescriptorDmaBuf* dmaBufDescriptor =
@@ -150,7 +150,7 @@
     ResultOrError<MemoryImportParams> Service::GetMemoryImportParams(
         const ExternalImageDescriptor* descriptor,
         VkImage image) {
-        if (descriptor->type != ExternalImageDescriptorType::DmaBuf) {
+        if (descriptor->type != ExternalImageType::DmaBuf) {
             return DAWN_VALIDATION_ERROR("ExternalImageDescriptor is not a dma-buf descriptor");
         }
         const ExternalImageDescriptorDmaBuf* dmaBufDescriptor =
@@ -216,7 +216,7 @@
 
     ResultOrError<VkImage> Service::CreateImage(const ExternalImageDescriptor* descriptor,
                                                 const VkImageCreateInfo& baseCreateInfo) {
-        if (descriptor->type != ExternalImageDescriptorType::DmaBuf) {
+        if (descriptor->type != ExternalImageType::DmaBuf) {
             return DAWN_VALIDATION_ERROR("ExternalImageDescriptor is not a dma-buf descriptor");
         }
         const ExternalImageDescriptorDmaBuf* dmaBufDescriptor =
diff --git a/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp b/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp
index d43a10f..ebdab5b 100644
--- a/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp
+++ b/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp
@@ -71,8 +71,7 @@
         // TODO(http://crbug.com/dawn/206): Investigate dedicated only images
         VkFlags memoryFlags =
             externalFormatProperties.externalMemoryProperties.externalMemoryFeatures;
-        return (memoryFlags & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT_KHR) &&
-               !(memoryFlags & VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT_KHR);
+        return (memoryFlags & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT_KHR) != 0;
     }
 
     bool Service::SupportsCreateImage(const ExternalImageDescriptor* descriptor,
@@ -84,7 +83,7 @@
     ResultOrError<MemoryImportParams> Service::GetMemoryImportParams(
         const ExternalImageDescriptor* descriptor,
         VkImage image) {
-        if (descriptor->type != ExternalImageDescriptorType::OpaqueFD) {
+        if (descriptor->type != ExternalImageType::OpaqueFD) {
             return DAWN_VALIDATION_ERROR("ExternalImageDescriptor is not an OpaqueFD descriptor");
         }
         const ExternalImageDescriptorOpaqueFD* opaqueFDDescriptor =
diff --git a/src/dawn_native/vulkan/external_memory/MemoryServiceZirconHandle.cpp b/src/dawn_native/vulkan/external_memory/MemoryServiceZirconHandle.cpp
index 85c4e4a..ae8744f 100644
--- a/src/dawn_native/vulkan/external_memory/MemoryServiceZirconHandle.cpp
+++ b/src/dawn_native/vulkan/external_memory/MemoryServiceZirconHandle.cpp
@@ -71,8 +71,7 @@
         // TODO(http://crbug.com/dawn/206): Investigate dedicated only images
         VkFlags memoryFlags =
             externalFormatProperties.externalMemoryProperties.externalMemoryFeatures;
-        return (memoryFlags & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT_KHR) &&
-               !(memoryFlags & VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT_KHR);
+        return (memoryFlags & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT_KHR) != 0;
     }
 
     bool Service::SupportsCreateImage(const ExternalImageDescriptor* descriptor,
@@ -84,7 +83,7 @@
     ResultOrError<MemoryImportParams> Service::GetMemoryImportParams(
         const ExternalImageDescriptor* descriptor,
         VkImage image) {
-        if (descriptor->type != ExternalImageDescriptorType::OpaqueFD) {
+        if (descriptor->type != ExternalImageType::OpaqueFD) {
             return DAWN_VALIDATION_ERROR("ExternalImageDescriptor is not an OpaqueFD descriptor");
         }
         const ExternalImageDescriptorOpaqueFD* opaqueFDDescriptor =
diff --git a/src/include/dawn_native/DawnNative.h b/src/include/dawn_native/DawnNative.h
index a57baeb..2199efa 100644
--- a/src/include/dawn_native/DawnNative.h
+++ b/src/include/dawn_native/DawnNative.h
@@ -200,8 +200,8 @@
     DAWN_NATIVE_EXPORT uint64_t AcquireErrorInjectorCallCount();
     DAWN_NATIVE_EXPORT void InjectErrorAt(uint64_t index);
 
-    // The different types of ExternalImageDescriptors
-    enum ExternalImageDescriptorType {
+    // The different types of external images
+    enum ExternalImageType {
         OpaqueFD,
         DmaBuf,
         IOSurface,
@@ -211,13 +211,26 @@
     // Common properties of external images
     struct DAWN_NATIVE_EXPORT ExternalImageDescriptor {
       public:
-        const ExternalImageDescriptorType type;
+        const ExternalImageType type;
         const WGPUTextureDescriptor* cTextureDescriptor;  // Must match image creation params
-        bool isCleared;  // Sets whether the texture will be cleared before use
+        union {
+            bool isInitialized;  // Whether the texture is initialized on import
+            bool isCleared;      // DEPRECATED: Sets whether the texture will be cleared before use
+        };
 
       protected:
-        ExternalImageDescriptor(ExternalImageDescriptorType type);
+        ExternalImageDescriptor(ExternalImageType type);
     };
+
+    struct DAWN_NATIVE_EXPORT ExternalImageExportInfo {
+      public:
+        const ExternalImageType type;
+        bool isInitialized;  // Whether the texture is initialized after export
+
+      protected:
+        ExternalImageExportInfo(ExternalImageType type);
+    };
+
 }  // namespace dawn_native
 
 #endif  // DAWNNATIVE_DAWNNATIVE_H_
diff --git a/src/include/dawn_native/VulkanBackend.h b/src/include/dawn_native/VulkanBackend.h
index 4e5aee9..88cf03d 100644
--- a/src/include/dawn_native/VulkanBackend.h
+++ b/src/include/dawn_native/VulkanBackend.h
@@ -33,19 +33,49 @@
     DAWN_NATIVE_EXPORT WGPUTextureFormat
     GetNativeSwapChainPreferredFormat(const DawnSwapChainImplementation* swapChain);
 
-// Can't use DAWN_PLATFORM_LINUX since header included in both dawn and chrome
+    struct DAWN_NATIVE_EXPORT ExternalImageDescriptorVk : ExternalImageDescriptor {
+      public:
+        // The following members may be ignored if |ExternalImageDescriptor::isInitialized| is false
+        // since the import does not need to preserve texture contents.
+
+        // See https://www.khronos.org/registry/vulkan/specs/1.1/html/chap7.html. The acquire
+        // operation old/new layouts must match exactly the layouts in the release operation. So
+        // we may need to issue two barriers releasedOldLayout -> releasedNewLayout ->
+        // cTextureDescriptor.usage if the new layout is not compatible with the desired usage.
+        // The first barrier is the queue transfer, the second is the layout transition to our
+        // desired usage.
+        VkImageLayout releasedOldLayout = VK_IMAGE_LAYOUT_GENERAL;
+        VkImageLayout releasedNewLayout = VK_IMAGE_LAYOUT_GENERAL;
+
+      protected:
+        using ExternalImageDescriptor::ExternalImageDescriptor;
+    };
+
+    struct ExternalImageExportInfoVk : ExternalImageExportInfo {
+      public:
+        // See comments in |ExternalImageDescriptorVk|
+        // Contains the old/new layouts used in the queue release operation.
+        VkImageLayout releasedOldLayout;
+        VkImageLayout releasedNewLayout;
+
+      protected:
+        using ExternalImageExportInfo::ExternalImageExportInfo;
+    };
+
+// Can't use DAWN_PLATFORM_LINUX since header included in both Dawn and Chrome
 #ifdef __linux__
+
         // Common properties of external images represented by FDs. On successful import the file
         // descriptor's ownership is transferred to the Dawn implementation and they shouldn't be
         // used outside of Dawn again. TODO(enga): Also transfer ownership in the error case so the
         // caller can assume the FD is always consumed.
-        struct DAWN_NATIVE_EXPORT ExternalImageDescriptorFD : ExternalImageDescriptor {
+        struct DAWN_NATIVE_EXPORT ExternalImageDescriptorFD : ExternalImageDescriptorVk {
           public:
             int memoryFD;  // A file descriptor from an export of the memory of the image
             std::vector<int> waitFDs;  // File descriptors of semaphores which will be waited on
 
           protected:
-            ExternalImageDescriptorFD(ExternalImageDescriptorType type);
+            using ExternalImageDescriptorVk::ExternalImageDescriptorVk;
         };
 
         // Descriptor for opaque file descriptor image import
@@ -64,8 +94,29 @@
             uint64_t drmModifier;  // DRM modifier of the buffer
         };
 
+        // Info struct that is written to in |ExportVulkanImage|.
+        struct DAWN_NATIVE_EXPORT ExternalImageExportInfoFD : ExternalImageExportInfoVk {
+          public:
+            // Contains the exported semaphore handles.
+            std::vector<int> semaphoreHandles;
+
+          protected:
+            using ExternalImageExportInfoVk::ExternalImageExportInfoVk;
+        };
+
+        struct DAWN_NATIVE_EXPORT ExternalImageExportInfoOpaqueFD : ExternalImageExportInfoFD {
+            ExternalImageExportInfoOpaqueFD();
+        };
+
+        struct DAWN_NATIVE_EXPORT ExternalImageExportInfoDmaBuf : ExternalImageExportInfoFD {
+            ExternalImageExportInfoDmaBuf();
+        };
+
+#endif  // __linux__
+
         // Exports a signal semaphore from a wrapped texture. This must be called on wrapped
         // textures before they are destroyed. On failure, returns -1
+        // TODO(enga): Remove after updating Chromium to use ExportVulkanImage.
         DAWN_NATIVE_EXPORT int ExportSignalSemaphoreOpaqueFD(WGPUDevice cDevice,
                                                              WGPUTexture cTexture);
 
@@ -74,8 +125,15 @@
         // primitives before the texture can be used.
         // On failure, returns a nullptr.
         DAWN_NATIVE_EXPORT WGPUTexture WrapVulkanImage(WGPUDevice cDevice,
-                                                       const ExternalImageDescriptor* descriptor);
-#endif  // __linux__
+                                                       const ExternalImageDescriptorVk* descriptor);
+
+        // Exports external memory from a Vulkan image. This must be called on wrapped textures
+        // before they are destroyed. It writes the semaphore to wait on and the old/new image
+        // layouts to |info|. Pass VK_IMAGE_LAYOUT_UNDEFINED as |desiredLayout| if you don't want to
+        // perform a layout transition.
+        DAWN_NATIVE_EXPORT bool ExportVulkanImage(WGPUTexture cTexture,
+                                                  VkImageLayout desiredLayout,
+                                                  ExternalImageExportInfoVk* info);
 
 }}  // namespace dawn_native::vulkan
 
diff --git a/src/tests/end2end/D3D12ResourceWrappingTests.cpp b/src/tests/end2end/D3D12ResourceWrappingTests.cpp
index b1187bc..a028319 100644
--- a/src/tests/end2end/D3D12ResourceWrappingTests.cpp
+++ b/src/tests/end2end/D3D12ResourceWrappingTests.cpp
@@ -300,7 +300,7 @@
                                   const wgpu::Color& clearColor,
                                   ID3D11Texture2D** d3d11TextureOut,
                                   IDXGIKeyedMutex** dxgiKeyedMutexOut,
-                                  bool isCleared = true) const {
+                                  bool isInitialized = true) const {
         ComPtr<ID3D11Texture2D> d3d11Texture;
         HRESULT hr = mD3d11Device->CreateTexture2D(d3dDescriptor, nullptr, &d3d11Texture);
         ASSERT_EQ(hr, S_OK);
@@ -339,7 +339,7 @@
             reinterpret_cast<const WGPUTextureDescriptor*>(dawnDescriptor);
         externDesc.sharedHandle = sharedHandle;
         externDesc.acquireMutexKey = 1;
-        externDesc.isCleared = isCleared;
+        externDesc.isInitialized = isInitialized;
         WGPUTexture dawnTexture = dawn_native::d3d12::WrapSharedHandle(device.Get(), &externDesc);
 
         *dawnTextureOut = wgpu::Texture::Acquire(dawnTexture);
@@ -502,9 +502,9 @@
 }
 
 // 1. Create and clear a D3D11 texture with clearColor
-// 2. Import the texture with isCleared = false
+// 2. Import the texture with isInitialized = false
 // 3. Verify clearColor is not visible in wrapped texture
-TEST_P(D3D12SharedHandleUsageTests, UnclearedTextureIsCleared) {
+TEST_P(D3D12SharedHandleUsageTests, UninitializedTextureIsCleared) {
     DAWN_SKIP_TEST_IF(UsesWire());
 
     const wgpu::Color clearColor{1.0f, 0.0f, 0.0f, 1.0f};
diff --git a/src/tests/end2end/IOSurfaceWrappingTests.cpp b/src/tests/end2end/IOSurfaceWrappingTests.cpp
index 9e44cb7..ad025f3 100644
--- a/src/tests/end2end/IOSurfaceWrappingTests.cpp
+++ b/src/tests/end2end/IOSurfaceWrappingTests.cpp
@@ -97,13 +97,13 @@
         wgpu::Texture WrapIOSurface(const wgpu::TextureDescriptor* descriptor,
                                     IOSurfaceRef ioSurface,
                                     uint32_t plane,
-                                    bool isCleared = true) {
+                                    bool isInitialized = true) {
             dawn_native::metal::ExternalImageDescriptorIOSurface externDesc;
             externDesc.cTextureDescriptor =
                 reinterpret_cast<const WGPUTextureDescriptor*>(descriptor);
             externDesc.ioSurface = ioSurface;
             externDesc.plane = plane;
-            externDesc.isCleared = isCleared;
+            externDesc.isInitialized = isInitialized;
             WGPUTexture texture = dawn_native::metal::WrapIOSurface(device.Get(), &externDesc);
             return wgpu::Texture::Acquire(texture);
         }
@@ -446,8 +446,8 @@
     DoClearTest(ioSurface.get(), wgpu::TextureFormat::RGBA8Unorm, &data, sizeof(data));
 }
 
-// Test that texture with color is cleared when isCleared = false
-TEST_P(IOSurfaceUsageTests, UnclearedTextureIsCleared) {
+// Test that texture with color is cleared when isInitialized = false
+TEST_P(IOSurfaceUsageTests, UninitializedTextureIsCleared) {
     DAWN_SKIP_TEST_IF(UsesWire());
 
     ScopedIOSurfaceRef ioSurface = CreateSinglePlaneIOSurface(1, 1, kCVPixelFormatType_32RGBA, 4);
@@ -465,7 +465,7 @@
     textureDescriptor.mipLevelCount = 1;
     textureDescriptor.usage = wgpu::TextureUsage::OutputAttachment | wgpu::TextureUsage::CopySrc;
 
-    // wrap ioSurface and ensure color is not visible when isCleared set to false
+    // wrap ioSurface and ensure color is not visible when isInitialized set to false
     wgpu::Texture ioSurfaceTexture = WrapIOSurface(&textureDescriptor, ioSurface.get(), 0, false);
     EXPECT_PIXEL_RGBA8_EQ(RGBA8(0, 0, 0, 0), ioSurfaceTexture, 0, 0);
 }
diff --git a/src/tests/white_box/VulkanImageWrappingTestsDmaBuf.cpp b/src/tests/white_box/VulkanImageWrappingTestsDmaBuf.cpp
index f52b365..8f6dae6 100644
--- a/src/tests/white_box/VulkanImageWrappingTestsDmaBuf.cpp
+++ b/src/tests/white_box/VulkanImageWrappingTestsDmaBuf.cpp
@@ -104,16 +104,34 @@
                                           uint32_t stride,
                                           uint64_t drmModifier,
                                           std::vector<int> waitFDs,
-                                          bool isCleared = true,
+                                          bool isInitialized = true,
+                                          bool expectValid = true) {
+                dawn_native::vulkan::ExternalImageDescriptorDmaBuf descriptor;
+                return WrapVulkanImage(dawnDevice, textureDescriptor, memoryFd, stride, drmModifier,
+                                       waitFDs, descriptor.releasedOldLayout,
+                                       descriptor.releasedNewLayout, isInitialized, expectValid);
+            }
+
+            wgpu::Texture WrapVulkanImage(wgpu::Device dawnDevice,
+                                          const wgpu::TextureDescriptor* textureDescriptor,
+                                          int memoryFd,
+                                          uint32_t stride,
+                                          uint64_t drmModifier,
+                                          std::vector<int> waitFDs,
+                                          VkImageLayout releasedOldLayout,
+                                          VkImageLayout releasedNewLayout,
+                                          bool isInitialized = true,
                                           bool expectValid = true) {
                 dawn_native::vulkan::ExternalImageDescriptorDmaBuf descriptor;
                 descriptor.cTextureDescriptor =
                     reinterpret_cast<const WGPUTextureDescriptor*>(textureDescriptor);
-                descriptor.isCleared = isCleared;
+                descriptor.isInitialized = isInitialized;
                 descriptor.stride = stride;
                 descriptor.drmModifier = drmModifier;
                 descriptor.memoryFD = memoryFd;
                 descriptor.waitFDs = waitFDs;
+                descriptor.releasedOldLayout = releasedOldLayout;
+                descriptor.releasedNewLayout = releasedNewLayout;
 
                 WGPUTexture texture =
                     dawn_native::vulkan::WrapVulkanImage(dawnDevice.Get(), &descriptor);
@@ -131,11 +149,13 @@
             // 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 dawnDevice, wgpu::Texture wrappedTexture) {
-                int fd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(dawnDevice.Get(),
-                                                                            wrappedTexture.Get());
-                ASSERT_NE(fd, -1);
-                close(fd);
+            void IgnoreSignalSemaphore(wgpu::Texture wrappedTexture) {
+                dawn_native::vulkan::ExternalImageExportInfoDmaBuf exportInfo;
+                dawn_native::vulkan::ExportVulkanImage(wrappedTexture.Get(), VK_IMAGE_LAYOUT_GENERAL, &exportInfo));
+                for (int handle : info.semaphoreHandles) {
+                    ASSERT_NE(handle, -1);
+                    close(handle);
+                }
             }
 
           protected:
@@ -157,7 +177,7 @@
         wgpu::Texture texture = WrapVulkanImage(device, &defaultDescriptor, defaultFd,
                                                 defaultStride, defaultModifier, {}, true, true);
         EXPECT_NE(texture.Get(), nullptr);
-        IgnoreSignalSemaphore(device, texture);
+        IgnoreSignalSemaphore(texture);
     }
 
     // Test an error occurs if the texture descriptor is missing
@@ -230,10 +250,12 @@
         wgpu::Texture texture = WrapVulkanImage(device, &defaultDescriptor, defaultFd,
                                                 defaultStride, defaultModifier, {}, 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);
+        IgnoreSignalSemaphore(texture);
+
+        dawn_native::vulkan::ExternalImageExportInfoDmaBuf exportInfo;
+        ASSERT_DEVICE_ERROR(bool success = dawn_native::vulkan::ExportVulkanImage(
+                                texture.Get(), VK_IMAGE_LAYOUT_GENERAL, &exportInfo));
+        ASSERT_FALSE(success);
     }
 
     // Test an error occurs if we try to export the signal semaphore from a normal texture
@@ -242,9 +264,11 @@
 
         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);
+
+        dawn_native::vulkan::ExternalImageExportInfoDmaBuf exportInfo;
+        ASSERT_DEVICE_ERROR(bool success = dawn_native::vulkan::ExportVulkanImage(
+                                texture.Get(), VK_IMAGE_LAYOUT_GENERAL, &exportInfo));
+        ASSERT_FALSE(success);
     }
 
     // Test an error occurs if we try to export the signal semaphore from a destroyed texture
@@ -254,9 +278,11 @@
         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);
+
+        dawn_native::vulkan::ExternalImageExportInfoDmaBuf exportInfo;
+        ASSERT_DEVICE_ERROR(bool success = dawn_native::vulkan::ExportVulkanImage(
+                                texture.Get(), VK_IMAGE_LAYOUT_GENERAL, &exportInfo));
+        ASSERT_FALSE(success);
     }
 
     // Fixture to test using external memory textures through different usages.
@@ -330,84 +356,56 @@
     // Verify clear color is visible in |device|
     TEST_P(VulkanImageWrappingUsageTests, ClearImageAcrossDevices) {
         // Import the image on |secondDevice|
-        wgpu::Texture wrappedTexture = WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd,
-                                                       defaultStride, defaultModifier, {});
+        wgpu::Texture wrappedTexture = WrapVulkanImage(
+            secondDevice, &defaultDescriptor, defaultFd, defaultStride, defaultModifier, {},
+            VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
 
         // 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());
+        dawn_native::vulkan::ExternalImageExportInfoDmaBuf exportInfo;
+        dawn_native::vulkan::ExportVulkanImage(wrappedTexture.Get(),
+                                               VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, &exportInfo);
 
         // Import the image to |device|, making sure we wait on signalFd
         int nextFd = gbm_bo_get_fd(defaultGbmBo);
-        wgpu::Texture nextWrappedTexture = WrapVulkanImage(
-            device, &defaultDescriptor, nextFd, defaultStride, defaultModifier, {signalFd});
+        wgpu::Texture nextWrappedTexture =
+            WrapVulkanImage(device, &defaultDescriptor, nextFd, defaultStride, defaultModifier,
+                            exportInfo.semaphoreHandles, exportInfo.releasedOldLayout,
+                            exportInfo.releasedNewLayout);
 
         // 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) {
-        // Import the image on |device|
-        wgpu::Texture wrappedTextureAlias = WrapVulkanImage(device, &defaultDescriptor, defaultFd,
-                                                            defaultStride, defaultModifier, {});
-
-        // Import the image on |secondDevice|
-        int nextFd = gbm_bo_get_fd(defaultGbmBo);
-        wgpu::Texture wrappedTexture = WrapVulkanImage(secondDevice, &defaultDescriptor, nextFd,
-                                                       defaultStride, defaultModifier, {});
-
-        // 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
-        nextFd = gbm_bo_get_fd(defaultGbmBo);
-        wgpu::Texture nextWrappedTexture = WrapVulkanImage(
-            device, &defaultDescriptor, nextFd, defaultStride, defaultModifier, {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);
+        IgnoreSignalSemaphore(nextWrappedTexture);
     }
 
     // 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) {
+    TEST_P(VulkanImageWrappingUsageTests, UninitializedTextureIsCleared) {
         // Import the image on |secondDevice|
-        wgpu::Texture wrappedTexture = WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd,
-                                                       defaultStride, defaultModifier, {});
+        wgpu::Texture wrappedTexture = WrapVulkanImage(
+            secondDevice, &defaultDescriptor, defaultFd, defaultStride, defaultModifier, {},
+            VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
 
         // 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());
+        dawn_native::vulkan::ExternalImageExportInfoDmaBuf exportInfo;
+        dawn_native::vulkan::ExportVulkanImage(wrappedTexture.Get(),
+                                               VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, &exportInfo);
 
         // Import the image to |device|, making sure we wait on signalFd
         int nextFd = gbm_bo_get_fd(defaultGbmBo);
-        wgpu::Texture nextWrappedTexture = WrapVulkanImage(
-            device, &defaultDescriptor, nextFd, defaultStride, defaultModifier, {signalFd}, false);
+        wgpu::Texture nextWrappedTexture =
+            WrapVulkanImage(device, &defaultDescriptor, nextFd, defaultStride, defaultModifier,
+                            exportInfo.semaphoreHandles, exportInfo.releasedOldLayout,
+                            exportInfo.releasedNewLayout, 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);
+        IgnoreSignalSemaphore(nextWrappedTexture);
     }
 
     // Import a texture into |secondDevice|
@@ -416,19 +414,23 @@
     // Verify the clear color from |secondDevice| is visible in |copyDstTexture|
     TEST_P(VulkanImageWrappingUsageTests, CopyTextureToTextureSrcSync) {
         // Import the image on |secondDevice|
-        wgpu::Texture wrappedTexture = WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd,
-                                                       defaultStride, defaultModifier, {});
+        wgpu::Texture wrappedTexture = WrapVulkanImage(
+            secondDevice, &defaultDescriptor, defaultFd, defaultStride, defaultModifier, {},
+            VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
 
         // 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());
+        dawn_native::vulkan::ExternalImageExportInfoDmaBuf exportInfo;
+        dawn_native::vulkan::ExportVulkanImage(wrappedTexture.Get(),
+                                               VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, &exportInfo);
 
         // Import the image to |device|, making sure we wait on |signalFd|
         int nextFd = gbm_bo_get_fd(defaultGbmBo);
-        wgpu::Texture deviceWrappedTexture = WrapVulkanImage(
-            device, &defaultDescriptor, nextFd, defaultStride, defaultModifier, {signalFd});
+        wgpu::Texture deviceWrappedTexture =
+            WrapVulkanImage(device, &defaultDescriptor, nextFd, defaultStride, defaultModifier,
+                            exportInfo.semaphoreHandles, exportInfo.releasedOldLayout,
+                            exportInfo.releasedNewLayout);
 
         // Create a second texture on |device|
         wgpu::Texture copyDstTexture = device.CreateTexture(&defaultDescriptor);
@@ -439,7 +441,7 @@
         // Verify |copyDstTexture| sees changes from |secondDevice|
         EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), copyDstTexture, 0, 0);
 
-        IgnoreSignalSemaphore(device, deviceWrappedTexture);
+        IgnoreSignalSemaphore(deviceWrappedTexture);
     }
 
     // Import a texture into |device|
@@ -453,19 +455,23 @@
     // into the texture first, then |device| writes color A
     TEST_P(VulkanImageWrappingUsageTests, CopyTextureToTextureDstSync) {
         // Import the image on |device|
-        wgpu::Texture wrappedTexture = WrapVulkanImage(device, &defaultDescriptor, defaultFd,
-                                                       defaultStride, defaultModifier, {});
+        wgpu::Texture wrappedTexture = WrapVulkanImage(
+            device, &defaultDescriptor, defaultFd, defaultStride, defaultModifier, {},
+            VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
 
         // 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());
+        dawn_native::vulkan::ExternalImageExportInfoDmaBuf exportInfo;
+        dawn_native::vulkan::ExportVulkanImage(wrappedTexture.Get(),
+                                               VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &exportInfo);
 
         // Import the image to |secondDevice|, making sure we wait on |signalFd|
         int nextFd = gbm_bo_get_fd(defaultGbmBo);
-        wgpu::Texture secondDeviceWrappedTexture = WrapVulkanImage(
-            secondDevice, &defaultDescriptor, nextFd, defaultStride, defaultModifier, {signalFd});
+        wgpu::Texture secondDeviceWrappedTexture =
+            WrapVulkanImage(secondDevice, &defaultDescriptor, nextFd, defaultStride,
+                            defaultModifier, exportInfo.semaphoreHandles,
+                            exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
 
         // Create a texture with color B on |secondDevice|
         wgpu::Texture copySrcTexture = secondDevice.CreateTexture(&defaultDescriptor);
@@ -477,17 +483,21 @@
                                    secondDeviceWrappedTexture);
 
         // Re-import back into |device|, waiting on |secondDevice|'s signal
-        signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(
-            secondDevice.Get(), secondDeviceWrappedTexture.Get());
+        dawn_native::vulkan::ExternalImageExportInfoDmaBuf secondExportInfo;
+        dawn_native::vulkan::ExportVulkanImage(secondDeviceWrappedTexture.Get(),
+                                               VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+                                               &secondExportInfo);
         nextFd = gbm_bo_get_fd(defaultGbmBo);
 
-        wgpu::Texture nextWrappedTexture = WrapVulkanImage(
-            device, &defaultDescriptor, nextFd, defaultStride, defaultModifier, {signalFd});
+        wgpu::Texture nextWrappedTexture =
+            WrapVulkanImage(device, &defaultDescriptor, nextFd, defaultStride, defaultModifier,
+                            secondExportInfo.semaphoreHandles, secondExportInfo.releasedOldLayout,
+                            secondExportInfo.releasedNewLayout);
 
         // Verify |nextWrappedTexture| contains the color from our copy
         EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), nextWrappedTexture, 0, 0);
 
-        IgnoreSignalSemaphore(device, nextWrappedTexture);
+        IgnoreSignalSemaphore(nextWrappedTexture);
     }
 
     // Import a texture from |secondDevice|
@@ -496,19 +506,23 @@
     // Verify the clear color from |secondDevice| is visible in |copyDstBuffer|
     TEST_P(VulkanImageWrappingUsageTests, CopyTextureToBufferSrcSync) {
         // Import the image on |secondDevice|
-        wgpu::Texture wrappedTexture = WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd,
-                                                       defaultStride, defaultModifier, {});
+        wgpu::Texture wrappedTexture = WrapVulkanImage(
+            secondDevice, &defaultDescriptor, defaultFd, defaultStride, defaultModifier, {},
+            VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
 
         // 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());
+        dawn_native::vulkan::ExternalImageExportInfoDmaBuf exportInfo;
+        dawn_native::vulkan::ExportVulkanImage(wrappedTexture.Get(),
+                                               VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, &exportInfo);
 
         // Import the image to |device|, making sure we wait on |signalFd|
         int nextFd = gbm_bo_get_fd(defaultGbmBo);
-        wgpu::Texture deviceWrappedTexture = WrapVulkanImage(
-            device, &defaultDescriptor, nextFd, defaultStride, defaultModifier, {signalFd});
+        wgpu::Texture deviceWrappedTexture =
+            WrapVulkanImage(device, &defaultDescriptor, nextFd, defaultStride, defaultModifier,
+                            exportInfo.semaphoreHandles, exportInfo.releasedOldLayout,
+                            exportInfo.releasedNewLayout);
 
         // Create a destination buffer on |device|
         wgpu::BufferDescriptor bufferDesc;
@@ -532,7 +546,7 @@
         uint32_t expected = 1;
         EXPECT_BUFFER_U32_EQ(expected, copyDstBuffer, 0);
 
-        IgnoreSignalSemaphore(device, deviceWrappedTexture);
+        IgnoreSignalSemaphore(deviceWrappedTexture);
     }
 
     // Import a texture into |device|
@@ -545,19 +559,23 @@
     // into the texture first, then |device| writes color A
     TEST_P(VulkanImageWrappingUsageTests, CopyBufferToTextureDstSync) {
         // Import the image on |device|
-        wgpu::Texture wrappedTexture = WrapVulkanImage(device, &defaultDescriptor, defaultFd,
-                                                       defaultStride, defaultModifier, {});
+        wgpu::Texture wrappedTexture = WrapVulkanImage(
+            device, &defaultDescriptor, defaultFd, defaultStride, defaultModifier, {},
+            VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
 
         // 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());
+        dawn_native::vulkan::ExternalImageExportInfoDmaBuf exportInfo;
+        dawn_native::vulkan::ExportVulkanImage(wrappedTexture.Get(),
+                                               VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, &exportInfo);
 
         // Import the image to |secondDevice|, making sure we wait on |signalFd|
         int nextFd = gbm_bo_get_fd(defaultGbmBo);
-        wgpu::Texture secondDeviceWrappedTexture = WrapVulkanImage(
-            secondDevice, &defaultDescriptor, nextFd, defaultStride, defaultModifier, {signalFd});
+        wgpu::Texture secondDeviceWrappedTexture =
+            WrapVulkanImage(secondDevice, &defaultDescriptor, nextFd, defaultStride,
+                            defaultModifier, exportInfo.semaphoreHandles,
+                            exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
 
         // Copy color B on |secondDevice|
         wgpu::Queue secondDeviceQueue = secondDevice.GetDefaultQueue();
@@ -579,17 +597,21 @@
         secondDeviceQueue.Submit(1, &commands);
 
         // Re-import back into |device|, waiting on |secondDevice|'s signal
-        signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(
-            secondDevice.Get(), secondDeviceWrappedTexture.Get());
+        dawn_native::vulkan::ExternalImageExportInfoDmaBuf secondExportInfo;
+        dawn_native::vulkan::ExportVulkanImage(secondDeviceWrappedTexture.Get(),
+                                               VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+                                               &secondExportInfo);
         nextFd = gbm_bo_get_fd(defaultGbmBo);
 
-        wgpu::Texture nextWrappedTexture = WrapVulkanImage(
-            device, &defaultDescriptor, nextFd, defaultStride, defaultModifier, {signalFd});
+        wgpu::Texture nextWrappedTexture =
+            WrapVulkanImage(device, &defaultDescriptor, nextFd, defaultStride, defaultModifier,
+                            secondExportInfo.semaphoreHandles, secondExportInfo.releasedOldLayout,
+                            secondExportInfo.releasedNewLayout);
 
         // Verify |nextWrappedTexture| contains the color from our copy
         EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), nextWrappedTexture, 0, 0);
 
-        IgnoreSignalSemaphore(device, nextWrappedTexture);
+        IgnoreSignalSemaphore(nextWrappedTexture);
     }
 
     // Import a texture from |secondDevice|
@@ -599,19 +621,23 @@
     // Verify the clear color from |secondDevice| is visible in both copies
     TEST_P(VulkanImageWrappingUsageTests, DoubleTextureUsage) {
         // Import the image on |secondDevice|
-        wgpu::Texture wrappedTexture = WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd,
-                                                       defaultStride, defaultModifier, {});
+        wgpu::Texture wrappedTexture = WrapVulkanImage(
+            secondDevice, &defaultDescriptor, defaultFd, defaultStride, defaultModifier, {},
+            VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
 
         // 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());
+        dawn_native::vulkan::ExternalImageExportInfoDmaBuf exportInfo;
+        dawn_native::vulkan::ExportVulkanImage(wrappedTexture.Get(),
+                                               VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, &exportInfo);
 
         // Import the image to |device|, making sure we wait on |signalFd|
         int nextFd = gbm_bo_get_fd(defaultGbmBo);
-        wgpu::Texture deviceWrappedTexture = WrapVulkanImage(
-            device, &defaultDescriptor, nextFd, defaultStride, defaultModifier, {signalFd});
+        wgpu::Texture deviceWrappedTexture =
+            WrapVulkanImage(device, &defaultDescriptor, nextFd, defaultStride, defaultModifier,
+                            exportInfo.semaphoreHandles, exportInfo.releasedOldLayout,
+                            exportInfo.releasedNewLayout);
 
         // Create a second texture on |device|
         wgpu::Texture copyDstTexture = device.CreateTexture(&defaultDescriptor);
@@ -631,7 +657,7 @@
         // Verify |secondCopyDstTexture| sees changes from |secondDevice|
         EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), secondCopyDstTexture, 0, 0);
 
-        IgnoreSignalSemaphore(device, deviceWrappedTexture);
+        IgnoreSignalSemaphore(deviceWrappedTexture);
     }
 
     // Tex A on device 3 (external export)
@@ -676,10 +702,12 @@
 
         // Import TexA, TexB on device 3
         wgpu::Texture wrappedTexADevice3 =
-            WrapVulkanImage(thirdDevice, &defaultDescriptor, fdA, strideA, modifierA, {});
+            WrapVulkanImage(thirdDevice, &defaultDescriptor, fdA, strideA, modifierA, {},
+                            VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
 
         wgpu::Texture wrappedTexBDevice3 =
-            WrapVulkanImage(thirdDevice, &defaultDescriptor, fdB, strideB, modifierB, {});
+            WrapVulkanImage(thirdDevice, &defaultDescriptor, fdB, strideB, modifierB, {},
+                            VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
 
         // Clear TexA
         ClearImage(thirdDevice, wrappedTexADevice3,
@@ -689,30 +717,37 @@
         SimpleCopyTextureToTexture(thirdDevice, thirdDeviceQueue, wrappedTexADevice3,
                                    wrappedTexBDevice3);
 
-        int signalFdTexBDevice3 = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(
-            thirdDevice.Get(), wrappedTexBDevice3.Get());
-        IgnoreSignalSemaphore(thirdDevice, wrappedTexADevice3);
+        dawn_native::vulkan::ExternalImageExportInfoDmaBuf exportInfoTexBDevice3;
+        dawn_native::vulkan::ExportVulkanImage(
+            wrappedTexBDevice3.Get(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, &exportInfoTexBDevice3);
+        IgnoreSignalSemaphore(wrappedTexADevice3);
 
         // Import TexB, TexC on device 2
         fdB = gbm_bo_get_fd(gbmBoB);
         wgpu::Texture wrappedTexBDevice2 = WrapVulkanImage(
-            secondDevice, &defaultDescriptor, fdB, strideB, modifierB, {signalFdTexBDevice3});
+            secondDevice, &defaultDescriptor, fdB, strideB, modifierB,
+            exportInfoTexBDevice3.semaphoreHandles, exportInfoTexBDevice3.releasedOldLayout,
+            exportInfoTexBDevice3.releasedNewLayout);
 
         wgpu::Texture wrappedTexCDevice2 =
-            WrapVulkanImage(secondDevice, &defaultDescriptor, fdC, strideC, modifierC, {});
+            WrapVulkanImage(secondDevice, &defaultDescriptor, fdC, strideC, modifierC, {},
+                            VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
 
         // Copy B->C on device 2
         SimpleCopyTextureToTexture(secondDevice, secondDeviceQueue, wrappedTexBDevice2,
                                    wrappedTexCDevice2);
 
-        int signalFdTexCDevice2 = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(
-            secondDevice.Get(), wrappedTexCDevice2.Get());
-        IgnoreSignalSemaphore(secondDevice, wrappedTexBDevice2);
+        dawn_native::vulkan::ExternalImageExportInfoDmaBuf exportInfoTexCDevice2;
+        dawn_native::vulkan::ExportVulkanImage(
+            wrappedTexCDevice2.Get(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, &exportInfoTexCDevice2);
+        IgnoreSignalSemaphore(wrappedTexBDevice2);
 
         // Import TexC on device 1
         fdC = gbm_bo_get_fd(gbmBoC);
-        wgpu::Texture wrappedTexCDevice1 = WrapVulkanImage(device, &defaultDescriptor, fdC, strideC,
-                                                           modifierC, {signalFdTexCDevice2});
+        wgpu::Texture wrappedTexCDevice1 = WrapVulkanImage(
+            device, &defaultDescriptor, fdC, strideC, modifierC,
+            exportInfoTexCDevice2.semaphoreHandles, exportInfoTexCDevice2.releasedOldLayout,
+            exportInfoTexCDevice2.releasedNewLayout);
 
         // Create TexD on device 1
         wgpu::Texture texD = device.CreateTexture(&defaultDescriptor);
@@ -723,7 +758,7 @@
         // Verify D matches clear color
         EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), texD, 0, 0);
 
-        IgnoreSignalSemaphore(device, wrappedTexCDevice1);
+        IgnoreSignalSemaphore(wrappedTexCDevice1);
     }
 
     // Tests a larger image is preserved when importing
@@ -756,7 +791,8 @@
 
         // Import the image on |secondDevice|
         wgpu::Texture wrappedTexture =
-            WrapVulkanImage(secondDevice, &descriptor, fd, stride, modifier, {});
+            WrapVulkanImage(secondDevice, &descriptor, fd, stride, modifier, {},
+                            VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
 
         // Draw a non-trivial picture
         uint32_t width = 640, height = 480, pixelSize = 4;
@@ -791,14 +827,15 @@
             wgpu::CommandBuffer commands = encoder.Finish();
             secondDeviceQueue.Submit(1, &commands);
         }
-
-        int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
-                                                                          wrappedTexture.Get());
+        dawn_native::vulkan::ExternalImageExportInfoDmaBuf exportInfo;
+        dawn_native::vulkan::ExportVulkanImage(wrappedTexture.Get(),
+                                               VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, &exportInfo);
         int nextFd = gbm_bo_get_fd(gbmBo);
 
         // Import the image on |device|
-        wgpu::Texture nextWrappedTexture =
-            WrapVulkanImage(device, &descriptor, nextFd, stride, modifier, {signalFd});
+        wgpu::Texture nextWrappedTexture = WrapVulkanImage(
+            device, &descriptor, nextFd, stride, modifier, exportInfo.semaphoreHandles,
+            exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
 
         // Copy the image into a buffer for comparison
         wgpu::BufferDescriptor copyDesc;
@@ -823,7 +860,7 @@
         EXPECT_BUFFER_U32_RANGE_EQ(reinterpret_cast<uint32_t*>(data.data()), copyDstBuffer, 0,
                                    data.size() / 4);
 
-        IgnoreSignalSemaphore(device, nextWrappedTexture);
+        IgnoreSignalSemaphore(nextWrappedTexture);
     }
 
     DAWN_INSTANTIATE_TEST(VulkanImageWrappingValidationTests, VulkanBackend());
diff --git a/src/tests/white_box/VulkanImageWrappingTestsOpaqueFD.cpp b/src/tests/white_box/VulkanImageWrappingTestsOpaqueFD.cpp
index cfd3c60..3259429 100644
--- a/src/tests/white_box/VulkanImageWrappingTestsOpaqueFD.cpp
+++ b/src/tests/white_box/VulkanImageWrappingTestsOpaqueFD.cpp
@@ -157,16 +157,34 @@
                                           VkDeviceSize allocationSize,
                                           uint32_t memoryTypeIndex,
                                           std::vector<int> waitFDs,
-                                          bool isCleared = true,
+                                          bool isInitialized = true,
+                                          bool expectValid = true) {
+                dawn_native::vulkan::ExternalImageDescriptorOpaqueFD descriptor;
+                return WrapVulkanImage(dawnDevice, textureDescriptor, memoryFd, allocationSize,
+                                       memoryTypeIndex, waitFDs, descriptor.releasedOldLayout,
+                                       descriptor.releasedNewLayout, isInitialized, expectValid);
+            }
+
+            wgpu::Texture WrapVulkanImage(wgpu::Device dawnDevice,
+                                          const wgpu::TextureDescriptor* textureDescriptor,
+                                          int memoryFd,
+                                          VkDeviceSize allocationSize,
+                                          uint32_t memoryTypeIndex,
+                                          std::vector<int> waitFDs,
+                                          VkImageLayout releasedOldLayout,
+                                          VkImageLayout releasedNewLayout,
+                                          bool isInitialized = true,
                                           bool expectValid = true) {
                 dawn_native::vulkan::ExternalImageDescriptorOpaqueFD descriptor;
                 descriptor.cTextureDescriptor =
                     reinterpret_cast<const WGPUTextureDescriptor*>(textureDescriptor);
-                descriptor.isCleared = isCleared;
+                descriptor.isInitialized = isInitialized;
                 descriptor.allocationSize = allocationSize;
                 descriptor.memoryTypeIndex = memoryTypeIndex;
                 descriptor.memoryFD = memoryFd;
                 descriptor.waitFDs = waitFDs;
+                descriptor.releasedOldLayout = releasedOldLayout;
+                descriptor.releasedNewLayout = releasedNewLayout;
 
                 WGPUTexture texture =
                     dawn_native::vulkan::WrapVulkanImage(dawnDevice.Get(), &descriptor);
@@ -184,11 +202,14 @@
             // 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 dawnDevice, wgpu::Texture wrappedTexture) {
-                int fd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(dawnDevice.Get(),
-                                                                            wrappedTexture.Get());
-                ASSERT_NE(fd, -1);
-                close(fd);
+            void IgnoreSignalSemaphore(wgpu::Texture wrappedTexture) {
+                dawn_native::vulkan::ExternalImageExportInfoOpaqueFD info;
+                dawn_native::vulkan::ExportVulkanImage(wrappedTexture.Get(),
+                                                       VK_IMAGE_LAYOUT_GENERAL, &info);
+                for (int handle : info.semaphoreHandles) {
+                    ASSERT_NE(handle, -1);
+                    close(handle);
+                }
             }
 
           protected:
@@ -244,7 +265,7 @@
             WrapVulkanImage(device, &defaultDescriptor, defaultFd, defaultAllocationSize,
                             defaultMemoryTypeIndex, {}, true, true);
         EXPECT_NE(texture.Get(), nullptr);
-        IgnoreSignalSemaphore(device, texture);
+        IgnoreSignalSemaphore(texture);
     }
 
     // Test an error occurs if the texture descriptor is missing
@@ -319,10 +340,12 @@
             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);
+        IgnoreSignalSemaphore(texture);
+
+        dawn_native::vulkan::ExternalImageExportInfoOpaqueFD exportInfo;
+        ASSERT_DEVICE_ERROR(bool success = dawn_native::vulkan::ExportVulkanImage(
+                                texture.Get(), VK_IMAGE_LAYOUT_GENERAL, &exportInfo));
+        ASSERT_FALSE(success);
     }
 
     // Test an error occurs if we try to export the signal semaphore from a normal texture
@@ -330,9 +353,11 @@
         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);
+
+        dawn_native::vulkan::ExternalImageExportInfoOpaqueFD exportInfo;
+        ASSERT_DEVICE_ERROR(bool success = dawn_native::vulkan::ExportVulkanImage(
+                                texture.Get(), VK_IMAGE_LAYOUT_GENERAL, &exportInfo));
+        ASSERT_FALSE(success);
     }
 
     // Test an error occurs if we try to export the signal semaphore from a destroyed texture
@@ -341,9 +366,11 @@
         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);
+
+        dawn_native::vulkan::ExternalImageExportInfoOpaqueFD exportInfo;
+        ASSERT_DEVICE_ERROR(bool success = dawn_native::vulkan::ExportVulkanImage(
+                                texture.Get(), VK_IMAGE_LAYOUT_GENERAL, &exportInfo));
+        ASSERT_FALSE(success);
     }
 
     // Fixture to test using external memory textures through different usages.
@@ -456,97 +483,58 @@
         // Import the image on |secondDevice|
         wgpu::Texture wrappedTexture =
             WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
-                            defaultMemoryTypeIndex, {});
+                            defaultMemoryTypeIndex, {}, VK_IMAGE_LAYOUT_UNDEFINED,
+                            VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
 
         // 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());
+        dawn_native::vulkan::ExternalImageExportInfoOpaqueFD exportInfo;
+        dawn_native::vulkan::ExportVulkanImage(wrappedTexture.Get(),
+                                               VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, &exportInfo);
 
         // 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});
+                            defaultMemoryTypeIndex, exportInfo.semaphoreHandles,
+                            exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
 
         // 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());
-
-        // WrapVulkanImage consumes the file descriptor so we can't import defaultFd twice.
-        // Duplicate the file descriptor so we can import it twice.
-        int defaultFdCopy = dup(defaultFd);
-        ASSERT(defaultFdCopy != -1);
-
-        // Import the image on |device
-        wgpu::Texture wrappedTextureAlias =
-            WrapVulkanImage(device, &defaultDescriptor, defaultFdCopy, defaultAllocationSize,
-                            defaultMemoryTypeIndex, {});
-
-        // 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| (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);
+        IgnoreSignalSemaphore(nextWrappedTexture);
     }
 
     // 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) {
+    TEST_P(VulkanImageWrappingUsageTests, UninitializedTextureIsCleared) {
         DAWN_SKIP_TEST_IF(UsesWire());
 
         // Import the image on |secondDevice|
         wgpu::Texture wrappedTexture =
             WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
-                            defaultMemoryTypeIndex, {});
+                            defaultMemoryTypeIndex, {}, VK_IMAGE_LAYOUT_UNDEFINED,
+                            VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
 
         // 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());
+        dawn_native::vulkan::ExternalImageExportInfoOpaqueFD exportInfo;
+        dawn_native::vulkan::ExportVulkanImage(wrappedTexture.Get(),
+                                               VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, &exportInfo);
 
-        // Import the image to |device|, making sure we wait on signalFd
+        // Import the image to |device|, making sure we wait on the semaphore
         int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
         wgpu::Texture nextWrappedTexture =
             WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
-                            defaultMemoryTypeIndex, {signalFd}, false);
+                            defaultMemoryTypeIndex, exportInfo.semaphoreHandles,
+                            exportInfo.releasedOldLayout, exportInfo.releasedNewLayout, 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);
+        IgnoreSignalSemaphore(nextWrappedTexture);
     }
 
     // Import a texture into |secondDevice|
@@ -558,19 +546,22 @@
         // Import the image on |secondDevice|
         wgpu::Texture wrappedTexture =
             WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
-                            defaultMemoryTypeIndex, {});
+                            defaultMemoryTypeIndex, {}, VK_IMAGE_LAYOUT_UNDEFINED,
+                            VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
 
         // 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());
+        dawn_native::vulkan::ExternalImageExportInfoOpaqueFD exportInfo;
+        dawn_native::vulkan::ExportVulkanImage(wrappedTexture.Get(),
+                                               VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, &exportInfo);
 
-        // Import the image to |device|, making sure we wait on |signalFd|
+        // Import the image to |device|, making sure we wait on the semaphore
         int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
         wgpu::Texture deviceWrappedTexture =
             WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
-                            defaultMemoryTypeIndex, {signalFd});
+                            defaultMemoryTypeIndex, exportInfo.semaphoreHandles,
+                            exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
 
         // Create a second texture on |device|
         wgpu::Texture copyDstTexture = device.CreateTexture(&defaultDescriptor);
@@ -581,7 +572,7 @@
         // Verify |copyDstTexture| sees changes from |secondDevice|
         EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), copyDstTexture, 0, 0);
 
-        IgnoreSignalSemaphore(device, deviceWrappedTexture);
+        IgnoreSignalSemaphore(deviceWrappedTexture);
     }
 
     // Import a texture into |device|
@@ -596,21 +587,23 @@
         DAWN_SKIP_TEST_IF(UsesWire());
 
         // Import the image on |device|
-        wgpu::Texture wrappedTexture =
-            WrapVulkanImage(device, &defaultDescriptor, defaultFd, defaultAllocationSize,
-                            defaultMemoryTypeIndex, {});
+        wgpu::Texture wrappedTexture = WrapVulkanImage(
+            device, &defaultDescriptor, defaultFd, defaultAllocationSize, defaultMemoryTypeIndex,
+            {}, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
 
         // 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());
+        dawn_native::vulkan::ExternalImageExportInfoOpaqueFD exportInfo;
+        dawn_native::vulkan::ExportVulkanImage(wrappedTexture.Get(),
+                                               VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &exportInfo);
 
-        // Import the image to |secondDevice|, making sure we wait on |signalFd|
+        // Import the image to |secondDevice|, making sure we wait on the semaphore
         int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
         wgpu::Texture secondDeviceWrappedTexture =
             WrapVulkanImage(secondDevice, &defaultDescriptor, memoryFd, defaultAllocationSize,
-                            defaultMemoryTypeIndex, {signalFd});
+                            defaultMemoryTypeIndex, exportInfo.semaphoreHandles,
+                            exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
 
         // Create a texture with color B on |secondDevice|
         wgpu::Texture copySrcTexture = secondDevice.CreateTexture(&defaultDescriptor);
@@ -622,18 +615,21 @@
                                    secondDeviceWrappedTexture);
 
         // Re-import back into |device|, waiting on |secondDevice|'s signal
-        signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(
-            secondDevice.Get(), secondDeviceWrappedTexture.Get());
+        dawn_native::vulkan::ExternalImageExportInfoOpaqueFD secondExportInfo;
+        dawn_native::vulkan::ExportVulkanImage(secondDeviceWrappedTexture.Get(),
+                                               VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+                                               &secondExportInfo);
         memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
 
         wgpu::Texture nextWrappedTexture =
             WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
-                            defaultMemoryTypeIndex, {signalFd});
+                            defaultMemoryTypeIndex, secondExportInfo.semaphoreHandles,
+                            secondExportInfo.releasedOldLayout, secondExportInfo.releasedNewLayout);
 
         // Verify |nextWrappedTexture| contains the color from our copy
         EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), nextWrappedTexture, 0, 0);
 
-        IgnoreSignalSemaphore(device, nextWrappedTexture);
+        IgnoreSignalSemaphore(nextWrappedTexture);
     }
 
     // Import a texture from |secondDevice|
@@ -645,19 +641,22 @@
         // Import the image on |secondDevice|
         wgpu::Texture wrappedTexture =
             WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
-                            defaultMemoryTypeIndex, {});
+                            defaultMemoryTypeIndex, {}, VK_IMAGE_LAYOUT_UNDEFINED,
+                            VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
 
         // 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());
+        dawn_native::vulkan::ExternalImageExportInfoOpaqueFD exportInfo;
+        dawn_native::vulkan::ExportVulkanImage(wrappedTexture.Get(),
+                                               VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, &exportInfo);
 
-        // Import the image to |device|, making sure we wait on |signalFd|
+        // Import the image to |device|, making sure we wait on the semaphore
         int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
         wgpu::Texture deviceWrappedTexture =
             WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
-                            defaultMemoryTypeIndex, {signalFd});
+                            defaultMemoryTypeIndex, exportInfo.semaphoreHandles,
+                            exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
 
         // Create a destination buffer on |device|
         wgpu::BufferDescriptor bufferDesc;
@@ -681,7 +680,7 @@
         uint32_t expected = 0x04030201;
         EXPECT_BUFFER_U32_EQ(expected, copyDstBuffer, 0);
 
-        IgnoreSignalSemaphore(device, deviceWrappedTexture);
+        IgnoreSignalSemaphore(deviceWrappedTexture);
     }
 
     // Import a texture into |device|
@@ -696,21 +695,23 @@
         DAWN_SKIP_TEST_IF(UsesWire());
 
         // Import the image on |device|
-        wgpu::Texture wrappedTexture =
-            WrapVulkanImage(device, &defaultDescriptor, defaultFd, defaultAllocationSize,
-                            defaultMemoryTypeIndex, {});
+        wgpu::Texture wrappedTexture = WrapVulkanImage(
+            device, &defaultDescriptor, defaultFd, defaultAllocationSize, defaultMemoryTypeIndex,
+            {}, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
 
         // 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());
+        dawn_native::vulkan::ExternalImageExportInfoOpaqueFD exportInfo;
+        dawn_native::vulkan::ExportVulkanImage(wrappedTexture.Get(),
+                                               VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, &exportInfo);
 
         // 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});
+                            defaultMemoryTypeIndex, exportInfo.semaphoreHandles,
+                            exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
 
         // Copy color B on |secondDevice|
         wgpu::Queue secondDeviceQueue = secondDevice.GetDefaultQueue();
@@ -732,18 +733,21 @@
         secondDeviceQueue.Submit(1, &commands);
 
         // Re-import back into |device|, waiting on |secondDevice|'s signal
-        signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(
-            secondDevice.Get(), secondDeviceWrappedTexture.Get());
+        dawn_native::vulkan::ExternalImageExportInfoOpaqueFD secondExportInfo;
+        dawn_native::vulkan::ExportVulkanImage(secondDeviceWrappedTexture.Get(),
+                                               VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+                                               &secondExportInfo);
         memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
 
         wgpu::Texture nextWrappedTexture =
             WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
-                            defaultMemoryTypeIndex, {signalFd});
+                            defaultMemoryTypeIndex, secondExportInfo.semaphoreHandles,
+                            secondExportInfo.releasedOldLayout, secondExportInfo.releasedNewLayout);
 
         // Verify |nextWrappedTexture| contains the color from our copy
         EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), nextWrappedTexture, 0, 0);
 
-        IgnoreSignalSemaphore(device, nextWrappedTexture);
+        IgnoreSignalSemaphore(nextWrappedTexture);
     }
 
     // Import a texture from |secondDevice|
@@ -756,19 +760,22 @@
         // Import the image on |secondDevice|
         wgpu::Texture wrappedTexture =
             WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
-                            defaultMemoryTypeIndex, {});
+                            defaultMemoryTypeIndex, {}, VK_IMAGE_LAYOUT_UNDEFINED,
+                            VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
 
         // 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());
+        dawn_native::vulkan::ExternalImageExportInfoOpaqueFD exportInfo;
+        dawn_native::vulkan::ExportVulkanImage(wrappedTexture.Get(),
+                                               VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, &exportInfo);
 
-        // Import the image to |device|, making sure we wait on |signalFd|
+        // Import the image to |device|, making sure we wait on the semaphore
         int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
         wgpu::Texture deviceWrappedTexture =
             WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
-                            defaultMemoryTypeIndex, {signalFd});
+                            defaultMemoryTypeIndex, exportInfo.semaphoreHandles,
+                            exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
 
         // Create a second texture on |device|
         wgpu::Texture copyDstTexture = device.CreateTexture(&defaultDescriptor);
@@ -788,7 +795,7 @@
         // Verify |secondCopyDstTexture| sees changes from |secondDevice|
         EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), secondCopyDstTexture, 0, 0);
 
-        IgnoreSignalSemaphore(device, deviceWrappedTexture);
+        IgnoreSignalSemaphore(deviceWrappedTexture);
     }
 
     // Tex A on device 3 (external export)
@@ -844,10 +851,12 @@
 
         // Import TexA, TexB on device 3
         wgpu::Texture wrappedTexADevice3 = WrapVulkanImage(
-            thirdDevice, &defaultDescriptor, memoryFdA, allocationSizeA, memoryTypeIndexA, {});
+            thirdDevice, &defaultDescriptor, memoryFdA, allocationSizeA, memoryTypeIndexA, {},
+            VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
 
         wgpu::Texture wrappedTexBDevice3 = WrapVulkanImage(
-            thirdDevice, &defaultDescriptor, memoryFdB, allocationSizeB, memoryTypeIndexB, {});
+            thirdDevice, &defaultDescriptor, memoryFdB, allocationSizeB, memoryTypeIndexB, {},
+            VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
 
         // Clear TexA
         ClearImage(thirdDevice, wrappedTexADevice3,
@@ -857,32 +866,39 @@
         SimpleCopyTextureToTexture(thirdDevice, thirdDeviceQueue, wrappedTexADevice3,
                                    wrappedTexBDevice3);
 
-        int signalFdTexBDevice3 = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(
-            thirdDevice.Get(), wrappedTexBDevice3.Get());
-        IgnoreSignalSemaphore(thirdDevice, wrappedTexADevice3);
+        dawn_native::vulkan::ExternalImageExportInfoOpaqueFD exportInfoTexBDevice3;
+        dawn_native::vulkan::ExportVulkanImage(
+            wrappedTexBDevice3.Get(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, &exportInfoTexBDevice3);
+
+        IgnoreSignalSemaphore(wrappedTexADevice3);
 
         // Import TexB, TexC on device 2
         memoryFdB = GetMemoryFd(secondDeviceVk, allocationB);
-        wgpu::Texture wrappedTexBDevice2 =
-            WrapVulkanImage(secondDevice, &defaultDescriptor, memoryFdB, allocationSizeB,
-                            memoryTypeIndexB, {signalFdTexBDevice3});
+        wgpu::Texture wrappedTexBDevice2 = WrapVulkanImage(
+            secondDevice, &defaultDescriptor, memoryFdB, allocationSizeB, memoryTypeIndexB,
+            exportInfoTexBDevice3.semaphoreHandles, exportInfoTexBDevice3.releasedOldLayout,
+            exportInfoTexBDevice3.releasedNewLayout);
 
         wgpu::Texture wrappedTexCDevice2 = WrapVulkanImage(
-            secondDevice, &defaultDescriptor, memoryFdC, allocationSizeC, memoryTypeIndexC, {});
+            secondDevice, &defaultDescriptor, memoryFdC, allocationSizeC, memoryTypeIndexC, {},
+            VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
 
         // Copy B->C on device 2
         SimpleCopyTextureToTexture(secondDevice, secondDeviceQueue, wrappedTexBDevice2,
                                    wrappedTexCDevice2);
 
-        int signalFdTexCDevice2 = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(
-            secondDevice.Get(), wrappedTexCDevice2.Get());
-        IgnoreSignalSemaphore(secondDevice, wrappedTexBDevice2);
+        dawn_native::vulkan::ExternalImageExportInfoOpaqueFD exportInfoTexCDevice2;
+        dawn_native::vulkan::ExportVulkanImage(
+            wrappedTexCDevice2.Get(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, &exportInfoTexCDevice2);
+
+        IgnoreSignalSemaphore(wrappedTexBDevice2);
 
         // Import TexC on device 1
         memoryFdC = GetMemoryFd(deviceVk, allocationC);
-        wgpu::Texture wrappedTexCDevice1 =
-            WrapVulkanImage(device, &defaultDescriptor, memoryFdC, allocationSizeC,
-                            memoryTypeIndexC, {signalFdTexCDevice2});
+        wgpu::Texture wrappedTexCDevice1 = WrapVulkanImage(
+            device, &defaultDescriptor, memoryFdC, allocationSizeC, memoryTypeIndexC,
+            exportInfoTexCDevice2.semaphoreHandles, exportInfoTexCDevice2.releasedOldLayout,
+            exportInfoTexCDevice2.releasedNewLayout);
 
         // Create TexD on device 1
         wgpu::Texture texD = device.CreateTexture(&defaultDescriptor);
@@ -900,7 +916,7 @@
         deviceVk->GetFencedDeleter()->DeleteWhenUnused(imageC);
         deviceVk->GetFencedDeleter()->DeleteWhenUnused(allocationC);
 
-        IgnoreSignalSemaphore(device, wrappedTexCDevice1);
+        IgnoreSignalSemaphore(wrappedTexCDevice1);
     }
 
     // Tests a larger image is preserved when importing
@@ -938,8 +954,9 @@
                               &allocationA, &allocationSizeA, &memoryTypeIndexA, &memoryFdA);
 
         // Import the image on |secondDevice|
-        wgpu::Texture wrappedTexture = WrapVulkanImage(secondDevice, &descriptor, memoryFdA,
-                                                       allocationSizeA, memoryTypeIndexA, {});
+        wgpu::Texture wrappedTexture =
+            WrapVulkanImage(secondDevice, &descriptor, memoryFdA, allocationSizeA, memoryTypeIndexA,
+                            {}, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
 
         // Draw a non-trivial picture
         uint32_t width = 640, height = 480, pixelSize = 4;
@@ -975,13 +992,17 @@
             secondDeviceQueue.Submit(1, &commands);
         }
 
-        int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
-                                                                          wrappedTexture.Get());
+        dawn_native::vulkan::ExternalImageExportInfoOpaqueFD exportInfo;
+        dawn_native::vulkan::ExportVulkanImage(wrappedTexture.Get(),
+                                               VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, &exportInfo);
+
         int memoryFd = GetMemoryFd(secondDeviceVk, allocationA);
 
         // Import the image on |device|
-        wgpu::Texture nextWrappedTexture = WrapVulkanImage(
-            device, &descriptor, memoryFd, allocationSizeA, memoryTypeIndexA, {signalFd});
+        wgpu::Texture nextWrappedTexture =
+            WrapVulkanImage(device, &descriptor, memoryFd, allocationSizeA, memoryTypeIndexA,
+                            exportInfo.semaphoreHandles, exportInfo.releasedOldLayout,
+                            exportInfo.releasedNewLayout);
 
         // Copy the image into a buffer for comparison
         wgpu::BufferDescriptor copyDesc;
@@ -1006,7 +1027,7 @@
         EXPECT_BUFFER_U32_RANGE_EQ(reinterpret_cast<uint32_t*>(data.data()), copyDstBuffer, 0,
                                    data.size() / 4);
 
-        IgnoreSignalSemaphore(device, nextWrappedTexture);
+        IgnoreSignalSemaphore(nextWrappedTexture);
         secondDeviceVk->GetFencedDeleter()->DeleteWhenUnused(imageA);
         secondDeviceVk->GetFencedDeleter()->DeleteWhenUnused(allocationA);
     }