// Copyright 2021 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "src/dawn/tests/white_box/VulkanImageWrappingTests.h"

#include <utility>

#include "partition_alloc/pointers/raw_ptr.h"
#include "src/dawn/common/Math.h"
#include "src/dawn/native/Adapter.h"
#include "src/dawn/native/vulkan/DeviceVk.h"
#include "src/dawn/tests/DawnTest.h"
#include "src/dawn/tests/white_box/VulkanImageWrappingTests_DmaBuf.h"
#include "src/dawn/tests/white_box/VulkanImageWrappingTests_OpaqueFD.h"
#include "src/dawn/utils/ComboRenderPipelineDescriptor.h"
#include "src/dawn/utils/WGPUHelpers.h"
#include "src/utils/compiler.h"

namespace dawn::native::vulkan {

void VulkanImageWrappingTestBackend::SetParam(
    const VulkanImageWrappingTestBackend::TestParams& params) {
    mParams = params;
}

const VulkanImageWrappingTestBackend::TestParams& VulkanImageWrappingTestBackend::GetParam() const {
    return mParams;
}

namespace {

using ExternalTexture = VulkanImageWrappingTestBackend::ExternalTexture;
using ExternalSemaphore = VulkanImageWrappingTestBackend::ExternalSemaphore;

using UseDedicatedAllocation = bool;
using DetectDedicatedAllocation = bool;

constexpr int kTestTexturesCount = 2;

DAWN_TEST_PARAM_STRUCT(ImageWrappingParams,
                       ExternalImageType,
                       UseDedicatedAllocation,
                       DetectDedicatedAllocation);

class VulkanImageWrappingTestBase : public DawnTestWithParams<ImageWrappingParams> {
  protected:
    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
        return {wgpu::FeatureName::DawnInternalUsages};
    }

  public:
    void SetUp() override {
        DawnTestWithParams::SetUp();
        DAWN_TEST_UNSUPPORTED_IF(UsesWire());

        // TODO(dawn:1552): Nvidia doesn't seem to correctly reflect whether an import requires a
        // dedicated allocation.
        DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && GetParam().mUseDedicatedAllocation &&
                              GetParam().mDetectDedicatedAllocation);

        // TODO(crbug.com/438257193): Nvidia is crashing for all tests that use DMA buffers.
        DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() &&
                              GetParam().mExternalImageType == ExternalImageType::DmaBuf);

        // TODO(crbug.com/342213634): Crashes on ChromeOS volteer devices.
        DAWN_SUPPRESS_TEST_IF(IsChromeOS() && IsIntel() && IsBackendValidationEnabled());

        switch (GetParam().mExternalImageType) {
            case ExternalImageType::OpaqueFD:
                mBackend = CreateOpaqueFDBackend(device);
                break;
            case ExternalImageType::DmaBuf:
                mBackend = CreateDMABufBackend(device);
                break;
            default:
                DAWN_UNREACHABLE();
        }

        VulkanImageWrappingTestBackend::TestParams params;
        params.externalImageType = GetParam().mExternalImageType;
        params.useDedicatedAllocation = GetParam().mUseDedicatedAllocation;
        params.detectDedicatedAllocation = GetParam().mDetectDedicatedAllocation;
        DAWN_TEST_UNSUPPORTED_IF(!mBackend->SupportsTestParams(params));
        mBackend->SetParam(params);

        defaultDescriptor.dimension = wgpu::TextureDimension::e2D;
        defaultDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;
        defaultDescriptor.size = {1, 1, 1};
        defaultDescriptor.sampleCount = 1;
        defaultDescriptor.mipLevelCount = 1;
        defaultDescriptor.usage = wgpu::TextureUsage::RenderAttachment |
                                  wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;

        for (int i = 0; i < kTestTexturesCount; ++i) {
            testTextures[i] =
                mBackend->CreateTexture(1, 1, defaultDescriptor.format, defaultDescriptor.usage);
        }
        defaultTexture = testTextures[0].get();
    }

    void TearDown() override {
        if (UsesWire()) {
            DawnTestWithParams::TearDown();
            return;
        }

        defaultTexture = nullptr;
        testTextures = {};
        mBackend = nullptr;
        DawnTestWithParams::TearDown();
    }

    ExternalImageDescriptorVkForTesting GetExternalImageDescriptor() {
        return ExternalImageDescriptorVkForTesting(GetParam().mExternalImageType);
    }

    ExternalImageExportInfoVkForTesting GetExternalImageExportInfo() {
        return ExternalImageExportInfoVkForTesting(GetParam().mExternalImageType);
    }

    wgpu::Texture WrapVulkanImage(wgpu::Device dawnDevice,
                                  const wgpu::TextureDescriptor* textureDescriptor,
                                  const ExternalTexture* externalTexture,
                                  std::vector<std::unique_ptr<ExternalSemaphore>> semaphores,
                                  bool isInitialized = true,
                                  bool expectValid = true) {
        ExternalImageDescriptorVkForTesting descriptor = GetExternalImageDescriptor();
        return WrapVulkanImage(dawnDevice, textureDescriptor, externalTexture,
                               std::move(semaphores), descriptor.releasedOldLayout,
                               descriptor.releasedNewLayout, isInitialized, expectValid);
    }

    wgpu::Texture WrapVulkanImage(wgpu::Device dawnDevice,
                                  const wgpu::TextureDescriptor* textureDescriptor,
                                  const ExternalTexture* externalTexture,
                                  std::vector<std::unique_ptr<ExternalSemaphore>> semaphores,
                                  VkImageLayout releasedOldLayout,
                                  VkImageLayout releasedNewLayout,
                                  bool isInitialized = true,
                                  bool expectValid = true) {
        ExternalImageDescriptorVkForTesting descriptor = GetExternalImageDescriptor();
        descriptor.cTextureDescriptor =
            reinterpret_cast<const WGPUTextureDescriptor*>(textureDescriptor);
        descriptor.isInitialized = isInitialized;
        descriptor.releasedOldLayout = releasedOldLayout;
        descriptor.releasedNewLayout = releasedNewLayout;

        wgpu::Texture texture =
            mBackend->WrapImage(dawnDevice, externalTexture, descriptor, std::move(semaphores));

        if (expectValid) {
            EXPECT_NE(texture.Get(), nullptr) << "Failed to wrap image, are external memory / "
                                                 "semaphore extensions supported?";
        } else {
            EXPECT_EQ(texture.Get(), nullptr);
        }

        return texture;
    }

    // Exports the signal from a wrapped texture and ignores it
    // We have to export the signal before destroying the wrapped texture else it's an
    // assertion failure
    void IgnoreSignalSemaphore(wgpu::Texture wrappedTexture) {
        ExternalImageExportInfoVkForTesting exportInfo = GetExternalImageExportInfo();
        bool result = mBackend->ExportImage(wrappedTexture, &exportInfo);
        DAWN_ASSERT(result);
    }

  protected:
    std::unique_ptr<VulkanImageWrappingTestBackend> mBackend;

    wgpu::TextureDescriptor defaultDescriptor;
    std::array<std::unique_ptr<ExternalTexture>, kTestTexturesCount> testTextures;
    raw_ptr<ExternalTexture> defaultTexture;
};

