//
// Copyright 2019 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

// VulkanExternalImageTest.cpp : Tests of images allocated externally using Vulkan.

#include "test_utils/ANGLETest.h"

#include "common/debug.h"
#include "test_utils/VulkanHelper.h"
#include "test_utils/gl_raii.h"

namespace angle
{

namespace
{

constexpr int kInvalidFd = -1;

constexpr VkImageUsageFlags kDefaultImageUsageFlags =
    VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT |
    VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
    VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
constexpr VkImageCreateFlags kDefaultImageCreateFlags = 0;

constexpr VkImageUsageFlags kNoStorageImageUsageFlags =
    kDefaultImageUsageFlags & ~VK_IMAGE_USAGE_STORAGE_BIT;
constexpr VkImageCreateFlags kMutableImageCreateFlags =
    kDefaultImageCreateFlags | VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;

// List of VkFormat/internalformat combinations Chrome uses.
// This is compiled from the maps in
// components/viz/common/resources/resource_format_utils.cc.
const struct ImageFormatPair
{
    VkFormat vkFormat;
    GLenum internalFormat;
    const char *requiredExtension;
} kChromeFormats[] = {
    {VK_FORMAT_R8G8B8A8_UNORM, GL_RGBA8_OES},                    // RGBA_8888
    {VK_FORMAT_B8G8R8A8_UNORM, GL_BGRA8_EXT},                    // BGRA_8888
    {VK_FORMAT_R4G4B4A4_UNORM_PACK16, GL_RGBA4},                 // RGBA_4444
    {VK_FORMAT_R16G16B16A16_SFLOAT, GL_RGBA16F_EXT},             // RGBA_F16
    {VK_FORMAT_R8_UNORM, GL_R8_EXT},                             // RED_8
    {VK_FORMAT_R5G6B5_UNORM_PACK16, GL_RGB565},                  // RGB_565
    {VK_FORMAT_R16_UNORM, GL_R16_EXT, "GL_EXT_texture_norm16"},  // R16_EXT
    {VK_FORMAT_A2B10G10R10_UNORM_PACK32, GL_RGB10_A2_EXT},       // RGBA_1010102
    {VK_FORMAT_R8_UNORM, GL_ALPHA8_EXT},                         // ALPHA_8
    {VK_FORMAT_R8_UNORM, GL_LUMINANCE8_EXT},                     // LUMINANCE_8
    {VK_FORMAT_R8G8_UNORM, GL_RG8_EXT},                          // RG_88
    {VK_FORMAT_R8G8B8A8_UNORM, GL_RGB8_OES},                     // RGBX_8888
};

struct OpaqueFdTraits
{
    using Handle = int;
    static Handle InvalidHandle() { return kInvalidFd; }

    static const char *MemoryObjectExtension() { return "GL_EXT_memory_object_fd"; }
    static const char *SemaphoreExtension() { return "GL_EXT_semaphore_fd"; }

    static bool CanCreateSemaphore(const VulkanHelper &helper)
    {
        return helper.canCreateSemaphoreOpaqueFd();
    }

    static VkResult CreateSemaphore(VulkanHelper *helper, VkSemaphore *semaphore)
    {
        return helper->createSemaphoreOpaqueFd(semaphore);
    }

    static VkResult ExportSemaphore(VulkanHelper *helper, VkSemaphore semaphore, Handle *handle)
    {
        return helper->exportSemaphoreOpaqueFd(semaphore, handle);
    }

    static void ImportSemaphore(GLuint semaphore, Handle handle)
    {
        glImportSemaphoreFdEXT(semaphore, GL_HANDLE_TYPE_OPAQUE_FD_EXT, handle);
    }

    static bool CanCreateImage(const VulkanHelper &helper,
                               VkFormat format,
                               VkImageType type,
                               VkImageTiling tiling,
                               VkImageCreateFlags createFlags,
                               VkImageUsageFlags usageFlags)
    {
        return helper.canCreateImageOpaqueFd(format, type, tiling, createFlags, usageFlags);
    }

    static VkResult CreateImage2D(VulkanHelper *helper,
                                  VkFormat format,
                                  VkImageCreateFlags createFlags,
                                  VkImageUsageFlags usageFlags,
                                  const void *imageCreateInfoPNext,
                                  VkExtent3D extent,
                                  VkImage *imageOut,
                                  VkDeviceMemory *deviceMemoryOut,
                                  VkDeviceSize *deviceMemorySizeOut)
    {
        return helper->createImage2DOpaqueFd(format, createFlags, usageFlags, imageCreateInfoPNext,
                                             extent, imageOut, deviceMemoryOut,
                                             deviceMemorySizeOut);
    }

    static VkResult ExportMemory(VulkanHelper *helper, VkDeviceMemory deviceMemory, Handle *handle)
    {
        return helper->exportMemoryOpaqueFd(deviceMemory, handle);
    }

    static void ImportMemory(GLuint memoryObject, GLuint64 size, Handle handle)
    {
        glImportMemoryFdEXT(memoryObject, size, GL_HANDLE_TYPE_OPAQUE_FD_EXT, handle);
    }
};

struct FuchsiaTraits
{
    using Handle = zx_handle_t;

    static Handle InvalidHandle() { return ZX_HANDLE_INVALID; }

    static const char *MemoryObjectExtension() { return "GL_ANGLE_memory_object_fuchsia"; }
    static const char *SemaphoreExtension() { return "GL_ANGLE_semaphore_fuchsia"; }

    static bool CanCreateSemaphore(const VulkanHelper &helper)
    {
        return helper.canCreateSemaphoreZirconEvent();
    }

    static VkResult CreateSemaphore(VulkanHelper *helper, VkSemaphore *semaphore)
    {
        return helper->createSemaphoreZirconEvent(semaphore);
    }

    static VkResult ExportSemaphore(VulkanHelper *helper, VkSemaphore semaphore, Handle *handle)
    {
        return helper->exportSemaphoreZirconEvent(semaphore, handle);
    }

    static void ImportSemaphore(GLuint semaphore, Handle handle)
    {
        glImportSemaphoreZirconHandleANGLE(semaphore, GL_HANDLE_TYPE_ZIRCON_EVENT_ANGLE, handle);
    }

    static bool CanCreateImage(const VulkanHelper &helper,
                               VkFormat format,
                               VkImageType type,
                               VkImageTiling tiling,
                               VkImageCreateFlags createFlags,
                               VkImageUsageFlags usageFlags)
    {
        return helper.canCreateImageZirconVmo(format, type, tiling, createFlags, usageFlags);
    }

    static VkResult CreateImage2D(VulkanHelper *helper,
                                  VkFormat format,
                                  VkImageCreateFlags createFlags,
                                  VkImageUsageFlags usageFlags,
                                  const void *imageCreateInfoPNext,
                                  VkExtent3D extent,
                                  VkImage *imageOut,
                                  VkDeviceMemory *deviceMemoryOut,
                                  VkDeviceSize *deviceMemorySizeOut)
    {
        return helper->createImage2DZirconVmo(format, createFlags, usageFlags, imageCreateInfoPNext,
                                              extent, imageOut, deviceMemoryOut,
                                              deviceMemorySizeOut);
    }

    static VkResult ExportMemory(VulkanHelper *helper, VkDeviceMemory deviceMemory, Handle *handle)
    {
        return helper->exportMemoryZirconVmo(deviceMemory, handle);
    }

    static void ImportMemory(GLuint memoryObject, GLuint64 size, Handle handle)
    {
        glImportMemoryZirconHandleANGLE(memoryObject, size, GL_HANDLE_TYPE_ZIRCON_VMO_ANGLE,
                                        handle);
    }
};

VkImageLayout GetPostReleaseVulkanLayout(GLenum glLayout)
{
    switch (glLayout)
    {
        case GL_NONE:
        case GL_LAYOUT_GENERAL_EXT:
        default:
            return VK_IMAGE_LAYOUT_GENERAL;
        case GL_LAYOUT_COLOR_ATTACHMENT_EXT:
            return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
        case GL_LAYOUT_DEPTH_STENCIL_ATTACHMENT_EXT:
            return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
        case GL_LAYOUT_DEPTH_STENCIL_READ_ONLY_EXT:
            return VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
        case GL_LAYOUT_SHADER_READ_ONLY_EXT:
            return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
        case GL_LAYOUT_TRANSFER_SRC_EXT:
            return VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
        case GL_LAYOUT_TRANSFER_DST_EXT:
            return VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
        case GL_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_EXT:
            return VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL_KHR;
        case GL_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_EXT:
            return VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL_KHR;
    }
}

void AdjustCreateFlags(bool useMemoryObjectFlags, VkImageCreateFlags *createFlags)
{
    // If the GL_ANGLE_memory_object_flags extension is not supported, GL assumes that the mutable
    // create flag is specified.
    if (!useMemoryObjectFlags)
    {
        *createFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
    }

    // The spec is not clear about the other create flags.
}

constexpr uint32_t kWidth  = 64;
constexpr uint32_t kHeight = 64;

}  // namespace

class VulkanExternalImageTest : public ANGLETest<>
{
  protected:
    VulkanExternalImageTest()
    {
        setWindowWidth(1);
        setWindowHeight(1);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
    }

