Vulkan: Lock device when creating and exporting ExternalImage.

Bug: dawn:1662
Change-Id: Iee505f3c2c861ef9c3a6cc6189deefff08c343b7
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/166160
Auto-Submit: Quyen Le <lehoangquyen@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn/native/vulkan/VulkanBackend.cpp b/src/dawn/native/vulkan/VulkanBackend.cpp
index b8703ec..b409077 100644
--- a/src/dawn/native/vulkan/VulkanBackend.cpp
+++ b/src/dawn/native/vulkan/VulkanBackend.cpp
@@ -77,10 +77,11 @@
 #endif
 
 WGPUTexture WrapVulkanImage(WGPUDevice device, const ExternalImageDescriptorVk* descriptor) {
+    Device* backendDevice = ToBackend(FromAPI(device));
+    auto deviceLock(backendDevice->GetScopedLock());
     switch (descriptor->GetType()) {
 #if DAWN_PLATFORM_IS(ANDROID)
         case ExternalImageType::AHardwareBuffer: {
-            Device* backendDevice = ToBackend(FromAPI(device));
             const ExternalImageDescriptorAHardwareBuffer* ahbDescriptor =
                 static_cast<const ExternalImageDescriptorAHardwareBuffer*>(descriptor);
 
@@ -90,7 +91,6 @@
 #elif DAWN_PLATFORM_IS(LINUX)
         case ExternalImageType::OpaqueFD:
         case ExternalImageType::DmaBuf: {
-            Device* backendDevice = ToBackend(FromAPI(device));
             const ExternalImageDescriptorFD* fdDescriptor =
                 static_cast<const ExternalImageDescriptorFD*>(descriptor);
 
@@ -110,13 +110,14 @@
     if (texture == nullptr) {
         return false;
     }
+    Texture* backendTexture = ToBackend(FromAPI(texture));
+    Device* device = ToBackend(backendTexture->GetDevice());
+    auto deviceLock(device->GetScopedLock());
 #if DAWN_PLATFORM_IS(ANDROID) || DAWN_PLATFORM_IS(LINUX)
     switch (info->GetType()) {
         case ExternalImageType::AHardwareBuffer:
         case ExternalImageType::OpaqueFD:
         case ExternalImageType::DmaBuf: {
-            Texture* backendTexture = ToBackend(FromAPI(texture));
-            Device* device = ToBackend(backendTexture->GetDevice());
             ExternalImageExportInfoFD* fdInfo = static_cast<ExternalImageExportInfoFD*>(info);
 
             return device->SignalAndExportExternalTexture(backendTexture, desiredLayout, fdInfo,
diff --git a/src/dawn/tests/white_box/VulkanImageWrappingTests.cpp b/src/dawn/tests/white_box/VulkanImageWrappingTests.cpp
index 57cf38f..1e41801 100644
--- a/src/dawn/tests/white_box/VulkanImageWrappingTests.cpp
+++ b/src/dawn/tests/white_box/VulkanImageWrappingTests.cpp
@@ -976,6 +976,57 @@
 
     IgnoreSignalSemaphore(texture);
 }
+class VulkanImageWrappingMultithreadTests : public VulkanImageWrappingUsageTests {
+  protected:
+    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
+        std::vector<wgpu::FeatureName> features;
+        // TODO(crbug.com/dawn/1678): DawnWire doesn't support thread safe API yet.
+        if (!UsesWire()) {
+            features.push_back(wgpu::FeatureName::ImplicitDeviceSynchronization);
+        }
+        return features;
+    }
+
+    void SetUp() override {
+        VulkanImageWrappingUsageTests::SetUp();
+        // TODO(crbug.com/dawn/1678): DawnWire doesn't support thread safe API yet.
+        DAWN_TEST_UNSUPPORTED_IF(UsesWire());
+    }
+};
+
+// Test that wrapping multiple VulkanImage and clear them on multiple threads work.
+TEST_P(VulkanImageWrappingMultithreadTests, WrapAndClear_OnMultipleThreads) {
+    std::vector<std::unique_ptr<ExternalTexture>> testTextures(10);
+    for (auto& testTexture : testTextures) {
+        testTexture =
+            mBackend->CreateTexture(1, 1, defaultDescriptor.format, defaultDescriptor.usage);
+    }
+
+    wgpu::Device writeDevice = CreateDevice();
+
+    utils::RunInParallel(testTextures.size(), [&](uint32_t idx) {
+        // Import the image on |writeDevice|
+        wgpu::Texture wrappedTexture =
+            WrapVulkanImage(writeDevice, &defaultDescriptor, testTextures[idx].get(), {},
+                            VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+
+        // Clear |wrappedTexture| on |writeDevice|
+        ClearImage(writeDevice, wrappedTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
+
+        ExternalImageExportInfoVkForTesting exportInfo = GetExternalImageExportInfo();
+        ASSERT_TRUE(mBackend->ExportImage(wrappedTexture, &exportInfo));
+
+        // Import the image to |device|, making sure we wait on signalFd
+        wgpu::Texture nextWrappedTexture = WrapVulkanImage(
+            device, &defaultDescriptor, testTextures[idx].get(), std::move(exportInfo.semaphores),
+            exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
+
+        // Verify |device| sees the changes from |secondDevice|
+        EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(1, 2, 3, 4), nextWrappedTexture, 0, 0);
+
+        IgnoreSignalSemaphore(nextWrappedTexture);
+    });
+}
 
 DAWN_INSTANTIATE_TEST_P(VulkanImageWrappingValidationTests,
                         {VulkanBackend()},
@@ -990,5 +1041,12 @@
                         {true, false}   // DetectDedicatedAllocation
 );
 
+DAWN_INSTANTIATE_TEST_P(VulkanImageWrappingMultithreadTests,
+                        {VulkanBackend()},
+                        {ExternalImageType::OpaqueFD, ExternalImageType::DmaBuf},
+                        {true, false},  // UseDedicatedAllocation
+                        {true, false}   // DetectDedicatedAllocation
+);
+
 }  // anonymous namespace
 }  // namespace dawn::native::vulkan