using VulkanImageWrappingValidationTests = VulkanImageWrappingTestBase;

// Test no error occurs if the import is valid
TEST_P(VulkanImageWrappingValidationTests, SuccessfulImport) {
    wgpu::Texture texture =
        WrapVulkanImage(device, &defaultDescriptor, defaultTexture, {}, true, true);
    EXPECT_NE(texture.Get(), nullptr);
    IgnoreSignalSemaphore(texture);
}

// Test no error occurs if the import is valid with DawnTextureInternalUsageDescriptor
TEST_P(VulkanImageWrappingValidationTests, SuccessfulImportWithInternalUsageDescriptor) {
    wgpu::DawnTextureInternalUsageDescriptor internalDesc = {};
    defaultDescriptor.nextInChain = &internalDesc;
    internalDesc.internalUsage = wgpu::TextureUsage::CopySrc;
    internalDesc.sType = wgpu::SType::DawnTextureInternalUsageDescriptor;

    wgpu::Texture texture =
        WrapVulkanImage(device, &defaultDescriptor, defaultTexture, {}, true, true);
    EXPECT_NE(texture.Get(), nullptr);
    IgnoreSignalSemaphore(texture);
}

// Test an error occurs if an invalid sType is the nextInChain
TEST_P(VulkanImageWrappingValidationTests, InvalidTextureDescriptor) {
    wgpu::ChainedStruct chainedDescriptor;
    chainedDescriptor.sType = wgpu::SType::SurfaceDescriptorFromWindowsUWPSwapChainPanel;
    defaultDescriptor.nextInChain = &chainedDescriptor;

    ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(device, &defaultDescriptor,
                                                                defaultTexture, {}, true, false));
    EXPECT_EQ(texture.Get(), nullptr);
}

// Test an error occurs if the descriptor dimension isn't 2D
TEST_P(VulkanImageWrappingValidationTests, InvalidTextureDimension) {
    defaultDescriptor.dimension = wgpu::TextureDimension::e1D;

    ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(device, &defaultDescriptor,
                                                                defaultTexture, {}, true, false));
    EXPECT_EQ(texture.Get(), nullptr);
}

// Test an error occurs if the descriptor mip level count isn't 1
TEST_P(VulkanImageWrappingValidationTests, InvalidMipLevelCount) {
    defaultDescriptor.mipLevelCount = 2;

    ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(device, &defaultDescriptor,
                                                                defaultTexture, {}, true, false));
    EXPECT_EQ(texture.Get(), nullptr);
}

// Test an error occurs if the descriptor depth isn't 1
TEST_P(VulkanImageWrappingValidationTests, InvalidDepth) {
    defaultDescriptor.size.depthOrArrayLayers = 2;

    ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(device, &defaultDescriptor,
                                                                defaultTexture, {}, true, false));
    EXPECT_EQ(texture.Get(), nullptr);
}

// Test an error occurs if the descriptor sample count isn't 1
TEST_P(VulkanImageWrappingValidationTests, InvalidSampleCount) {
    defaultDescriptor.sampleCount = 4;

    ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(device, &defaultDescriptor,
                                                                defaultTexture, {}, true, false));
    EXPECT_EQ(texture.Get(), nullptr);
}

// Test an error occurs if we try to export the signal semaphore twice
TEST_P(VulkanImageWrappingValidationTests, DoubleSignalSemaphoreExport) {
    wgpu::Texture texture =
        WrapVulkanImage(device, &defaultDescriptor, defaultTexture, {}, true, true);
    ASSERT_NE(texture.Get(), nullptr);
    IgnoreSignalSemaphore(texture);

    ExternalImageExportInfoVkForTesting exportInfo = GetExternalImageExportInfo();
    ASSERT_DEVICE_ERROR(bool success = mBackend->ExportImage(texture, &exportInfo));
    ASSERT_FALSE(success);
    ASSERT_EQ(exportInfo.semaphores.size(), 0u);
}