    template <typename Traits>
    void runShouldDrawTest(bool isSwiftshader, bool enableDebugLayers);

    template <typename Traits>
    void runWaitSemaphoresRetainsContentTest(bool isSwiftshader, bool enableDebugLayers);
};

class VulkanExternalImageTestES31 : public VulkanExternalImageTest
{};

template <typename Traits>
void RunShouldImportMemoryTest(VkImageCreateFlags createFlags,
                               VkImageUsageFlags usageFlags,
                               bool isSwiftshader,
                               bool enableDebugLayers)
{
    ASSERT(EnsureGLExtensionEnabled(Traits::MemoryObjectExtension()));

    VulkanHelper helper;
    helper.initialize(isSwiftshader, enableDebugLayers);

    VkFormat format = VK_FORMAT_R8G8B8A8_UNORM;
    ANGLE_SKIP_TEST_IF(!Traits::CanCreateImage(helper, format, VK_IMAGE_TYPE_2D,
                                               VK_IMAGE_TILING_OPTIMAL, createFlags, usageFlags));

    VkImage image                 = VK_NULL_HANDLE;
    VkDeviceMemory deviceMemory   = VK_NULL_HANDLE;
    VkDeviceSize deviceMemorySize = 0;

    VkExtent3D extent = {1, 1, 1};
    VkResult result   = Traits::CreateImage2D(&helper, format, createFlags, usageFlags, nullptr,
                                              extent, &image, &deviceMemory, &deviceMemorySize);
    EXPECT_EQ(result, VK_SUCCESS);

    typename Traits::Handle memoryHandle = Traits::InvalidHandle();
    result = Traits::ExportMemory(&helper, deviceMemory, &memoryHandle);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_NE(memoryHandle, Traits::InvalidHandle());

    {
        GLMemoryObject memoryObject;
        GLint dedicatedMemory = GL_TRUE;
        glMemoryObjectParameterivEXT(memoryObject, GL_DEDICATED_MEMORY_OBJECT_EXT,
                                     &dedicatedMemory);
        Traits::ImportMemory(memoryObject, deviceMemorySize, memoryHandle);

        // Test that after calling glImportMemoryFdEXT, the parameters of the memory object cannot
        // be changed
        dedicatedMemory = GL_FALSE;
        glMemoryObjectParameterivEXT(memoryObject, GL_DEDICATED_MEMORY_OBJECT_EXT,
                                     &dedicatedMemory);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    }

    EXPECT_GL_NO_ERROR();

    vkDestroyImage(helper.getDevice(), image, nullptr);
    vkFreeMemory(helper.getDevice(), deviceMemory, nullptr);
}

// glImportMemoryFdEXT must be able to import a valid opaque fd.
TEST_P(VulkanExternalImageTest, ShouldImportMemoryOpaqueFd)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    RunShouldImportMemoryTest<OpaqueFdTraits>(kDefaultImageCreateFlags, kDefaultImageUsageFlags,
                                              isSwiftshader(), enableDebugLayers());
}

// glImportMemoryZirconHandleANGLE must be able to import a valid vmo.
TEST_P(VulkanExternalImageTest, ShouldImportMemoryZirconVmo)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_fuchsia"));
    RunShouldImportMemoryTest<FuchsiaTraits>(kDefaultImageCreateFlags, kDefaultImageUsageFlags,
                                             isSwiftshader(), enableDebugLayers());
}

template <typename Traits>
void RunShouldImportSemaphoreTest(bool isSwiftshader, bool enableDebugLayers)
{
    ASSERT(EnsureGLExtensionEnabled(Traits::SemaphoreExtension()));

    VulkanHelper helper;
    helper.initialize(isSwiftshader, enableDebugLayers);

    ANGLE_SKIP_TEST_IF(!Traits::CanCreateSemaphore(helper));

    VkSemaphore vkSemaphore = VK_NULL_HANDLE;
    VkResult result         = helper.createSemaphoreOpaqueFd(&vkSemaphore);
    EXPECT_EQ(result, VK_SUCCESS);

    typename Traits::Handle semaphoreHandle = Traits::InvalidHandle();
    result = Traits::ExportSemaphore(&helper, vkSemaphore, &semaphoreHandle);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_NE(semaphoreHandle, Traits::InvalidHandle());

    {
        GLSemaphore glSemaphore;
        Traits::ImportSemaphore(glSemaphore, semaphoreHandle);
    }

    EXPECT_GL_NO_ERROR();

    vkDestroySemaphore(helper.getDevice(), vkSemaphore, nullptr);
}

// glImportSemaphoreFdEXT must be able to import a valid opaque fd.
TEST_P(VulkanExternalImageTest, ShouldImportSemaphoreOpaqueFd)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_semaphore_fd"));
    RunShouldImportSemaphoreTest<OpaqueFdTraits>(isSwiftshader(), enableDebugLayers());
}

// glImportSemaphoreZirconHandleANGLE must be able to import a valid handle.
TEST_P(VulkanExternalImageTest, ShouldImportSemaphoreZirconEvent)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_semaphore_fuchsia"));
    RunShouldImportSemaphoreTest<FuchsiaTraits>(isSwiftshader(), enableDebugLayers());
}

template <typename Traits>
void RunShouldClearTest(bool useMemoryObjectFlags,
                        VkImageCreateFlags createFlags,
                        VkImageUsageFlags usageFlags,
                        bool isSwiftshader,
                        bool enableDebugLayers)
{
    ASSERT(EnsureGLExtensionEnabled(Traits::MemoryObjectExtension()));

    VulkanHelper helper;
    helper.initialize(isSwiftshader, enableDebugLayers);

    AdjustCreateFlags(useMemoryObjectFlags, &createFlags);

    VkFormat format = VK_FORMAT_R8G8B8A8_UNORM;
    ANGLE_SKIP_TEST_IF(!Traits::CanCreateImage(helper, format, VK_IMAGE_TYPE_2D,
                                               VK_IMAGE_TILING_OPTIMAL, createFlags, usageFlags));

    VkImage image                 = VK_NULL_HANDLE;
    VkDeviceMemory deviceMemory   = VK_NULL_HANDLE;
    VkDeviceSize deviceMemorySize = 0;

    VkExtent3D extent = {1, 1, 1};
    VkResult result   = Traits::CreateImage2D(&helper, format, createFlags, usageFlags, nullptr,
                                              extent, &image, &deviceMemory, &deviceMemorySize);
    EXPECT_EQ(result, VK_SUCCESS);

    typename Traits::Handle memoryHandle = Traits::InvalidHandle();
    result = Traits::ExportMemory(&helper, deviceMemory, &memoryHandle);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_NE(memoryHandle, Traits::InvalidHandle());

    {
        GLMemoryObject memoryObject;
        GLint dedicatedMemory = GL_TRUE;
        glMemoryObjectParameterivEXT(memoryObject, GL_DEDICATED_MEMORY_OBJECT_EXT,
                                     &dedicatedMemory);
        Traits::ImportMemory(memoryObject, deviceMemorySize, memoryHandle);

        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D, texture);
        if (useMemoryObjectFlags)
        {
            glTexStorageMemFlags2DANGLE(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1, memoryObject, 0,
                                        createFlags, usageFlags, nullptr);
        }
        else
        {
            glTexStorageMem2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1, memoryObject, 0);
        }

        GLFramebuffer framebuffer;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);

        glClearColor(0.5f, 0.5f, 0.5f, 0.5f);
        glClear(GL_COLOR_BUFFER_BIT);

        EXPECT_PIXEL_NEAR(0, 0, 128, 128, 128, 128, 1.0);
    }

    EXPECT_GL_NO_ERROR();

    vkDestroyImage(helper.getDevice(), image, nullptr);
    vkFreeMemory(helper.getDevice(), deviceMemory, nullptr);
}

// Test creating and clearing a simple RGBA8 texture in an opaque fd.
TEST_P(VulkanExternalImageTest, ShouldClearOpaqueFdRGBA8)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    // http://anglebug.com/42263236
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGL() && (IsPixel2() || IsPixel2XL()));
    RunShouldClearTest<OpaqueFdTraits>(false, kDefaultImageCreateFlags, kDefaultImageUsageFlags,
                                       isSwiftshader(), enableDebugLayers());
}

// Test creating and clearing a simple RGBA8 texture in an opaque fd, using
// GL_ANGLE_memory_object_flags.
TEST_P(VulkanExternalImageTest, ShouldClearOpaqueWithFlagsFdRGBA8)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_flags"));
    RunShouldClearTest<OpaqueFdTraits>(true, kDefaultImageCreateFlags, kDefaultImageUsageFlags,
                                       isSwiftshader(), enableDebugLayers());
}

// Test creating and clearing a simple RGBA8 texture without STORAGE usage in an opaque fd.
TEST_P(VulkanExternalImageTest, ShouldClearNoStorageUsageOpaqueFdRGBA8)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_flags"));
    RunShouldClearTest<OpaqueFdTraits>(true, kDefaultImageCreateFlags, kNoStorageImageUsageFlags,
                                       isSwiftshader(), enableDebugLayers());
}