// Test an error occurs if we try to export the signal semaphore from a destroyed texture
TEST_P(VulkanImageWrappingValidationTests, DestroyedTextureSignalSemaphoreExport) {
    wgpu::Texture texture =
        WrapVulkanImage(device, &defaultDescriptor, defaultTexture, {}, true, true);
    ASSERT_NE(texture.Get(), nullptr);
    texture.Destroy();

    ExternalImageExportInfoVkForTesting exportInfo = GetExternalImageExportInfo();
    ASSERT_DEVICE_ERROR(bool success = mBackend->ExportImage(texture, &exportInfo));
    ASSERT_FALSE(success);
    ASSERT_EQ(exportInfo.semaphores.size(), 0u);
}

// Fixture to test using external memory textures through different usages.
// These tests are skipped if the harness is using the wire.
class VulkanImageWrappingUsageTests : public VulkanImageWrappingTestBase {
  public:
    void SetUp() override {
        VulkanImageWrappingTestBase::SetUp();
        if (UsesWire()) {
            return;
        }

        // Create another device based on the original
        adapterBase = native::FromAPI(device.Get())->GetAdapter();
        deviceDescriptor.nextInChain = &deviceTogglesDesc;
        deviceTogglesDesc.enabledToggles = GetParam().forceEnabledWorkarounds.data();
        deviceTogglesDesc.enabledToggleCount = GetParam().forceEnabledWorkarounds.size();
        deviceTogglesDesc.disabledToggles = GetParam().forceDisabledWorkarounds.data();
        deviceTogglesDesc.disabledToggleCount = GetParam().forceDisabledWorkarounds.size();

        secondDeviceVk = native::vulkan::ToBackend(adapterBase->APICreateDevice(&deviceDescriptor));
        secondDevice = wgpu::Device::Acquire(native::ToAPI(secondDeviceVk));
    }

  protected:
    raw_ptr<native::AdapterBase> adapterBase;
    native::DeviceDescriptor deviceDescriptor;
    native::DawnTogglesDescriptor deviceTogglesDesc;

    wgpu::Device secondDevice;
    raw_ptr<native::vulkan::Device> secondDeviceVk;

    // Clear a texture on a given device
    void ClearImage(wgpu::Device dawnDevice, wgpu::Texture wrappedTexture, wgpu::Color clearColor) {
        wgpu::TextureView wrappedView = wrappedTexture.CreateView();

        // Submit a clear operation
        utils::ComboRenderPassDescriptor renderPassDescriptor({wrappedView}, {});
        renderPassDescriptor.cColorAttachments[0].clearValue = clearColor;
        renderPassDescriptor.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear;

        wgpu::CommandEncoder encoder = dawnDevice.CreateCommandEncoder();
        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDescriptor);
        pass.End();

        wgpu::CommandBuffer commands = encoder.Finish();

        wgpu::Queue queue = dawnDevice.GetQueue();

        queue.Submit(1, &commands);
    }

    void ClearImages(wgpu::Device dawnDevice,
                     std::vector<wgpu::Texture> wrappedTextures,
                     wgpu::Color clearColor) {
        wgpu::CommandEncoder encoder = dawnDevice.CreateCommandEncoder();

        for (auto wrappedTexture : wrappedTextures) {
            wgpu::TextureView wrappedView = wrappedTexture.CreateView();

            // Submit a clear operation
            utils::ComboRenderPassDescriptor renderPassDescriptor({wrappedView}, {});
            renderPassDescriptor.cColorAttachments[0].clearValue = clearColor;
            renderPassDescriptor.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear;

            wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDescriptor);
            pass.End();
        }

        wgpu::CommandBuffer commands = encoder.Finish();

        wgpu::Queue queue = dawnDevice.GetQueue();

        queue.Submit(1, &commands);
    }

    // Submits a 1x1x1 copy from source to destination
    void SimpleCopyTextureToTexture(wgpu::Device dawnDevice,
                                    wgpu::Queue dawnQueue,
                                    wgpu::Texture source,
                                    wgpu::Texture destination) {
        wgpu::TexelCopyTextureInfo copySrc =
            utils::CreateTexelCopyTextureInfo(source, 0, {0, 0, 0});
        wgpu::TexelCopyTextureInfo copyDst =
            utils::CreateTexelCopyTextureInfo(destination, 0, {0, 0, 0});

        wgpu::Extent3D copySize = {1, 1, 1};

        wgpu::CommandEncoder encoder = dawnDevice.CreateCommandEncoder();
        encoder.CopyTextureToTexture(&copySrc, &copyDst, &copySize);
        wgpu::CommandBuffer commands = encoder.Finish();

        dawnQueue.Submit(1, &commands);
    }
};