// Test creating and clearing a simple RGBA8 texture without STORAGE usage but with MUTABLE in an
// opaque fd.
TEST_P(VulkanExternalImageTest, ShouldClearMutableNoStorageUsageOpaqueFdRGBA8)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_flags"));
    RunShouldClearTest<OpaqueFdTraits>(true, kMutableImageCreateFlags, kNoStorageImageUsageFlags,
                                       isSwiftshader(), enableDebugLayers());
}

// Test creating and clearing a simple RGBA8 texture in a zircon vmo.
TEST_P(VulkanExternalImageTest, ShouldClearZirconVmoRGBA8)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_fuchsia"));
    RunShouldClearTest<FuchsiaTraits>(false, kDefaultImageCreateFlags, kDefaultImageUsageFlags,
                                      isSwiftshader(), enableDebugLayers());
}

// Test creating and clearing a simple RGBA8 texture in a zircon vmo, using
// GL_ANGLE_memory_object_flags.
TEST_P(VulkanExternalImageTest, ShouldClearZirconWithFlagsVmoRGBA8)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_fuchsia"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_flags"));
    RunShouldClearTest<FuchsiaTraits>(true, kDefaultImageCreateFlags, kDefaultImageUsageFlags,
                                      isSwiftshader(), enableDebugLayers());
}

// Test creating and clearing a simple RGBA8 texture without STORAGE usage in a zircon vmo.
TEST_P(VulkanExternalImageTest, ShouldClearNoStorageUsageZirconVmoRGBA8)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_fuchsia"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_flags"));
    RunShouldClearTest<FuchsiaTraits>(true, kDefaultImageCreateFlags, kNoStorageImageUsageFlags,
                                      isSwiftshader(), enableDebugLayers());
}

// Test creating and clearing a simple RGBA8 texture without STORAGE usage but with MUTABLE in a
// zircon vmo.
TEST_P(VulkanExternalImageTest, ShouldClearMutableNoStorageUsageZirconVmoRGBA8)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_fuchsia"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_flags"));
    RunShouldClearTest<FuchsiaTraits>(true, kMutableImageCreateFlags, kNoStorageImageUsageFlags,
                                      isSwiftshader(), enableDebugLayers());
}

template <typename Traits>
void RunTextureFormatCompatChromiumTest(bool useMemoryObjectFlags,
                                        VkImageCreateFlags createFlags,
                                        VkImageUsageFlags usageFlags,
                                        bool isSwiftshader,
                                        bool enableDebugLayers,
                                        bool isES3)
{
    ASSERT(EnsureGLExtensionEnabled(Traits::MemoryObjectExtension()));

    AdjustCreateFlags(useMemoryObjectFlags, &createFlags);

    VulkanHelper helper;
    helper.initialize(isSwiftshader, enableDebugLayers);
    for (const ImageFormatPair &format : kChromeFormats)
    {
        // https://crbug.com/angleproject/5046
        if ((format.vkFormat == VK_FORMAT_R4G4B4A4_UNORM_PACK16) && IsIntel())
        {
            continue;
        }

        if (!Traits::CanCreateImage(helper, format.vkFormat, VK_IMAGE_TYPE_2D,
                                    VK_IMAGE_TILING_OPTIMAL, createFlags, usageFlags))
        {
            continue;
        }

        if (format.requiredExtension && !IsGLExtensionEnabled(format.requiredExtension))
        {
            continue;
        }

        if (format.internalFormat == GL_RGB10_A2_EXT && !isES3 &&
            !IsGLExtensionEnabled("GL_EXT_texture_type_2_10_10_10_REV"))
        {
            continue;
        }

        VkImage image                 = VK_NULL_HANDLE;
        VkDeviceMemory deviceMemory   = VK_NULL_HANDLE;
        VkDeviceSize deviceMemorySize = 0;

        VkExtent3D extent = {113, 211, 1};
        VkResult result =
            Traits::CreateImage2D(&helper, format.vkFormat, createFlags, usageFlags, nullptr,
                                  extent, &image, &deviceMemory, &deviceMemorySize);
        EXPECT_EQ(result, VK_SUCCESS);

        typename Traits::Handle memoryHandle = Traits::InvalidHandle();
        result = Traits::ExportMemory(&helper, deviceMemory, &memoryHandle);
        EXPECT_EQ(result, VK_SUCCESS);
        EXPECT_NE(memoryHandle, Traits::InvalidHandle());

        {
            GLMemoryObject memoryObject;
            GLint dedicatedMemory = GL_TRUE;
            glMemoryObjectParameterivEXT(memoryObject, GL_DEDICATED_MEMORY_OBJECT_EXT,
                                         &dedicatedMemory);
            Traits::ImportMemory(memoryObject, deviceMemorySize, memoryHandle);

            GLTexture texture;
            glBindTexture(GL_TEXTURE_2D, texture);
            if (useMemoryObjectFlags)
            {
                glTexStorageMemFlags2DANGLE(GL_TEXTURE_2D, 1, format.internalFormat, extent.width,
                                            extent.height, memoryObject, 0, createFlags, usageFlags,
                                            nullptr);
            }
            else
            {
                glTexStorageMem2DEXT(GL_TEXTURE_2D, 1, format.internalFormat, extent.width,
                                     extent.height, memoryObject, 0);
            }
        }

        EXPECT_GL_NO_ERROR();

        vkDestroyImage(helper.getDevice(), image, nullptr);
        vkFreeMemory(helper.getDevice(), deviceMemory, nullptr);
    }
}

// Test all format combinations used by Chrome import successfully (opaque fd).
TEST_P(VulkanExternalImageTest, TextureFormatCompatChromiumFd)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    RunTextureFormatCompatChromiumTest<OpaqueFdTraits>(
        false, kDefaultImageCreateFlags, kDefaultImageUsageFlags, isSwiftshader(),
        enableDebugLayers(), getClientMajorVersion() >= 3);
}

// Test all format combinations used by Chrome import successfully (opaque fd), using
// GL_ANGLE_memory_object_flags.
TEST_P(VulkanExternalImageTest, TextureFormatCompatChromiumWithFlagsFd)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_flags"));
    RunTextureFormatCompatChromiumTest<OpaqueFdTraits>(
        true, kDefaultImageCreateFlags, kDefaultImageUsageFlags, isSwiftshader(),
        enableDebugLayers(), getClientMajorVersion() >= 3);
}

// Test all format combinations used by Chrome import successfully (opaque fd), without STORAGE
// usage.
TEST_P(VulkanExternalImageTest, TextureFormatCompatChromiumNoStorageFd)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_flags"));
    RunTextureFormatCompatChromiumTest<OpaqueFdTraits>(
        true, kDefaultImageCreateFlags, kNoStorageImageUsageFlags, isSwiftshader(),
        enableDebugLayers(), getClientMajorVersion() >= 3);
}

// Test all format combinations used by Chrome import successfully (opaque fd), without STORAGE
// usage but with MUTABLE.
TEST_P(VulkanExternalImageTest, TextureFormatCompatChromiumMutableNoStorageFd)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_flags"));

    // http://anglebug.com/42264218
    ANGLE_SKIP_TEST_IF(IsLinux() && IsAMD() && IsVulkan());

    RunTextureFormatCompatChromiumTest<OpaqueFdTraits>(
        true, kMutableImageCreateFlags, kNoStorageImageUsageFlags, isSwiftshader(),
        enableDebugLayers(), getClientMajorVersion() >= 3);
}

// Test all format combinations used by Chrome import successfully (fuchsia).
TEST_P(VulkanExternalImageTest, TextureFormatCompatChromiumZirconVmo)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_fuchsia"));
    RunTextureFormatCompatChromiumTest<FuchsiaTraits>(
        false, kDefaultImageCreateFlags, kDefaultImageUsageFlags, isSwiftshader(),
        enableDebugLayers(), getClientMajorVersion() >= 3);
}

// Test all format combinations used by Chrome import successfully (fuchsia), using
// GL_ANGLE_memory_object_flags.
TEST_P(VulkanExternalImageTest, TextureFormatCompatChromiumWithFlagsZirconVmo)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_fuchsia"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_flags"));
    RunTextureFormatCompatChromiumTest<FuchsiaTraits>(
        true, kDefaultImageCreateFlags, kDefaultImageUsageFlags, isSwiftshader(),
        enableDebugLayers(), getClientMajorVersion() >= 3);
}

// Test all format combinations used by Chrome import successfully (fuchsia), without STORAGE usage.
TEST_P(VulkanExternalImageTest, TextureFormatCompatChromiumNoStorageZirconVmo)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_fuchsia"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_flags"));
    RunTextureFormatCompatChromiumTest<FuchsiaTraits>(
        true, kDefaultImageCreateFlags, kNoStorageImageUsageFlags, isSwiftshader(),
        enableDebugLayers(), getClientMajorVersion() >= 3);
}

// Test all format combinations used by Chrome import successfully (fuchsia), without STORAGE usage
// but with MUTABLE.
TEST_P(VulkanExternalImageTest, TextureFormatCompatChromiumMutableNoStorageZirconVmo)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_fuchsia"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_flags"));
    RunTextureFormatCompatChromiumTest<FuchsiaTraits>(
        true, kMutableImageCreateFlags, kNoStorageImageUsageFlags, isSwiftshader(),
        enableDebugLayers(), getClientMajorVersion() >= 3);
}

template <typename Traits>
void RunShouldClearWithSemaphoresTest(bool useMemoryObjectFlags,
                                      VkImageCreateFlags createFlags,
                                      VkImageUsageFlags usageFlags,
                                      bool isSwiftshader,
                                      bool enableDebugLayers)
{
    ASSERT(EnsureGLExtensionEnabled(Traits::MemoryObjectExtension()));
    ASSERT(EnsureGLExtensionEnabled(Traits::SemaphoreExtension()));

    AdjustCreateFlags(useMemoryObjectFlags, &createFlags);

    VulkanHelper helper;
    helper.initialize(isSwiftshader, enableDebugLayers);

    VkFormat format = VK_FORMAT_R8G8B8A8_UNORM;
    ANGLE_SKIP_TEST_IF(!Traits::CanCreateImage(helper, format, VK_IMAGE_TYPE_2D,
                                               VK_IMAGE_TILING_OPTIMAL, createFlags, usageFlags));
    ANGLE_SKIP_TEST_IF(!Traits::CanCreateSemaphore(helper));

    VkSemaphore vkAcquireSemaphore = VK_NULL_HANDLE;
    VkResult result                = Traits::CreateSemaphore(&helper, &vkAcquireSemaphore);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_TRUE(vkAcquireSemaphore != VK_NULL_HANDLE);

    VkSemaphore vkReleaseSemaphore = VK_NULL_HANDLE;
    result                         = Traits::CreateSemaphore(&helper, &vkReleaseSemaphore);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_TRUE(vkReleaseSemaphore != VK_NULL_HANDLE);

    typename Traits::Handle acquireSemaphoreHandle = Traits::InvalidHandle();
    result = Traits::ExportSemaphore(&helper, vkAcquireSemaphore, &acquireSemaphoreHandle);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_NE(acquireSemaphoreHandle, Traits::InvalidHandle());

    typename Traits::Handle releaseSemaphoreHandle = Traits::InvalidHandle();
    result = Traits::ExportSemaphore(&helper, vkReleaseSemaphore, &releaseSemaphoreHandle);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_NE(releaseSemaphoreHandle, Traits::InvalidHandle());

    VkImage image                 = VK_NULL_HANDLE;
    VkDeviceMemory deviceMemory   = VK_NULL_HANDLE;
    VkDeviceSize deviceMemorySize = 0;

    VkExtent3D extent = {1, 1, 1};
    result = Traits::CreateImage2D(&helper, format, createFlags, usageFlags, nullptr, extent,
                                   &image, &deviceMemory, &deviceMemorySize);
    EXPECT_EQ(result, VK_SUCCESS);

    typename Traits::Handle memoryHandle = Traits::InvalidHandle();
    result = Traits::ExportMemory(&helper, deviceMemory, &memoryHandle);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_NE(memoryHandle, Traits::InvalidHandle());

    {
        GLMemoryObject memoryObject;
        GLint dedicatedMemory = GL_TRUE;
        glMemoryObjectParameterivEXT(memoryObject, GL_DEDICATED_MEMORY_OBJECT_EXT,
                                     &dedicatedMemory);
        Traits::ImportMemory(memoryObject, deviceMemorySize, memoryHandle);

        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D, texture);
        if (useMemoryObjectFlags)
        {
            glTexStorageMemFlags2DANGLE(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1, memoryObject, 0,
                                        createFlags, usageFlags, nullptr);
        }
        else
        {
            glTexStorageMem2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1, memoryObject, 0);
        }

        GLSemaphore glAcquireSemaphore;
        Traits::ImportSemaphore(glAcquireSemaphore, acquireSemaphoreHandle);

        helper.releaseImageAndSignalSemaphore(image, VK_IMAGE_LAYOUT_UNDEFINED,
                                              VK_IMAGE_LAYOUT_GENERAL, vkAcquireSemaphore);

        const GLuint barrierTextures[] = {
            texture,
        };
        constexpr uint32_t textureBarriersCount = std::extent<decltype(barrierTextures)>();
        const GLenum textureSrcLayouts[]        = {
            GL_LAYOUT_GENERAL_EXT,
        };
        constexpr uint32_t textureSrcLayoutsCount = std::extent<decltype(textureSrcLayouts)>();
        static_assert(textureBarriersCount == textureSrcLayoutsCount,
                      "barrierTextures and textureSrcLayouts must be the same length");
        glWaitSemaphoreEXT(glAcquireSemaphore, 0, nullptr, textureBarriersCount, barrierTextures,
                           textureSrcLayouts);

        GLFramebuffer framebuffer;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);

        glClearColor(0.5f, 0.5f, 0.5f, 0.5f);
        glClear(GL_COLOR_BUFFER_BIT);

        GLSemaphore glReleaseSemaphore;
        Traits::ImportSemaphore(glReleaseSemaphore, releaseSemaphoreHandle);

        const GLenum textureDstLayouts[] = {
            GL_LAYOUT_TRANSFER_SRC_EXT,
        };
        constexpr uint32_t textureDstLayoutsCount = std::extent<decltype(textureSrcLayouts)>();
        static_assert(textureBarriersCount == textureDstLayoutsCount,
                      "barrierTextures and textureDstLayouts must be the same length");
        glSignalSemaphoreEXT(glReleaseSemaphore, 0, nullptr, textureBarriersCount, barrierTextures,
                             textureDstLayouts);

        helper.waitSemaphoreAndAcquireImage(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
                                            VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
                                            vkReleaseSemaphore);
        uint8_t pixels[4];
        VkOffset3D offset = {};
        helper.readPixels(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, format, offset, extent,
                          pixels, sizeof(pixels));

        EXPECT_NEAR(0x80, pixels[0], 1);
        EXPECT_NEAR(0x80, pixels[1], 1);
        EXPECT_NEAR(0x80, pixels[2], 1);
        EXPECT_NEAR(0x80, pixels[3], 1);
    }

    EXPECT_GL_NO_ERROR();

    vkDeviceWaitIdle(helper.getDevice());
    vkDestroyImage(helper.getDevice(), image, nullptr);
    vkDestroySemaphore(helper.getDevice(), vkAcquireSemaphore, nullptr);
    vkDestroySemaphore(helper.getDevice(), vkReleaseSemaphore, nullptr);
    vkFreeMemory(helper.getDevice(), deviceMemory, nullptr);
}

// Test creating and clearing RGBA8 texture in opaque fd with acquire/release.
TEST_P(VulkanExternalImageTest, ShouldClearOpaqueFdWithSemaphores)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_semaphore_fd"));

    // http://issuetracker.google.com/173004081
    ANGLE_SKIP_TEST_IF(IsVulkan() && IsIntel() && IsLinux() &&
                       getEGLWindow()->isFeatureEnabled(Feature::AsyncCommandQueue));
    // http://anglebug.com/42263923
    ANGLE_SKIP_TEST_IF(IsLinux() && IsAMD() && IsDesktopOpenGL());

    RunShouldClearWithSemaphoresTest<OpaqueFdTraits>(false, kDefaultImageCreateFlags,
                                                     kDefaultImageUsageFlags, isSwiftshader(),
                                                     enableDebugLayers());
}

// Test creating and clearing RGBA8 texture in opaque fd with acquire/release, using
// GL_ANGLE_memory_object_flags.
TEST_P(VulkanExternalImageTest, ShouldClearOpaqueFdWithSemaphoresWithFlags)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_semaphore_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_flags"));

    // http://issuetracker.google.com/173004081
    ANGLE_SKIP_TEST_IF(IsVulkan() && IsIntel() && IsLinux() &&
                       getEGLWindow()->isFeatureEnabled(Feature::AsyncCommandQueue));

    RunShouldClearWithSemaphoresTest<OpaqueFdTraits>(true, kDefaultImageCreateFlags,
                                                     kDefaultImageUsageFlags, isSwiftshader(),
                                                     enableDebugLayers());
}

// Test creating and clearing RGBA8 texture without STORAGE usage in opaque fd with acquire/release.
TEST_P(VulkanExternalImageTest, ShouldClearOpaqueFdWithSemaphoresNoStorage)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_semaphore_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_flags"));

    // http://issuetracker.google.com/173004081
    ANGLE_SKIP_TEST_IF(IsVulkan() && IsIntel() && IsLinux() &&
                       getEGLWindow()->isFeatureEnabled(Feature::AsyncCommandQueue));

    RunShouldClearWithSemaphoresTest<OpaqueFdTraits>(true, kDefaultImageCreateFlags,
                                                     kNoStorageImageUsageFlags, isSwiftshader(),
                                                     enableDebugLayers());
}

// Test creating and clearing RGBA8 texture without STORAGE usage but with MUTABLE in opaque fd with
// acquire/release.
TEST_P(VulkanExternalImageTest, ShouldClearOpaqueFdWithSemaphoresMutableNoStorage)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_semaphore_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_flags"));

    // http://issuetracker.google.com/173004081
    ANGLE_SKIP_TEST_IF(IsVulkan() && IsIntel() && IsLinux() &&
                       getEGLWindow()->isFeatureEnabled(Feature::AsyncCommandQueue));

    RunShouldClearWithSemaphoresTest<OpaqueFdTraits>(true, kMutableImageCreateFlags,
                                                     kNoStorageImageUsageFlags, isSwiftshader(),
                                                     enableDebugLayers());
}