// Clear an image in |secondDevice|
// Verify clear color is visible in |device|
TEST_P(VulkanImageWrappingUsageTests, ClearImageAcrossDevices) {
    // Import the image on |secondDevice|
    wgpu::Texture wrappedTexture =
        WrapVulkanImage(secondDevice, &defaultDescriptor, defaultTexture, {},
                        VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
    ASSERT_NE(wrappedTexture.Get(), nullptr);

    // Clear |wrappedTexture| on |secondDevice|
    ClearImage(secondDevice, 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, defaultTexture, std::move(exportInfo.semaphores),
        exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
    ASSERT_NE(nextWrappedTexture.Get(), nullptr);

    // Verify |device| sees the changes from |secondDevice|
    EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(1, 2, 3, 4), nextWrappedTexture, 0, 0);

    IgnoreSignalSemaphore(nextWrappedTexture);
}

// Clear two images in |secondDevice|
// Verify clear color is visible in |device|
// This is intended to verify that waiting on the signalFd for one external texture does not affect
// those of other external textures.
TEST_P(VulkanImageWrappingUsageTests, ClearTwoImagesAcrossDevices) {
    // TODO(crbug.com/341124484): Fails on Linux/Intel UHD 770.
    DAWN_SUPPRESS_TEST_IF(IsLinux() && IsBackendValidationEnabled() && IsIntelGen12());

    // crbug.com/358408563
    DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan() && IsBackendValidationEnabled());

    static_assert(kTestTexturesCount >= 2);

    std::vector<wgpu::Texture> wrappedTextures;
    for (int i = 0; i < 2; ++i) {
        // Import the images on |secondDevice|
        wgpu::Texture wrappedTexture =
            WrapVulkanImage(secondDevice, &defaultDescriptor, testTextures[i].get(), {},
                            VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
        ASSERT_NE(wrappedTexture.Get(), nullptr);
        wrappedTextures.push_back(wrappedTexture);
    }

    // Clear |wrappedTextures| on |secondDevice|
    ClearImages(secondDevice, wrappedTextures, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});

    for (int i = 0; i < 2; ++i) {
        ExternalImageExportInfoVkForTesting exportInfo = GetExternalImageExportInfo();
        ASSERT_TRUE(mBackend->ExportImage(wrappedTextures[i], &exportInfo));

        // Import the image to |device|, making sure we wait on signalFd
        wgpu::Texture nextWrappedTexture = WrapVulkanImage(
            device, &defaultDescriptor, testTextures[i].get(), std::move(exportInfo.semaphores),
            exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
        ASSERT_NE(nextWrappedTexture.Get(), nullptr);

        // Verify |device| sees the changes from |secondDevice|
        EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(1, 2, 3, 4), nextWrappedTexture, 0, 0);

        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, UninitializedTextureIsCleared) {
    // Import the image on |secondDevice|
    wgpu::Texture wrappedTexture =
        WrapVulkanImage(secondDevice, &defaultDescriptor, defaultTexture, {},
                        VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
    ASSERT_NE(wrappedTexture.Get(), nullptr);

    // Clear |wrappedTexture| on |secondDevice|
    ClearImage(secondDevice, 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, defaultTexture, std::move(exportInfo.semaphores),
        exportInfo.releasedOldLayout, exportInfo.releasedNewLayout, false);
    ASSERT_NE(nextWrappedTexture.Get(), nullptr);

    // Verify |device| doesn't see the changes from |secondDevice|
    EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(0, 0, 0, 0), nextWrappedTexture, 0, 0);

    IgnoreSignalSemaphore(nextWrappedTexture);
}

// Import a texture into |secondDevice|
// Clear the texture on |secondDevice|
// Issue a copy of the imported texture inside |device| to |copyDstTexture|
// Verify the clear color from |secondDevice| is visible in |copyDstTexture|
TEST_P(VulkanImageWrappingUsageTests, CopyTextureToTextureSrcSync) {
    // Import the image on |secondDevice|
    wgpu::Texture wrappedTexture =
        WrapVulkanImage(secondDevice, &defaultDescriptor, defaultTexture, {},
                        VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
    ASSERT_NE(wrappedTexture.Get(), nullptr);

    // Clear |wrappedTexture| on |secondDevice|
    ClearImage(secondDevice, 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 deviceWrappedTexture = WrapVulkanImage(
        device, &defaultDescriptor, defaultTexture, std::move(exportInfo.semaphores),
        exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
    ASSERT_NE(deviceWrappedTexture.Get(), nullptr);

    // Create a second texture on |device|
    wgpu::Texture copyDstTexture = device.CreateTexture(&defaultDescriptor);

    // Copy |deviceWrappedTexture| into |copyDstTexture|
    SimpleCopyTextureToTexture(device, queue, deviceWrappedTexture, copyDstTexture);

    // Verify |copyDstTexture| sees changes from |secondDevice|
    EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(1, 2, 3, 4), copyDstTexture, 0, 0);

    IgnoreSignalSemaphore(deviceWrappedTexture);
}

// Import a texture into |device|
// Clear texture with color A on |device|
// Import same texture into |secondDevice|, waiting on the copy signal
// Clear the new texture with color B on |secondDevice|
// Copy color B using Texture to Texture copy on |secondDevice|
// Import texture back into |device|, waiting on color B signal
// Verify texture contains color B
// If texture destination isn't synchronized, |secondDevice| could copy color B
// into the texture first, then |device| writes color A
TEST_P(VulkanImageWrappingUsageTests, CopyTextureToTextureDstSync) {
    // Import the image on |device|
    wgpu::Texture wrappedTexture =
        WrapVulkanImage(device, &defaultDescriptor, defaultTexture, {}, VK_IMAGE_LAYOUT_UNDEFINED,
                        VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
    ASSERT_NE(wrappedTexture.Get(), nullptr);

    // Clear |wrappedTexture| on |device|
    ClearImage(device, wrappedTexture, {5 / 255.0f, 6 / 255.0f, 7 / 255.0f, 8 / 255.0f});

    ExternalImageExportInfoVkForTesting exportInfo = GetExternalImageExportInfo();
    ASSERT_TRUE(mBackend->ExportImage(wrappedTexture, &exportInfo));

    // Import the image to |secondDevice|, making sure we wait on |signalFd|
    wgpu::Texture secondDeviceWrappedTexture = WrapVulkanImage(
        secondDevice, &defaultDescriptor, defaultTexture, std::move(exportInfo.semaphores),
        exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
    ASSERT_NE(secondDeviceWrappedTexture.Get(), nullptr);

    // Create a texture with color B on |secondDevice|
    wgpu::Texture copySrcTexture = secondDevice.CreateTexture(&defaultDescriptor);
    ClearImage(secondDevice, copySrcTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});

    // Copy color B on |secondDevice|
    wgpu::Queue secondDeviceQueue = secondDevice.GetQueue();
    SimpleCopyTextureToTexture(secondDevice, secondDeviceQueue, copySrcTexture,
                               secondDeviceWrappedTexture);

    // Re-import back into |device|, waiting on |secondDevice|'s signal
    ExternalImageExportInfoVkForTesting secondExportInfo = GetExternalImageExportInfo();
    ASSERT_TRUE(mBackend->ExportImage(secondDeviceWrappedTexture, &secondExportInfo));

    wgpu::Texture nextWrappedTexture = WrapVulkanImage(
        device, &defaultDescriptor, defaultTexture, std::move(secondExportInfo.semaphores),
        secondExportInfo.releasedOldLayout, secondExportInfo.releasedNewLayout);
    ASSERT_NE(nextWrappedTexture.Get(), nullptr);

    // Verify |nextWrappedTexture| contains the color from our copy
    EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(1, 2, 3, 4), nextWrappedTexture, 0, 0);

    IgnoreSignalSemaphore(nextWrappedTexture);
}

// Import a texture from |secondDevice|
// Clear the texture on |secondDevice|
// Issue a copy of the imported texture inside |device| to |copyDstBuffer|
// Verify the clear color from |secondDevice| is visible in |copyDstBuffer|
TEST_P(VulkanImageWrappingUsageTests, CopyTextureToBufferSrcSync) {
    // Import the image on |secondDevice|
    wgpu::Texture wrappedTexture =
        WrapVulkanImage(secondDevice, &defaultDescriptor, defaultTexture, {},
                        VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
    ASSERT_NE(wrappedTexture.Get(), nullptr);

    // Clear |wrappedTexture| on |secondDevice|
    ClearImage(secondDevice, 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 deviceWrappedTexture = WrapVulkanImage(
        device, &defaultDescriptor, defaultTexture, std::move(exportInfo.semaphores),
        exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
    ASSERT_NE(deviceWrappedTexture.Get(), nullptr);

    // Create a destination buffer on |device|
    wgpu::BufferDescriptor bufferDesc;
    bufferDesc.size = 4;
    bufferDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc;
    wgpu::Buffer copyDstBuffer = device.CreateBuffer(&bufferDesc);

    // Copy |deviceWrappedTexture| into |copyDstBuffer|
    wgpu::TexelCopyTextureInfo copySrc =
        utils::CreateTexelCopyTextureInfo(deviceWrappedTexture, 0, {0, 0, 0});
    wgpu::TexelCopyBufferInfo copyDst = utils::CreateTexelCopyBufferInfo(copyDstBuffer, 0, 256);

    wgpu::Extent3D copySize = {1, 1, 1};

    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
    encoder.CopyTextureToBuffer(&copySrc, &copyDst, &copySize);
    wgpu::CommandBuffer commands = encoder.Finish();
    queue.Submit(1, &commands);

    // Verify |copyDstBuffer| sees changes from |secondDevice|
    uint32_t expected = 0x04030201;
    EXPECT_BUFFER_U32_EQ(expected, copyDstBuffer, 0);

    IgnoreSignalSemaphore(deviceWrappedTexture);
}

// Import a texture into |device|
// Clear texture with color A on |device|
// Import same texture into |secondDevice|, waiting on the copy signal
// Copy color B using Buffer to Texture copy on |secondDevice|
// Import texture back into |device|, waiting on color B signal
// Verify texture contains color B
// If texture destination isn't synchronized, |secondDevice| could copy color B
// into the texture first, then |device| writes color A
TEST_P(VulkanImageWrappingUsageTests, CopyBufferToTextureDstSync) {
    // Import the image on |device|
    wgpu::Texture wrappedTexture =
        WrapVulkanImage(device, &defaultDescriptor, defaultTexture, {}, VK_IMAGE_LAYOUT_UNDEFINED,
                        VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
    ASSERT_NE(wrappedTexture.Get(), nullptr);

    // Clear |wrappedTexture| on |device|
    ClearImage(device, wrappedTexture, {5 / 255.0f, 6 / 255.0f, 7 / 255.0f, 8 / 255.0f});

    ExternalImageExportInfoVkForTesting exportInfo = GetExternalImageExportInfo();
    ASSERT_TRUE(mBackend->ExportImage(wrappedTexture, &exportInfo));

    // Import the image to |secondDevice|, making sure we wait on |signalFd|
    wgpu::Texture secondDeviceWrappedTexture = WrapVulkanImage(
        secondDevice, &defaultDescriptor, defaultTexture, std::move(exportInfo.semaphores),
        exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
    ASSERT_NE(secondDeviceWrappedTexture.Get(), nullptr);

    // Copy color B on |secondDevice|
    wgpu::Queue secondDeviceQueue = secondDevice.GetQueue();

    // Create a buffer on |secondDevice|
    wgpu::Buffer copySrcBuffer =
        utils::CreateBufferFromData(secondDevice, wgpu::BufferUsage::CopySrc, {0x04030201});

    // Copy |copySrcBuffer| into |secondDeviceWrappedTexture|
    wgpu::TexelCopyBufferInfo copySrc = utils::CreateTexelCopyBufferInfo(copySrcBuffer, 0, 256);
    wgpu::TexelCopyTextureInfo copyDst =
        utils::CreateTexelCopyTextureInfo(secondDeviceWrappedTexture, 0, {0, 0, 0});

    wgpu::Extent3D copySize = {1, 1, 1};

    wgpu::CommandEncoder encoder = secondDevice.CreateCommandEncoder();
    encoder.CopyBufferToTexture(&copySrc, &copyDst, &copySize);
    wgpu::CommandBuffer commands = encoder.Finish();
    secondDeviceQueue.Submit(1, &commands);

    // Re-import back into |device|, waiting on |secondDevice|'s signal
    ExternalImageExportInfoVkForTesting secondExportInfo = GetExternalImageExportInfo();
    ASSERT_TRUE(mBackend->ExportImage(secondDeviceWrappedTexture, &secondExportInfo));

    wgpu::Texture nextWrappedTexture = WrapVulkanImage(
        device, &defaultDescriptor, defaultTexture, std::move(secondExportInfo.semaphores),
        secondExportInfo.releasedOldLayout, secondExportInfo.releasedNewLayout);
    ASSERT_NE(nextWrappedTexture.Get(), nullptr);

    // Verify |nextWrappedTexture| contains the color from our copy
    EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(1, 2, 3, 4), nextWrappedTexture, 0, 0);

    IgnoreSignalSemaphore(nextWrappedTexture);
}

// Import a texture from |secondDevice|
// Clear the texture on |secondDevice|
// Issue a copy of the imported texture inside |device| to |copyDstTexture|
// Issue second copy to |secondCopyDstTexture|
// Verify the clear color from |secondDevice| is visible in both copies
TEST_P(VulkanImageWrappingUsageTests, DoubleTextureUsage) {
    // Import the image on |secondDevice|
    wgpu::Texture wrappedTexture =
        WrapVulkanImage(secondDevice, &defaultDescriptor, defaultTexture, {},
                        VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
    ASSERT_NE(wrappedTexture.Get(), nullptr);

    // Clear |wrappedTexture| on |secondDevice|
    ClearImage(secondDevice, 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 deviceWrappedTexture = WrapVulkanImage(
        device, &defaultDescriptor, defaultTexture, std::move(exportInfo.semaphores),
        exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
    ASSERT_NE(deviceWrappedTexture.Get(), nullptr);

    // Create a second texture on |device|
    wgpu::Texture copyDstTexture = device.CreateTexture(&defaultDescriptor);

    // Create a third texture on |device|
    wgpu::Texture secondCopyDstTexture = device.CreateTexture(&defaultDescriptor);

    // Copy |deviceWrappedTexture| into |copyDstTexture|
    SimpleCopyTextureToTexture(device, queue, deviceWrappedTexture, copyDstTexture);

    // Copy |deviceWrappedTexture| into |secondCopyDstTexture|
    SimpleCopyTextureToTexture(device, queue, deviceWrappedTexture, secondCopyDstTexture);

    // Verify |copyDstTexture| sees changes from |secondDevice|
    EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(1, 2, 3, 4), copyDstTexture, 0, 0);

    // Verify |secondCopyDstTexture| sees changes from |secondDevice|
    EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(1, 2, 3, 4), secondCopyDstTexture, 0, 0);

    IgnoreSignalSemaphore(deviceWrappedTexture);
}

// Tex A on device 3 (external export)
// Tex B on device 2 (external export)
// Tex C on device 1 (external export)
// Clear color for A on device 3
// Copy A->B on device 3
// Copy B->C on device 2 (wait on B from previous op)
// Copy C->D on device 1 (wait on C from previous op)
// Verify D has same color as A
TEST_P(VulkanImageWrappingUsageTests, ChainTextureCopy) {
    // device 1 = |device|
    // device 2 = |secondDevice|
    // Create device 3
    native::vulkan::Device* thirdDeviceVk =
        native::vulkan::ToBackend(adapterBase->APICreateDevice(&deviceDescriptor));
    wgpu::Device thirdDevice = wgpu::Device::Acquire(native::ToAPI(thirdDeviceVk));

    // Make queue for device 2 and 3
    wgpu::Queue secondDeviceQueue = secondDevice.GetQueue();
    wgpu::Queue thirdDeviceQueue = thirdDevice.GetQueue();

    // Create textures A, B, C
    std::unique_ptr<ExternalTexture> textureA =
        mBackend->CreateTexture(1, 1, wgpu::TextureFormat::RGBA8Unorm, defaultDescriptor.usage);
    std::unique_ptr<ExternalTexture> textureB =
        mBackend->CreateTexture(1, 1, wgpu::TextureFormat::RGBA8Unorm, defaultDescriptor.usage);
    std::unique_ptr<ExternalTexture> textureC =
        mBackend->CreateTexture(1, 1, wgpu::TextureFormat::RGBA8Unorm, defaultDescriptor.usage);

    // Import TexA, TexB on device 3
    wgpu::Texture wrappedTexADevice3 =
        WrapVulkanImage(thirdDevice, &defaultDescriptor, textureA.get(), {},
                        VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
    ASSERT_NE(wrappedTexADevice3.Get(), nullptr);

    wgpu::Texture wrappedTexBDevice3 =
        WrapVulkanImage(thirdDevice, &defaultDescriptor, textureB.get(), {},
                        VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
    ASSERT_NE(wrappedTexBDevice3.Get(), nullptr);

    // Clear TexA
    ClearImage(thirdDevice, wrappedTexADevice3, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});

    // Copy A->B
    SimpleCopyTextureToTexture(thirdDevice, thirdDeviceQueue, wrappedTexADevice3,
                               wrappedTexBDevice3);

    ExternalImageExportInfoVkForTesting exportInfoTexBDevice3 = GetExternalImageExportInfo();
    ASSERT_TRUE(mBackend->ExportImage(wrappedTexBDevice3, &exportInfoTexBDevice3));
    IgnoreSignalSemaphore(wrappedTexADevice3);

    // Import TexB, TexC on device 2
    wgpu::Texture wrappedTexBDevice2 = WrapVulkanImage(
        secondDevice, &defaultDescriptor, textureB.get(),
        std::move(exportInfoTexBDevice3.semaphores), exportInfoTexBDevice3.releasedOldLayout,
        exportInfoTexBDevice3.releasedNewLayout);
    ASSERT_NE(wrappedTexBDevice2.Get(), nullptr);

    wgpu::Texture wrappedTexCDevice2 =
        WrapVulkanImage(secondDevice, &defaultDescriptor, textureC.get(), {},
                        VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
    ASSERT_NE(wrappedTexCDevice2.Get(), nullptr);

    // Copy B->C on device 2
    SimpleCopyTextureToTexture(secondDevice, secondDeviceQueue, wrappedTexBDevice2,
                               wrappedTexCDevice2);

    ExternalImageExportInfoVkForTesting exportInfoTexCDevice2 = GetExternalImageExportInfo();
    ASSERT_TRUE(mBackend->ExportImage(wrappedTexCDevice2, &exportInfoTexCDevice2));
    IgnoreSignalSemaphore(wrappedTexBDevice2);

    // Import TexC on device 1
    wgpu::Texture wrappedTexCDevice1 = WrapVulkanImage(
        device, &defaultDescriptor, textureC.get(), std::move(exportInfoTexCDevice2.semaphores),
        exportInfoTexCDevice2.releasedOldLayout, exportInfoTexCDevice2.releasedNewLayout);
    ASSERT_NE(wrappedTexCDevice1.Get(), nullptr);

    // Create TexD on device 1
    wgpu::Texture texD = device.CreateTexture(&defaultDescriptor);

    // Copy C->D on device 1
    SimpleCopyTextureToTexture(device, queue, wrappedTexCDevice1, texD);

    // Verify D matches clear color
    EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(1, 2, 3, 4), texD, 0, 0);

    IgnoreSignalSemaphore(wrappedTexCDevice1);
}

// Tests a larger image is preserved when importing
TEST_P(VulkanImageWrappingUsageTests, LargerImage) {
    wgpu::TextureDescriptor descriptor;
    descriptor.dimension = wgpu::TextureDimension::e2D;
    descriptor.size.width = 640;
    descriptor.size.height = 480;
    descriptor.size.depthOrArrayLayers = 1;
    descriptor.sampleCount = 1;
    descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
    descriptor.mipLevelCount = 1;
    descriptor.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc;

    // Fill memory with textures
    std::vector<wgpu::Texture> textures;
    for (int i = 0; i < 20; i++) {
        textures.push_back(device.CreateTexture(&descriptor));
    }

    wgpu::Queue secondDeviceQueue = secondDevice.GetQueue();

    // Make an image on |secondDevice|
    std::unique_ptr<ExternalTexture> texture = mBackend->CreateTexture(
        descriptor.size.width, descriptor.size.height, descriptor.format, descriptor.usage);

    // Import the image on |secondDevice|
    wgpu::Texture wrappedTexture =
        WrapVulkanImage(secondDevice, &descriptor, texture.get(), {}, VK_IMAGE_LAYOUT_UNDEFINED,
                        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
    ASSERT_NE(wrappedTexture.Get(), nullptr);

    // Draw a non-trivial picture
    uint32_t width = 640, height = 480, pixelSize = 4;
    uint32_t bytesPerRow = Align(width * pixelSize, kTextureBytesPerRowAlignment);
    std::vector<unsigned char> data(bytesPerRow * (height - 1) + width * pixelSize);

    for (uint32_t row = 0; row < height; row++) {
        for (uint32_t col = 0; col < width; col++) {
            float normRow = static_cast<float>(row) / height;
            float normCol = static_cast<float>(col) / width;
            float dist = sqrt(normRow * normRow + normCol * normCol) * 3;
            dist = dist - static_cast<int>(dist);
            data[4 * (row * width + col)] = static_cast<unsigned char>(dist * 255);
            data[4 * (row * width + col) + 1] = static_cast<unsigned char>(dist * 255);
            data[4 * (row * width + col) + 2] = static_cast<unsigned char>(dist * 255);
            data[4 * (row * width + col) + 3] = 255;
        }
    }

    // Write the picture
    {
        wgpu::Buffer copySrcBuffer = utils::CreateBufferFromData(
            secondDevice, data.data(), data.size(), wgpu::BufferUsage::CopySrc);
        wgpu::TexelCopyBufferInfo copySrc =
            utils::CreateTexelCopyBufferInfo(copySrcBuffer, 0, bytesPerRow);
        wgpu::TexelCopyTextureInfo copyDst =
            utils::CreateTexelCopyTextureInfo(wrappedTexture, 0, {0, 0, 0});
        wgpu::Extent3D copySize = {width, height, 1};

        wgpu::CommandEncoder encoder = secondDevice.CreateCommandEncoder();
        encoder.CopyBufferToTexture(&copySrc, &copyDst, &copySize);
        wgpu::CommandBuffer commands = encoder.Finish();
        secondDeviceQueue.Submit(1, &commands);
    }
    ExternalImageExportInfoVkForTesting exportInfo = GetExternalImageExportInfo();
    ASSERT_TRUE(mBackend->ExportImage(wrappedTexture, &exportInfo));

    // Import the image on |device|
    wgpu::Texture nextWrappedTexture =
        WrapVulkanImage(device, &descriptor, texture.get(), std::move(exportInfo.semaphores),
                        exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
    ASSERT_NE(nextWrappedTexture.Get(), nullptr);

    // Copy the image into a buffer for comparison
    wgpu::BufferDescriptor copyDesc;
    copyDesc.size = data.size();
    copyDesc.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
    wgpu::Buffer copyDstBuffer = device.CreateBuffer(&copyDesc);
    {
        wgpu::TexelCopyTextureInfo copySrc =
            utils::CreateTexelCopyTextureInfo(nextWrappedTexture, 0, {0, 0, 0});
        wgpu::TexelCopyBufferInfo copyDst =
            utils::CreateTexelCopyBufferInfo(copyDstBuffer, 0, bytesPerRow);

        wgpu::Extent3D copySize = {width, height, 1};

        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        encoder.CopyTextureToBuffer(&copySrc, &copyDst, &copySize);
        wgpu::CommandBuffer commands = encoder.Finish();
        queue.Submit(1, &commands);
    }

    // Check the image is not corrupted on |device|
    DAWN_UNSAFE_TODO(EXPECT_BUFFER_U32_RANGE_EQ(reinterpret_cast<uint32_t*>(data.data()),
                                                copyDstBuffer, 0, data.size() / 4));

    IgnoreSignalSemaphore(nextWrappedTexture);
}

// Test that texture descriptor view formats are passed to the backend for wrapped external
// textures, and that contents may be reinterpreted as sRGB.
TEST_P(VulkanImageWrappingUsageTests, SRGBReinterpretation) {
    wgpu::TextureViewDescriptor viewDesc = {};
    viewDesc.format = wgpu::TextureFormat::RGBA8UnormSrgb;

    wgpu::TextureDescriptor textureDesc = {};
    textureDesc.size = {2, 2, 1};
    textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
    textureDesc.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding;
    textureDesc.viewFormatCount = 1;
    textureDesc.viewFormats = &viewDesc.format;

    std::unique_ptr<ExternalTexture> backendTexture = mBackend->CreateTexture(
        textureDesc.size.width, textureDesc.size.height, textureDesc.format, textureDesc.usage);

    // Import the image on |device|
    wgpu::Texture texture =
        WrapVulkanImage(device, &textureDesc, backendTexture.get(), {}, VK_IMAGE_LAYOUT_UNDEFINED,
                        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
    ASSERT_NE(texture.Get(), nullptr);

    wgpu::TexelCopyTextureInfo dst = {};
    dst.texture = texture;
    std::array<utils::RGBA8, 4> rgbaTextureData = {
        utils::RGBA8(180, 0, 0, 255),
        utils::RGBA8(0, 84, 0, 127),
        utils::RGBA8(0, 0, 62, 100),
        utils::RGBA8(62, 180, 84, 90),
    };

    wgpu::TexelCopyBufferLayout dataLayout = {};
    dataLayout.bytesPerRow = textureDesc.size.width * sizeof(utils::RGBA8);

    queue.WriteTexture(&dst, rgbaTextureData.data(), rgbaTextureData.size() * sizeof(utils::RGBA8),
                       &dataLayout, &textureDesc.size);

    wgpu::TextureView textureView = texture.CreateView(&viewDesc);

    utils::ComboRenderPipelineDescriptor pipelineDesc;
    pipelineDesc.vertex.module = utils::CreateShaderModule(device, R"(
            @vertex
            fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f {
                var pos = array(
                                            vec2f(-1.0, -1.0),
                                            vec2f(-1.0,  1.0),
                                            vec2f( 1.0, -1.0),
                                            vec2f(-1.0,  1.0),
                                            vec2f( 1.0, -1.0),
                                            vec2f( 1.0,  1.0));
                return vec4f(pos[VertexIndex], 0.0, 1.0);
            }
        )");
    pipelineDesc.cFragment.module = utils::CreateShaderModule(device, R"(
            @group(0) @binding(0) var texture : texture_2d<f32>;

            @fragment
            fn main(@builtin(position) coord: vec4f) -> @location(0) vec4f {
                return textureLoad(texture, vec2i(coord.xy), 0);
            }
        )");

    utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(
        device, textureDesc.size.width, textureDesc.size.height, wgpu::TextureFormat::RGBA8Unorm);
    pipelineDesc.cTargets[0].format = renderPass.colorFormat;

    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
    {
        wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDesc);

        wgpu::BindGroup bindGroup =
            utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, textureView}});

        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
        pass.SetPipeline(pipeline);
        pass.SetBindGroup(0, bindGroup);
        pass.Draw(6);
        pass.End();
    }

    wgpu::CommandBuffer commands = encoder.Finish();
    queue.Submit(1, &commands);

    EXPECT_PIXEL_RGBA8_BETWEEN(        //
        utils::RGBA8(116, 0, 0, 255),  //
        utils::RGBA8(117, 0, 0, 255), renderPass.color, 0, 0);
    EXPECT_PIXEL_RGBA8_BETWEEN(       //
        utils::RGBA8(0, 23, 0, 127),  //
        utils::RGBA8(0, 24, 0, 127), renderPass.color, 1, 0);
    EXPECT_PIXEL_RGBA8_BETWEEN(       //
        utils::RGBA8(0, 0, 12, 100),  //
        utils::RGBA8(0, 0, 13, 100), renderPass.color, 0, 1);
    EXPECT_PIXEL_RGBA8_BETWEEN(         //
        utils::RGBA8(12, 116, 23, 90),  //
        utils::RGBA8(13, 117, 24, 90), renderPass.color, 1, 1);

    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) {
    // TODO(crbug.com/341124484): Crashes on Linux/Intel UHD 770.
    DAWN_SUPPRESS_TEST_IF(IsLinux() && IsBackendValidationEnabled() && IsIntelGen12());

    // crbug.com/358408563
    DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan() && IsBackendValidationEnabled());

    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);
        ASSERT_NE(wrappedTexture.Get(), nullptr);

        // 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);
        ASSERT_NE(nextWrappedTexture.Get(), nullptr);

        // 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()},
                        {ExternalImageType::OpaqueFD, ExternalImageType::DmaBuf},
                        {true, false},  // UseDedicatedAllocation
                        {true, false}   // DetectDedicatedAllocation
);
DAWN_INSTANTIATE_TEST_P(VulkanImageWrappingUsageTests,
                        {VulkanBackend()},
                        {ExternalImageType::OpaqueFD, ExternalImageType::DmaBuf},
                        {true, false},  // UseDedicatedAllocation
                        {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