// Test creating and clearing RGBA8 texture in zircon vmo with acquire/release.
TEST_P(VulkanExternalImageTest, ShouldClearZirconVmoWithSemaphores)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_fuchsia"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_semaphore_fuchsia"));
    RunShouldClearWithSemaphoresTest<FuchsiaTraits>(false, kDefaultImageCreateFlags,
                                                    kDefaultImageUsageFlags, isSwiftshader(),
                                                    enableDebugLayers());
}

// Test creating and clearing RGBA8 texture in zircon vmo with acquire/release, using
// GL_ANGLE_memory_object_flags.
TEST_P(VulkanExternalImageTest, ShouldClearZirconVmoWithSemaphoresWithFlags)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_fuchsia"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_semaphore_fuchsia"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_flags"));
    RunShouldClearWithSemaphoresTest<FuchsiaTraits>(true, kDefaultImageCreateFlags,
                                                    kDefaultImageUsageFlags, isSwiftshader(),
                                                    enableDebugLayers());
}

// Test creating and clearing RGBA8 texture without STORAGE usage in zircon vmo with
// acquire/release.
TEST_P(VulkanExternalImageTest, ShouldClearZirconVmoWithSemaphoresNoStorage)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_fuchsia"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_semaphore_fuchsia"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_flags"));
    RunShouldClearWithSemaphoresTest<FuchsiaTraits>(true, kDefaultImageCreateFlags,
                                                    kNoStorageImageUsageFlags, isSwiftshader(),
                                                    enableDebugLayers());
}

// Test creating and clearing RGBA8 texture without STORAGE usage but with MUTABLE in zircon vmo
// with acquire/release.
TEST_P(VulkanExternalImageTest, ShouldClearZirconVmoWithSemaphoresMutableNoStorage)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_fuchsia"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_semaphore_fuchsia"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_flags"));
    RunShouldClearWithSemaphoresTest<FuchsiaTraits>(true, kMutableImageCreateFlags,
                                                    kNoStorageImageUsageFlags, isSwiftshader(),
                                                    enableDebugLayers());
}

template <typename Traits>
void VulkanExternalImageTest::runShouldDrawTest(bool isSwiftshader, bool enableDebugLayers)
{
    ASSERT(EnsureGLExtensionEnabled(Traits::MemoryObjectExtension()));
    ASSERT(EnsureGLExtensionEnabled(Traits::SemaphoreExtension()));

    VulkanHelper helper;
    helper.initialize(isSwiftshader, enableDebugLayers);

    VkFormat format = VK_FORMAT_R8G8B8A8_UNORM;
    ANGLE_SKIP_TEST_IF(!Traits::CanCreateImage(helper, format, VK_IMAGE_TYPE_2D,
                                               VK_IMAGE_TILING_OPTIMAL, kDefaultImageCreateFlags,
                                               kDefaultImageUsageFlags));
    ANGLE_SKIP_TEST_IF(!Traits::CanCreateSemaphore(helper));

    VkSemaphore vkAcquireSemaphore = VK_NULL_HANDLE;
    VkResult result                = Traits::CreateSemaphore(&helper, &vkAcquireSemaphore);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_TRUE(vkAcquireSemaphore != VK_NULL_HANDLE);

    VkSemaphore vkReleaseSemaphore = VK_NULL_HANDLE;
    result                         = Traits::CreateSemaphore(&helper, &vkReleaseSemaphore);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_TRUE(vkReleaseSemaphore != VK_NULL_HANDLE);

    typename Traits::Handle acquireSemaphoreHandle = Traits::InvalidHandle();
    result = Traits::ExportSemaphore(&helper, vkAcquireSemaphore, &acquireSemaphoreHandle);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_NE(acquireSemaphoreHandle, Traits::InvalidHandle());

    typename Traits::Handle releaseSemaphoreHandle = Traits::InvalidHandle();
    result = Traits::ExportSemaphore(&helper, vkReleaseSemaphore, &releaseSemaphoreHandle);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_NE(releaseSemaphoreHandle, Traits::InvalidHandle());

    VkImage image                 = VK_NULL_HANDLE;
    VkDeviceMemory deviceMemory   = VK_NULL_HANDLE;
    VkDeviceSize deviceMemorySize = 0;

    VkExtent3D extent = {1, 1, 1};
    result =
        Traits::CreateImage2D(&helper, format, kDefaultImageCreateFlags, kDefaultImageUsageFlags,
                              nullptr, extent, &image, &deviceMemory, &deviceMemorySize);
    EXPECT_EQ(result, VK_SUCCESS);

    typename Traits::Handle memoryHandle = Traits::InvalidHandle();
    result = Traits::ExportMemory(&helper, deviceMemory, &memoryHandle);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_NE(memoryHandle, Traits::InvalidHandle());

    {
        GLMemoryObject memoryObject;
        GLint dedicatedMemory = GL_TRUE;
        glMemoryObjectParameterivEXT(memoryObject, GL_DEDICATED_MEMORY_OBJECT_EXT,
                                     &dedicatedMemory);
        Traits::ImportMemory(memoryObject, deviceMemorySize, memoryHandle);

        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexStorageMem2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1, memoryObject, 0);

        GLSemaphore glAcquireSemaphore;
        Traits::ImportSemaphore(glAcquireSemaphore, acquireSemaphoreHandle);

        // Transfer ownership to GL.
        helper.releaseImageAndSignalSemaphore(image, VK_IMAGE_LAYOUT_UNDEFINED,
                                              VK_IMAGE_LAYOUT_GENERAL, vkAcquireSemaphore);

        const GLuint barrierTextures[] = {
            texture,
        };
        constexpr uint32_t textureBarriersCount = std::extent<decltype(barrierTextures)>();
        const GLenum textureSrcLayouts[]        = {
            GL_LAYOUT_GENERAL_EXT,
        };
        constexpr uint32_t textureSrcLayoutsCount = std::extent<decltype(textureSrcLayouts)>();
        static_assert(textureBarriersCount == textureSrcLayoutsCount,
                      "barrierTextures and textureSrcLayouts must be the same length");
        glWaitSemaphoreEXT(glAcquireSemaphore, 0, nullptr, textureBarriersCount, barrierTextures,
                           textureSrcLayouts);

        GLFramebuffer framebuffer;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
        EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

        // Make the texture red.
        ANGLE_GL_PROGRAM(drawRed, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
        drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0.0f);
        EXPECT_GL_NO_ERROR();

        // Transfer ownership back to test.
        GLSemaphore glReleaseSemaphore;
        Traits::ImportSemaphore(glReleaseSemaphore, releaseSemaphoreHandle);

        const GLenum textureDstLayouts[] = {
            GL_LAYOUT_TRANSFER_SRC_EXT,
        };
        constexpr uint32_t textureDstLayoutsCount = std::extent<decltype(textureSrcLayouts)>();
        static_assert(textureBarriersCount == textureDstLayoutsCount,
                      "barrierTextures and textureDstLayouts must be the same length");
        glSignalSemaphoreEXT(glReleaseSemaphore, 0, nullptr, textureBarriersCount, barrierTextures,
                             textureDstLayouts);

        helper.waitSemaphoreAndAcquireImage(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
                                            VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
                                            vkReleaseSemaphore);

        uint8_t pixels[4];
        VkOffset3D offset = {};
        helper.readPixels(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, format, offset, extent,
                          pixels, sizeof(pixels));

        EXPECT_EQ(0xFF, pixels[0]);
        EXPECT_EQ(0x00, pixels[1]);
        EXPECT_EQ(0x00, pixels[2]);
        EXPECT_EQ(0xFF, pixels[3]);
    }

    EXPECT_GL_NO_ERROR();

    vkDeviceWaitIdle(helper.getDevice());
    vkDestroyImage(helper.getDevice(), image, nullptr);
    vkDestroySemaphore(helper.getDevice(), vkAcquireSemaphore, nullptr);
    vkDestroySemaphore(helper.getDevice(), vkReleaseSemaphore, nullptr);
    vkFreeMemory(helper.getDevice(), deviceMemory, nullptr);
}

// Test drawing to RGBA8 texture in opaque fd with acquire/release.
TEST_P(VulkanExternalImageTest, ShouldDrawOpaqueFdWithSemaphores)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_semaphore_fd"));

    // http://issuetracker.google.com/173004081
    ANGLE_SKIP_TEST_IF(IsVulkan() && IsIntel() && IsLinux() &&
                       getEGLWindow()->isFeatureEnabled(Feature::AsyncCommandQueue));
    // http://anglebug.com/42263923
    ANGLE_SKIP_TEST_IF(IsLinux() && IsAMD() && IsDesktopOpenGL());

    runShouldDrawTest<OpaqueFdTraits>(isSwiftshader(), enableDebugLayers());
}

// Test drawing to RGBA8 texture in zircon vmo with acquire/release multiple times.
TEST_P(VulkanExternalImageTest, ShouldDrawZirconVmoWithSemaphores)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_fuchsia"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_semaphore_fuchsia"));
    runShouldDrawTest<FuchsiaTraits>(isSwiftshader(), enableDebugLayers());
}

template <typename Traits>
void VulkanExternalImageTest::runWaitSemaphoresRetainsContentTest(bool isSwiftshader,
                                                                  bool enableDebugLayers)
{
    ASSERT(EnsureGLExtensionEnabled(Traits::MemoryObjectExtension()));
    ASSERT(EnsureGLExtensionEnabled(Traits::SemaphoreExtension()));

    VulkanHelper helper;
    helper.initialize(isSwiftshader, enableDebugLayers);

    VkFormat format = VK_FORMAT_R8G8B8A8_UNORM;
    ANGLE_SKIP_TEST_IF(!Traits::CanCreateImage(helper, format, VK_IMAGE_TYPE_2D,
                                               VK_IMAGE_TILING_OPTIMAL, kDefaultImageCreateFlags,
                                               kDefaultImageUsageFlags));
    ANGLE_SKIP_TEST_IF(!Traits::CanCreateSemaphore(helper));

    VkSemaphore vkAcquireSemaphore = VK_NULL_HANDLE;
    VkResult result                = Traits::CreateSemaphore(&helper, &vkAcquireSemaphore);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_TRUE(vkAcquireSemaphore != VK_NULL_HANDLE);

    VkSemaphore vkReleaseSemaphore = VK_NULL_HANDLE;
    result                         = Traits::CreateSemaphore(&helper, &vkReleaseSemaphore);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_TRUE(vkReleaseSemaphore != VK_NULL_HANDLE);

    typename Traits::Handle acquireSemaphoreHandle = Traits::InvalidHandle();
    result = Traits::ExportSemaphore(&helper, vkAcquireSemaphore, &acquireSemaphoreHandle);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_NE(acquireSemaphoreHandle, Traits::InvalidHandle());

    typename Traits::Handle releaseSemaphoreHandle = Traits::InvalidHandle();
    result = Traits::ExportSemaphore(&helper, vkReleaseSemaphore, &releaseSemaphoreHandle);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_NE(releaseSemaphoreHandle, Traits::InvalidHandle());

    VkImage image                 = VK_NULL_HANDLE;
    VkDeviceMemory deviceMemory   = VK_NULL_HANDLE;
    VkDeviceSize deviceMemorySize = 0;

    VkExtent3D extent = {kWidth, kHeight, 1};
    result =
        Traits::CreateImage2D(&helper, format, kDefaultImageCreateFlags, kDefaultImageUsageFlags,
                              nullptr, extent, &image, &deviceMemory, &deviceMemorySize);
    EXPECT_EQ(result, VK_SUCCESS);

    typename Traits::Handle memoryHandle = Traits::InvalidHandle();
    result = Traits::ExportMemory(&helper, deviceMemory, &memoryHandle);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_NE(memoryHandle, Traits::InvalidHandle());

    {
        GLMemoryObject memoryObject;
        GLint dedicatedMemory = GL_TRUE;
        glMemoryObjectParameterivEXT(memoryObject, GL_DEDICATED_MEMORY_OBJECT_EXT,
                                     &dedicatedMemory);
        Traits::ImportMemory(memoryObject, deviceMemorySize, memoryHandle);

        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexStorageMem2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, kWidth, kHeight, memoryObject, 0);

        GLSemaphore glAcquireSemaphore;
        Traits::ImportSemaphore(glAcquireSemaphore, acquireSemaphoreHandle);

        // Transfer ownership to GL.
        helper.releaseImageAndSignalSemaphore(image, VK_IMAGE_LAYOUT_UNDEFINED,
                                              VK_IMAGE_LAYOUT_GENERAL, vkAcquireSemaphore);

        const GLuint barrierTextures[] = {
            texture,
        };
        constexpr uint32_t textureBarriersCount = std::extent<decltype(barrierTextures)>();
        const GLenum textureSrcLayouts[]        = {
            GL_LAYOUT_GENERAL_EXT,
        };
        constexpr uint32_t textureSrcLayoutsCount = std::extent<decltype(textureSrcLayouts)>();
        static_assert(textureBarriersCount == textureSrcLayoutsCount,
                      "barrierTextures and textureSrcLayouts must be the same length");
        glWaitSemaphoreEXT(glAcquireSemaphore, 0, nullptr, textureBarriersCount, barrierTextures,
                           textureSrcLayouts);

        GLFramebuffer framebuffer;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
        EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

        // Make the texture red.
        ANGLE_GL_PROGRAM(drawRed, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
        glViewport(0, 0, kWidth, kHeight);
        drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0.0f);
        EXPECT_GL_NO_ERROR();

        // Transfer ownership back to test.
        GLSemaphore glReleaseSemaphore;
        Traits::ImportSemaphore(glReleaseSemaphore, releaseSemaphoreHandle);

        const GLenum textureDstLayouts[] = {
            GL_LAYOUT_TRANSFER_SRC_EXT,
        };
        constexpr uint32_t textureDstLayoutsCount = std::extent<decltype(textureSrcLayouts)>();
        static_assert(textureBarriersCount == textureDstLayoutsCount,
                      "barrierTextures and textureDstLayouts must be the same length");
        glSignalSemaphoreEXT(glReleaseSemaphore, 0, nullptr, textureBarriersCount, barrierTextures,
                             textureDstLayouts);

        helper.waitSemaphoreAndAcquireImage(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
                                            VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
                                            vkReleaseSemaphore);

        // Transfer ownership to GL again, and make sure the contents are preserved.
        helper.releaseImageAndSignalSemaphore(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
                                              VK_IMAGE_LAYOUT_GENERAL, vkAcquireSemaphore);
        glWaitSemaphoreEXT(glAcquireSemaphore, 0, nullptr, textureBarriersCount, barrierTextures,
                           textureSrcLayouts);

        // Blend green
        ANGLE_GL_PROGRAM(drawGreen, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        drawQuad(drawGreen, essl1_shaders::PositionAttrib(), 0.0f);

        // Transfer ownership back to test
        glSignalSemaphoreEXT(glReleaseSemaphore, 0, nullptr, textureBarriersCount, barrierTextures,
                             textureDstLayouts);
        helper.waitSemaphoreAndAcquireImage(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
                                            VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
                                            vkReleaseSemaphore);

        uint8_t pixels[4 * kWidth * kHeight];
        VkOffset3D offset = {};
        helper.readPixels(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, format, offset, extent,
                          pixels, sizeof(pixels));

        EXPECT_EQ(0xFF, pixels[0]);
        EXPECT_EQ(0xFF, pixels[1]);
        EXPECT_EQ(0x00, pixels[2]);
        EXPECT_EQ(0xFF, pixels[3]);

        EXPECT_EQ(0xFF, pixels[4]);
        EXPECT_EQ(0xFF, pixels[5]);
        EXPECT_EQ(0x00, pixels[6]);
        EXPECT_EQ(0xFF, pixels[7]);
    }

    EXPECT_GL_NO_ERROR();

    vkDeviceWaitIdle(helper.getDevice());
    vkDestroyImage(helper.getDevice(), image, nullptr);
    vkDestroySemaphore(helper.getDevice(), vkAcquireSemaphore, nullptr);
    vkDestroySemaphore(helper.getDevice(), vkReleaseSemaphore, nullptr);
    vkFreeMemory(helper.getDevice(), deviceMemory, nullptr);
}

// Test drawing to RGBA8 texture in opaque fd with acquire/release multiple times.
TEST_P(VulkanExternalImageTest, WaitSemaphoresRetainsContentOpaqueFd)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_semaphore_fd"));

    // http://issuetracker.google.com/173004081
    ANGLE_SKIP_TEST_IF(IsVulkan() && IsIntel() && IsLinux() &&
                       getEGLWindow()->isFeatureEnabled(Feature::AsyncCommandQueue));
    // http://anglebug.com/42263923
    ANGLE_SKIP_TEST_IF(IsLinux() && IsAMD() && IsDesktopOpenGL());

    runWaitSemaphoresRetainsContentTest<OpaqueFdTraits>(isSwiftshader(), enableDebugLayers());
}

// Test drawing to RGBA8 texture in zircon vmo with acquire/release multiple times.
TEST_P(VulkanExternalImageTest, WaitSemaphoresRetainsContentZirconVmo)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_fuchsia"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_semaphore_fuchsia"));
    runWaitSemaphoresRetainsContentTest<FuchsiaTraits>(isSwiftshader(), enableDebugLayers());
}

// Support for Zircon handle types is mandatory on Fuchsia.
TEST_P(VulkanExternalImageTest, ShouldSupportExternalHandlesFuchsia)
{
    ANGLE_SKIP_TEST_IF(!IsFuchsia());
    EXPECT_TRUE(EnsureGLExtensionEnabled("GL_ANGLE_memory_object_fuchsia"));
    EXPECT_TRUE(EnsureGLExtensionEnabled("GL_ANGLE_semaphore_fuchsia"));
    VulkanHelper helper;
    helper.initialize(isSwiftshader(), enableDebugLayers());
    EXPECT_TRUE(helper.canCreateSemaphoreZirconEvent());
    EXPECT_TRUE(helper.canCreateImageZirconVmo(VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TYPE_2D,
                                               VK_IMAGE_TILING_OPTIMAL, kDefaultImageCreateFlags,
                                               kDefaultImageUsageFlags));
}

template <typename Traits>
void RunPreInitializedOnGLImportTest(bool useMemoryObjectFlags,
                                     VkImageTiling tiling,
                                     bool isSwiftshader,
                                     bool enableDebugLayers)
{
    ASSERT(EnsureGLExtensionEnabled(Traits::MemoryObjectExtension()));
    ASSERT(EnsureGLExtensionEnabled(Traits::SemaphoreExtension()));

    VkImageCreateFlags createFlags = kDefaultImageCreateFlags;
    VkImageUsageFlags usageFlags   = kDefaultImageUsageFlags;

    AdjustCreateFlags(useMemoryObjectFlags, &createFlags);

    VulkanHelper helper;
    helper.initialize(isSwiftshader, enableDebugLayers);

    VkFormat format = VK_FORMAT_R8G8B8A8_UNORM;
    ANGLE_SKIP_TEST_IF(
        !Traits::CanCreateImage(helper, format, VK_IMAGE_TYPE_2D, tiling, createFlags, usageFlags));
    ANGLE_SKIP_TEST_IF(!Traits::CanCreateSemaphore(helper));

    VkSemaphore vkAcquireSemaphore = VK_NULL_HANDLE;
    VkResult result                = Traits::CreateSemaphore(&helper, &vkAcquireSemaphore);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_TRUE(vkAcquireSemaphore != VK_NULL_HANDLE);

    VkSemaphore vkReleaseSemaphore = VK_NULL_HANDLE;
    result                         = Traits::CreateSemaphore(&helper, &vkReleaseSemaphore);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_TRUE(vkReleaseSemaphore != VK_NULL_HANDLE);

    typename Traits::Handle acquireSemaphoreHandle = Traits::InvalidHandle();
    result = Traits::ExportSemaphore(&helper, vkAcquireSemaphore, &acquireSemaphoreHandle);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_NE(acquireSemaphoreHandle, Traits::InvalidHandle());

    typename Traits::Handle releaseSemaphoreHandle = Traits::InvalidHandle();
    result = Traits::ExportSemaphore(&helper, vkReleaseSemaphore, &releaseSemaphoreHandle);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_NE(releaseSemaphoreHandle, Traits::InvalidHandle());

    VkImage image                 = VK_NULL_HANDLE;
    VkDeviceMemory deviceMemory   = VK_NULL_HANDLE;
    VkDeviceSize deviceMemorySize = 0;

    VkExtent3D extent = {kWidth, kHeight, 1};
    result = Traits::CreateImage2D(&helper, format, createFlags, usageFlags, nullptr, extent,
                                   &image, &deviceMemory, &deviceMemorySize);
    EXPECT_EQ(result, VK_SUCCESS);

    // Initialize a pixel in the image
    constexpr uint32_t kPixel = 0x12345678;
    helper.writePixels(image, VK_IMAGE_LAYOUT_UNDEFINED, VK_FORMAT_R8G8B8A8_UNORM, {0, 0, 0},
                       {1, 1, 1}, static_cast<const void *>(&kPixel), sizeof(kPixel));

    typename Traits::Handle memoryHandle = Traits::InvalidHandle();
    result = Traits::ExportMemory(&helper, deviceMemory, &memoryHandle);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_NE(memoryHandle, Traits::InvalidHandle());

    {
        GLMemoryObject memoryObject;
        GLint dedicatedMemory = GL_TRUE;
        glMemoryObjectParameterivEXT(memoryObject, GL_DEDICATED_MEMORY_OBJECT_EXT,
                                     &dedicatedMemory);
        Traits::ImportMemory(memoryObject, deviceMemorySize, memoryHandle);

        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        if (tiling == VK_IMAGE_TILING_LINEAR)
        {
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_TILING_EXT, GL_LINEAR_TILING_EXT);
        }
        if (useMemoryObjectFlags)
        {
            glTexStorageMemFlags2DANGLE(GL_TEXTURE_2D, 1, GL_RGBA8, kWidth, kHeight, memoryObject,
                                        0, createFlags, usageFlags, nullptr);
        }
        else
        {
            glTexStorageMem2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, kWidth, kHeight, memoryObject, 0);
        }

        GLSemaphore glAcquireSemaphore;
        Traits::ImportSemaphore(glAcquireSemaphore, acquireSemaphoreHandle);

        // Note: writePixels leaves the image in TRANSFER_DST_OPTIMAL layout.
        helper.releaseImageAndSignalSemaphore(image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                                              VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
                                              vkAcquireSemaphore);

        const GLuint barrierTexture   = texture;
        const GLenum textureSrcLayout = GL_LAYOUT_COLOR_ATTACHMENT_EXT;
        glWaitSemaphoreEXT(glAcquireSemaphore, 0, nullptr, 1, &barrierTexture, &textureSrcLayout);

        GLFramebuffer framebuffer;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);

        // Readback the initialized pixel, ensure it contains the value written to it.
        uint32_t pixel = 0u;
        glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel);
        EXPECT_EQ(pixel, kPixel);
        EXPECT_GL_NO_ERROR();

        GLSemaphore glReleaseSemaphore;
        Traits::ImportSemaphore(glReleaseSemaphore, releaseSemaphoreHandle);

        const GLenum textureDstLayout = GL_LAYOUT_TRANSFER_SRC_EXT;
        glSignalSemaphoreEXT(glReleaseSemaphore, 0, nullptr, 1, &barrierTexture, &textureDstLayout);

        helper.waitSemaphoreAndAcquireImage(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
                                            VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
                                            vkReleaseSemaphore);
    }

    EXPECT_GL_NO_ERROR();

    vkDeviceWaitIdle(helper.getDevice());
    vkDestroyImage(helper.getDevice(), image, nullptr);
    vkDestroySemaphore(helper.getDevice(), vkAcquireSemaphore, nullptr);
    vkDestroySemaphore(helper.getDevice(), vkReleaseSemaphore, nullptr);
    vkFreeMemory(helper.getDevice(), deviceMemory, nullptr);
}

// Test that texture storage created from VkImage memory can be considered pre-initialized in GL.
TEST_P(VulkanExternalImageTest, PreInitializedOnGLImport)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_semaphore_fd"));

    RunPreInitializedOnGLImportTest<OpaqueFdTraits>(false, VK_IMAGE_TILING_OPTIMAL, isSwiftshader(),
                                                    enableDebugLayers());
}

// Test that texture storage created from VkImage memory can be considered pre-initialized in GL.
// Uses linear tiling.
TEST_P(VulkanExternalImageTest, PreInitializedOnGLImportLinear)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_semaphore_fd"));

    RunPreInitializedOnGLImportTest<OpaqueFdTraits>(false, VK_IMAGE_TILING_LINEAR, isSwiftshader(),
                                                    enableDebugLayers());
}

// Test that texture storage created from VkImage memory can be considered pre-initialized in GL,
// using GL_ANGLE_memory_object_flags.
TEST_P(VulkanExternalImageTest, PreInitializedOnGLImportWithFlags)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_semaphore_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_flags"));

    RunPreInitializedOnGLImportTest<OpaqueFdTraits>(true, VK_IMAGE_TILING_OPTIMAL, isSwiftshader(),
                                                    enableDebugLayers());
}

// Test that texture storage created from VkImage memory can be considered pre-initialized in GL,
// using GL_ANGLE_memory_object_flags.  Uses linear tiling.
TEST_P(VulkanExternalImageTest, PreInitializedOnGLImportLinearWithFlags)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_semaphore_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_memory_object_flags"));

    RunPreInitializedOnGLImportTest<OpaqueFdTraits>(true, VK_IMAGE_TILING_LINEAR, isSwiftshader(),
                                                    enableDebugLayers());
}

template <typename Traits>
void RunUninitializedOnGLImportTest(bool useMemoryObjectFlags,
                                    std::function<GLenum(GLuint)> useTexture,
                                    const uint32_t *expectInVulkan,
                                    bool isSwiftshader,
                                    bool enableDebugLayers)
{
    ASSERT(EnsureGLExtensionEnabled(Traits::MemoryObjectExtension()));
    ASSERT(EnsureGLExtensionEnabled(Traits::SemaphoreExtension()));

    VkImageCreateFlags createFlags = kDefaultImageCreateFlags;
    VkImageUsageFlags usageFlags   = kDefaultImageUsageFlags;

    AdjustCreateFlags(useMemoryObjectFlags, &createFlags);

    VulkanHelper helper;
    helper.initialize(isSwiftshader, enableDebugLayers);

    VkFormat format = VK_FORMAT_R8G8B8A8_UNORM;
    ANGLE_SKIP_TEST_IF(!Traits::CanCreateImage(helper, format, VK_IMAGE_TYPE_2D,
                                               VK_IMAGE_TILING_OPTIMAL, createFlags, usageFlags));
    ANGLE_SKIP_TEST_IF(!Traits::CanCreateSemaphore(helper));

    VkSemaphore vkAcquireSemaphore = VK_NULL_HANDLE;
    VkResult result                = Traits::CreateSemaphore(&helper, &vkAcquireSemaphore);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_TRUE(vkAcquireSemaphore != VK_NULL_HANDLE);

    VkSemaphore vkReleaseSemaphore = VK_NULL_HANDLE;
    result                         = Traits::CreateSemaphore(&helper, &vkReleaseSemaphore);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_TRUE(vkReleaseSemaphore != VK_NULL_HANDLE);

    typename Traits::Handle acquireSemaphoreHandle = Traits::InvalidHandle();
    result = Traits::ExportSemaphore(&helper, vkAcquireSemaphore, &acquireSemaphoreHandle);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_NE(acquireSemaphoreHandle, Traits::InvalidHandle());

    typename Traits::Handle releaseSemaphoreHandle = Traits::InvalidHandle();
    result = Traits::ExportSemaphore(&helper, vkReleaseSemaphore, &releaseSemaphoreHandle);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_NE(releaseSemaphoreHandle, Traits::InvalidHandle());

    VkImage image                 = VK_NULL_HANDLE;
    VkDeviceMemory deviceMemory   = VK_NULL_HANDLE;
    VkDeviceSize deviceMemorySize = 0;

    VkExtent3D extent = {kWidth, kHeight, 1};
    result = Traits::CreateImage2D(&helper, format, createFlags, usageFlags, nullptr, extent,
                                   &image, &deviceMemory, &deviceMemorySize);
    EXPECT_EQ(result, VK_SUCCESS);

    typename Traits::Handle memoryHandle = Traits::InvalidHandle();
    result = Traits::ExportMemory(&helper, deviceMemory, &memoryHandle);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_NE(memoryHandle, Traits::InvalidHandle());

    {
        GLMemoryObject memoryObject;
        GLint dedicatedMemory = GL_TRUE;
        glMemoryObjectParameterivEXT(memoryObject, GL_DEDICATED_MEMORY_OBJECT_EXT,
                                     &dedicatedMemory);
        Traits::ImportMemory(memoryObject, deviceMemorySize, memoryHandle);

        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        if (useMemoryObjectFlags)
        {
            glTexStorageMemFlags2DANGLE(GL_TEXTURE_2D, 1, GL_RGBA8, kWidth, kHeight, memoryObject,
                                        0, createFlags, usageFlags, nullptr);
        }
        else
        {
            glTexStorageMem2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, kWidth, kHeight, memoryObject, 0);
        }

        GLSemaphore glAcquireSemaphore;
        Traits::ImportSemaphore(glAcquireSemaphore, acquireSemaphoreHandle);

        // Submit the semaphore without touching the image
        helper.signalSemaphore(vkAcquireSemaphore);

        const GLuint barrierTexture   = texture;
        const GLenum textureSrcLayout = GL_NONE;
        glWaitSemaphoreEXT(glAcquireSemaphore, 0, nullptr, 1, &barrierTexture, &textureSrcLayout);

        GLSemaphore glReleaseSemaphore;
        Traits::ImportSemaphore(glReleaseSemaphore, releaseSemaphoreHandle);

        const GLenum textureDstLayout = useTexture(texture);
        glSignalSemaphoreEXT(glReleaseSemaphore, 0, nullptr, 1, &barrierTexture, &textureDstLayout);

        const VkImageLayout imageLayout = GetPostReleaseVulkanLayout(textureDstLayout);
        helper.waitSemaphoreAndAcquireImage(
            image, imageLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, vkReleaseSemaphore);
    }

    EXPECT_GL_NO_ERROR();

    // Verify the contents of the image from the Vulkan side too if needed
    if (expectInVulkan != nullptr)
    {
        uint8_t pixels[4];
        const VkOffset3D offset     = {};
        const VkExtent3D readExtent = {1, 1, 1};
        helper.readPixels(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_FORMAT_R8G8B8A8_UNORM,
                          offset, readExtent, pixels, sizeof(pixels));

        const uint32_t pixel = pixels[0] | pixels[1] << 8 | pixels[2] << 16 | pixels[3] << 24;

        EXPECT_EQ(pixel, *expectInVulkan);
    }

    vkDeviceWaitIdle(helper.getDevice());
    vkDestroyImage(helper.getDevice(), image, nullptr);
    vkDestroySemaphore(helper.getDevice(), vkAcquireSemaphore, nullptr);
    vkDestroySemaphore(helper.getDevice(), vkReleaseSemaphore, nullptr);
    vkFreeMemory(helper.getDevice(), deviceMemory, nullptr);
}

// Test that texture storage created from VkImage memory can be imported as uninitialized in GL.
TEST_P(VulkanExternalImageTest, UninitializedOnGLImport)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_semaphore_fd"));

    constexpr uint32_t kExpect = 0xFF0000FF;

    auto render = [kExpect](GLuint texture) {
        GLFramebuffer framebuffer;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);

        glClearColor(1, 0, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT);

        uint32_t pixel = 0u;
        glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel);
        EXPECT_GL_NO_ERROR();

        EXPECT_EQ(pixel, kExpect);
        EXPECT_GL_NO_ERROR();

        return GL_LAYOUT_TRANSFER_SRC_EXT;
    };

    RunUninitializedOnGLImportTest<OpaqueFdTraits>(false, render, &kExpect, isSwiftshader(),
                                                   enableDebugLayers());
}

// Test that texture storage created from VkImage memory can be imported as uninitialized in GL and
// released without being touched.
TEST_P(VulkanExternalImageTest, UninitializedOnGLImportAndExport)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_semaphore_fd"));

    auto doNothing = [](GLuint) { return GL_NONE; };

    RunUninitializedOnGLImportTest<OpaqueFdTraits>(false, doNothing, nullptr, isSwiftshader(),
                                                   enableDebugLayers());
}

// Test that texture storage created from VkImage memory can be imported as uninitialized in GL and
// then used as the target of a copy.
TEST_P(VulkanExternalImageTest, UninitializedOnGLImportAndCopy)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_semaphore_fd"));

    constexpr uint32_t kExpect = 0xFF00FF00;

    auto copy = [kExpect](GLuint texture) {
        std::vector<GLColor> initData(kWidth * kHeight, GLColor::green);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE,
                        initData.data());

        GLFramebuffer framebuffer;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);

        uint32_t pixel = 0u;
        glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel);
        EXPECT_GL_NO_ERROR();

        EXPECT_EQ(pixel, kExpect);
        EXPECT_GL_NO_ERROR();

        return GL_LAYOUT_TRANSFER_SRC_EXT;
    };

    RunUninitializedOnGLImportTest<OpaqueFdTraits>(false, copy, &kExpect, isSwiftshader(),
                                                   enableDebugLayers());
}

// Test that texture storage created from VkImage memory can be imported as uninitialized in GL and
// then used as sampler.  Because the image is initialized, sampled results would be garbage, so
// this test is primarily ensuring no validation errors are generated.
TEST_P(VulkanExternalImageTest, UninitializedOnGLImportAndSample)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_semaphore_fd"));

    auto sample = [this](GLuint texture) {
        GLProgram program;
        program.makeRaster(essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
        drawQuad(program, std::string(essl1_shaders::PositionAttrib()), 0.0f);
        EXPECT_GL_NO_ERROR();

        return GL_LAYOUT_SHADER_READ_ONLY_EXT;
    };

    RunUninitializedOnGLImportTest<OpaqueFdTraits>(false, sample, nullptr, isSwiftshader(),
                                                   enableDebugLayers());
}

// Test that texture storage created from VkImage memory can be imported as uninitialized in GL and
// then used as storage image.
TEST_P(VulkanExternalImageTestES31, UninitializedOnGLImportAndStorageWrite)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_memory_object_fd"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_semaphore_fd"));

    constexpr uint32_t kExpect = 0xFF00FFFF;

    auto storageWrite = [kExpect](GLuint texture) {
        constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=8, local_size_y=8) in;
layout(rgba8) uniform highp writeonly image2D img;
void main()
{
    imageStore(img, ivec2(gl_GlobalInvocationID.xy), vec4(1, 1, 0, 1));
})";

        GLProgram program;
        program.makeCompute(kCS);
        glUseProgram(program);

        glBindImageTexture(0, texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8);
        glDispatchCompute(kWidth / 8, kHeight / 8, 1);
        EXPECT_GL_NO_ERROR();

        glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);

        GLFramebuffer framebuffer;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);

        uint32_t pixel = 0u;
        glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel);
        EXPECT_GL_NO_ERROR();

        EXPECT_EQ(pixel, kExpect);
        EXPECT_GL_NO_ERROR();

        return GL_LAYOUT_TRANSFER_SRC_EXT;
    };

    RunUninitializedOnGLImportTest<OpaqueFdTraits>(false, storageWrite, &kExpect, isSwiftshader(),
                                                   enableDebugLayers());
}

ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(VulkanExternalImageTest);
ANGLE_INSTANTIATE_TEST_ES31(VulkanExternalImageTestES31);
}  // namespace angle
