//
// Copyright 2021 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.
//

// VulkanImageTest.cpp : Tests of EGL_ANGLE_vulkan_image & GL_ANGLE_vulkan_image extensions.

#include "common/unsafe_buffers.h"
#include "test_utils/ANGLETest.h"

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

namespace angle
{

constexpr GLuint kWidth  = 64u;
constexpr GLuint kHeight = 64u;
constexpr GLuint kWhite  = 0xffffffff;
constexpr GLuint kRed    = 0xff0000ff;

class VulkanImageTest : public ANGLETest<>
{
  protected:
    VulkanImageTest() { setRobustResourceInit(true); }

    GLenum expectedGLLayoutAfterTransfer(const VulkanHelper &helper)
    {
        return helper.useUnifiedImageLayouts() ? GL_LAYOUT_GENERAL_EXT : GL_LAYOUT_TRANSFER_DST_EXT;
    }
    VkImageLayout expectedVKLayoutAfterTransfer(const VulkanHelper &helper)
    {
        return helper.useUnifiedImageLayouts() ? VK_IMAGE_LAYOUT_GENERAL
                                               : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
    }

    GLenum expectedGLLayoutAfterColorWrite(const VulkanHelper &helper)
    {
        return helper.useUnifiedImageLayouts() ? GL_LAYOUT_GENERAL_EXT
                                               : GL_LAYOUT_COLOR_ATTACHMENT_EXT;
    }
    VkImageLayout expectedVKLayoutAfterColorWrite(const VulkanHelper &helper)
    {
        return helper.useUnifiedImageLayouts() ? VK_IMAGE_LAYOUT_GENERAL
                                               : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
    }
};

class VulkanRGB565Test : public VulkanImageTest
{};

class VulkanMemoryTest : public ANGLETest<>
{
  protected:
    VulkanMemoryTest() { setRobustResourceInit(true); }

    bool compatibleMemorySizesForDeviceOOMTest(VkPhysicalDevice physicalDevice,
                                               VkDeviceSize *totalDeviceMemorySizeOut);

    angle::VulkanPerfCounters getPerfCounters()
    {
        if (mIndexMap.empty())
        {
            mIndexMap = BuildCounterNameToIndexMap();
        }

        return GetPerfCounters(mIndexMap);
    }

    CounterNameToIndexMap mIndexMap;
};

bool VulkanMemoryTest::compatibleMemorySizesForDeviceOOMTest(VkPhysicalDevice physicalDevice,
                                                             VkDeviceSize *totalDeviceMemorySizeOut)
{
    // Acquire the sizes and memory property flags for all available memory types. There should be
    // at least one memory heap without the device local bit (VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT).
    // Otherwise, the test should be skipped.
    VkPhysicalDeviceMemoryProperties memoryProperties;
    vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties);

    *totalDeviceMemorySizeOut                 = 0;
    uint32_t heapsWithoutLocalDeviceMemoryBit = 0;
    for (uint32_t i = 0; i < memoryProperties.memoryHeapCount; i++)
    {
        if ((ANGLE_UNSAFE_TODO(memoryProperties.memoryHeaps[i]).flags &
             VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) == 0)
        {
            heapsWithoutLocalDeviceMemoryBit++;
        }
        else
        {
            *totalDeviceMemorySizeOut += ANGLE_UNSAFE_TODO(memoryProperties.memoryHeaps[i]).size;
        }
    }

    bool isCompatible = heapsWithoutLocalDeviceMemoryBit != 0 && *totalDeviceMemorySizeOut != 0;
    return isCompatible;
}

// Check extensions with Vukan backend.
TEST_P(VulkanImageTest, HasVulkanImageExtensions)
{
    ANGLE_SKIP_TEST_IF(!IsVulkan());

    EGLWindow *window  = getEGLWindow();
    EGLDisplay display = window->getDisplay();

    EXPECT_TRUE(IsEGLClientExtensionEnabled("EGL_EXT_device_query"));
    EXPECT_TRUE(IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_vulkan_image"));
    EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_vulkan_image"));

    EGLAttrib result = 0;
    EXPECT_EGL_TRUE(eglQueryDisplayAttribEXT(display, EGL_DEVICE_EXT, &result));

    EGLDeviceEXT device = reinterpret_cast<EGLDeviceEXT>(result);
    EXPECT_NE(EGL_NO_DEVICE_EXT, device);
    EXPECT_TRUE(IsEGLDeviceExtensionEnabled(device, "EGL_ANGLE_device_vulkan"));
}

TEST_P(VulkanImageTest, DeviceVulkan)
{
    ANGLE_SKIP_TEST_IF(!IsVulkan());

    EGLWindow *window  = getEGLWindow();
    EGLDisplay display = window->getDisplay();

    EGLAttrib result = 0;
    EXPECT_EGL_TRUE(eglQueryDisplayAttribEXT(display, EGL_DEVICE_EXT, &result));

    EGLDeviceEXT device = reinterpret_cast<EGLDeviceEXT>(result);
    EXPECT_NE(EGL_NO_DEVICE_EXT, device);

    EXPECT_EGL_TRUE(eglQueryDeviceAttribEXT(device, EGL_VULKAN_INSTANCE_ANGLE, &result));
    VkInstance instance = reinterpret_cast<VkInstance>(result);
    EXPECT_NE(instance, static_cast<VkInstance>(VK_NULL_HANDLE));

    EXPECT_EGL_TRUE(eglQueryDeviceAttribEXT(device, EGL_VULKAN_PHYSICAL_DEVICE_ANGLE, &result));
    VkPhysicalDevice physical_device = reinterpret_cast<VkPhysicalDevice>(result);
    EXPECT_NE(physical_device, static_cast<VkPhysicalDevice>(VK_NULL_HANDLE));

    EXPECT_EGL_TRUE(eglQueryDeviceAttribEXT(device, EGL_VULKAN_DEVICE_ANGLE, &result));
    VkDevice vk_device = reinterpret_cast<VkDevice>(result);
    EXPECT_NE(vk_device, static_cast<VkDevice>(VK_NULL_HANDLE));

    EXPECT_EGL_TRUE(eglQueryDeviceAttribEXT(device, EGL_VULKAN_QUEUE_ANGLE, &result));
    VkQueue queue = reinterpret_cast<VkQueue>(result);
    EXPECT_NE(queue, static_cast<VkQueue>(VK_NULL_HANDLE));

    EXPECT_EGL_TRUE(eglQueryDeviceAttribEXT(device, EGL_VULKAN_QUEUE_FAMILIY_INDEX_ANGLE, &result));

    {
        EXPECT_EGL_TRUE(
            eglQueryDeviceAttribEXT(device, EGL_VULKAN_DEVICE_EXTENSIONS_ANGLE, &result));
        const char *const *extensions = reinterpret_cast<const char *const *>(result);
        EXPECT_NE(extensions, nullptr);
        int extension_count = 0;
        while (ANGLE_UNSAFE_TODO(extensions[extension_count]))
        {
            extension_count++;
        }
        EXPECT_NE(extension_count, 0);
    }

    {
        EXPECT_EGL_TRUE(
            eglQueryDeviceAttribEXT(device, EGL_VULKAN_INSTANCE_EXTENSIONS_ANGLE, &result));
        const char *const *extensions = reinterpret_cast<const char *const *>(result);
        EXPECT_NE(extensions, nullptr);
        int extension_count = 0;
        while (ANGLE_UNSAFE_TODO(extensions[extension_count]))
        {
            extension_count++;
        }
        EXPECT_NE(extension_count, 0);
    }

    EXPECT_EGL_TRUE(eglQueryDeviceAttribEXT(device, EGL_VULKAN_FEATURES_ANGLE, &result));
    const VkPhysicalDeviceFeatures2 *features =
        reinterpret_cast<const VkPhysicalDeviceFeatures2 *>(result);
    EXPECT_NE(features, nullptr);
    EXPECT_EQ(features->sType, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2);

    EXPECT_EGL_TRUE(eglQueryDeviceAttribEXT(device, EGL_VULKAN_GET_INSTANCE_PROC_ADDR, &result));
    PFN_vkGetInstanceProcAddr get_instance_proc_addr =
        reinterpret_cast<PFN_vkGetInstanceProcAddr>(result);
    EXPECT_NE(get_instance_proc_addr, nullptr);
}

TEST_P(VulkanImageTest, ExportVKImage)
{
    EGLWindow *window  = getEGLWindow();
    EGLDisplay display = window->getDisplay();
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_vulkan_image"));

    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glBindTexture(GL_TEXTURE_2D, 0);
    EXPECT_GL_NO_ERROR();

    EGLContext context   = window->getContext();
    EGLImageKHR eglImage = eglCreateImageKHR(
        display, context, EGL_GL_TEXTURE_2D_KHR,
        reinterpret_cast<EGLClientBuffer>(static_cast<uintptr_t>(texture)), nullptr);
    EXPECT_NE(eglImage, EGL_NO_IMAGE_KHR);

    VkImage vkImage        = VK_NULL_HANDLE;
    VkImageCreateInfo info = {};
    EXPECT_EGL_TRUE(eglExportVkImageANGLE(display, eglImage, &vkImage, &info));
    EXPECT_NE(vkImage, static_cast<VkImage>(VK_NULL_HANDLE));
    EXPECT_EQ(info.sType, VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO);
    EXPECT_EQ(info.pNext, nullptr);
    EXPECT_EQ(info.imageType, VK_IMAGE_TYPE_2D);
    EXPECT_EQ(info.format, VK_FORMAT_R8G8B8A8_UNORM);
    EXPECT_EQ(info.extent.width, kWidth);
    EXPECT_EQ(info.extent.height, kHeight);
    EXPECT_EQ(info.extent.depth, 1u);
    EXPECT_EQ(info.queueFamilyIndexCount, 0u);
    EXPECT_EQ(info.pQueueFamilyIndices, nullptr);
    EXPECT_EQ(info.initialLayout, VK_IMAGE_LAYOUT_UNDEFINED);

    EXPECT_EGL_TRUE(eglDestroyImageKHR(display, eglImage));
}

// Check pixels after glTexImage2D
TEST_P(VulkanImageTest, PixelTestTexImage2D)
{
    EGLWindow *window  = getEGLWindow();
    EGLDisplay display = window->getDisplay();

    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_vulkan_image"));

    VulkanHelper helper;
    helper.initializeFromANGLE();

    constexpr GLuint kColor = 0xafbfcfdf;

    GLTexture texture;

    {
        glBindTexture(GL_TEXTURE_2D, texture);
        std::vector<GLuint> pixels(kWidth * kHeight, kColor);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     pixels.data());
        glBindTexture(GL_TEXTURE_2D, 0);
    }

    EGLContext context   = window->getContext();
    EGLImageKHR eglImage = eglCreateImageKHR(
        display, context, EGL_GL_TEXTURE_2D_KHR,
        reinterpret_cast<EGLClientBuffer>(static_cast<uintptr_t>(texture)), nullptr);
    EXPECT_NE(eglImage, EGL_NO_IMAGE_KHR);

    VkImage vkImage        = VK_NULL_HANDLE;
    VkImageCreateInfo info = {};
    EXPECT_EGL_TRUE(eglExportVkImageANGLE(display, eglImage, &vkImage, &info));
    EXPECT_NE(vkImage, static_cast<VkImage>(VK_NULL_HANDLE));

    GLuint textures[1] = {texture};
    GLenum layouts[1]  = {GL_NONE};
    glReleaseTexturesANGLE(1, textures, layouts);
    EXPECT_EQ(layouts[0], expectedGLLayoutAfterTransfer(helper));

    {
        std::vector<GLuint> pixels(kWidth * kHeight);
        helper.readPixels(vkImage, expectedVKLayoutAfterTransfer(helper), info.format, {},
                          info.extent, pixels.data(), pixels.size() * sizeof(GLuint));
        EXPECT_EQ(pixels, std::vector<GLuint>(kWidth * kHeight, kColor));
    }

    layouts[0] = GL_LAYOUT_TRANSFER_SRC_EXT;
    glAcquireTexturesANGLE(1, textures, layouts);

    EXPECT_GL_NO_ERROR();
    EXPECT_EGL_TRUE(eglDestroyImageKHR(display, eglImage));
}

// Check pixels after glClear
TEST_P(VulkanImageTest, PixelTestClear)
{
    EGLWindow *window  = getEGLWindow();
    EGLDisplay display = window->getDisplay();

    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_vulkan_image"));

    VulkanHelper helper;
    helper.initializeFromANGLE();

    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glBindTexture(GL_TEXTURE_2D, 0);

    EGLContext context   = window->getContext();
    EGLImageKHR eglImage = eglCreateImageKHR(
        display, context, EGL_GL_TEXTURE_2D_KHR,
        reinterpret_cast<EGLClientBuffer>(static_cast<uintptr_t>(texture)), nullptr);
    EXPECT_NE(eglImage, EGL_NO_IMAGE_KHR);

    VkImage vkImage        = VK_NULL_HANDLE;
    VkImageCreateInfo info = {};
    EXPECT_EGL_TRUE(eglExportVkImageANGLE(display, eglImage, &vkImage, &info));
    EXPECT_NE(vkImage, static_cast<VkImage>(VK_NULL_HANDLE));

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

    glViewport(0, 0, kWidth, kHeight);
    // clear framebuffer with white color.
    glClearColor(1.f, 1.f, 1.f, 1.f);
    glClear(GL_COLOR_BUFFER_BIT);

    GLuint textures[1] = {texture};
    GLenum layouts[1]  = {GL_NONE};
    glReleaseTexturesANGLE(1, textures, layouts);
    EXPECT_EQ(layouts[0], expectedGLLayoutAfterTransfer(helper));

    std::vector<GLuint> pixels(kWidth * kHeight);
    helper.readPixels(vkImage, expectedVKLayoutAfterTransfer(helper), info.format, {}, info.extent,
                      pixels.data(), pixels.size() * sizeof(GLuint));
    EXPECT_EQ(pixels, std::vector<GLuint>(kWidth * kHeight, kWhite));

    layouts[0] = GL_LAYOUT_TRANSFER_SRC_EXT;
    glAcquireTexturesANGLE(1, textures, layouts);

    // clear framebuffer with red color.
    glClearColor(1.f, 0.f, 0.f, 1.f);
    glClear(GL_COLOR_BUFFER_BIT);

    glReleaseTexturesANGLE(1, textures, layouts);
    EXPECT_EQ(layouts[0], expectedGLLayoutAfterTransfer(helper));

    helper.readPixels(vkImage, expectedVKLayoutAfterTransfer(helper), info.format, {}, info.extent,
                      pixels.data(), pixels.size() * sizeof(GLuint));
    EXPECT_EQ(pixels, std::vector<GLuint>(kWidth * kHeight, kRed));

    layouts[0] = GL_LAYOUT_TRANSFER_SRC_EXT;
    glAcquireTexturesANGLE(1, textures, layouts);

    EXPECT_GL_NO_ERROR();
    EXPECT_EGL_TRUE(eglDestroyImageKHR(display, eglImage));
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

// Check pixels after GL draw.
TEST_P(VulkanImageTest, PixelTestDrawQuad)
{
    EGLWindow *window  = getEGLWindow();
    EGLDisplay display = window->getDisplay();

    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_vulkan_image"));

    VulkanHelper helper;
    helper.initializeFromANGLE();

    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glBindTexture(GL_TEXTURE_2D, 0);

    EGLContext context   = window->getContext();
    EGLImageKHR eglImage = eglCreateImageKHR(
        display, context, EGL_GL_TEXTURE_2D_KHR,
        reinterpret_cast<EGLClientBuffer>(static_cast<uintptr_t>(texture)), nullptr);
    EXPECT_NE(eglImage, EGL_NO_IMAGE_KHR);

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

    glViewport(0, 0, kWidth, kHeight);
    // clear framebuffer with black color.
    glClearColor(0.f, 0.f, 0.f, 0.f);
    glClear(GL_COLOR_BUFFER_BIT);

    // draw red quad
    ANGLE_GL_PROGRAM(drawRed, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0.5f);

    GLuint textures[1] = {texture};
    GLenum layouts[1]  = {GL_NONE};
    glReleaseTexturesANGLE(1, textures, layouts);
    EXPECT_EQ(layouts[0], expectedGLLayoutAfterColorWrite(helper));

    VkImage vkImage        = VK_NULL_HANDLE;
    VkImageCreateInfo info = {};
    EXPECT_EGL_TRUE(eglExportVkImageANGLE(display, eglImage, &vkImage, &info));
    EXPECT_NE(vkImage, static_cast<VkImage>(VK_NULL_HANDLE));

    std::vector<GLuint> pixels(kWidth * kHeight);
    helper.readPixels(vkImage, expectedVKLayoutAfterColorWrite(helper), info.format, {},
                      info.extent, pixels.data(), pixels.size() * sizeof(GLuint));
    EXPECT_EQ(pixels, std::vector<GLuint>(kWidth * kHeight, kRed));

    layouts[0] = GL_LAYOUT_TRANSFER_SRC_EXT;
    glAcquireTexturesANGLE(1, textures, layouts);

    EXPECT_GL_NO_ERROR();
    EXPECT_EGL_TRUE(eglDestroyImageKHR(display, eglImage));
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

// Test importing VkImage with eglCreateImageKHR
TEST_P(VulkanImageTest, ClientBuffer)
{
    EGLWindow *window  = getEGLWindow();
    EGLDisplay display = window->getDisplay();

    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_vulkan_image"));

    VulkanHelper helper;
    helper.initializeFromANGLE();

    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;

    VkImage vkImage                   = VK_NULL_HANDLE;
    VkDeviceMemory vkDeviceMemory     = VK_NULL_HANDLE;
    VkDeviceSize deviceSize           = 0u;
    VkImageCreateInfo imageCreateInfo = {};

    VkResult result = VK_SUCCESS;
    result          = helper.createImage2D(VK_FORMAT_R8G8B8A8_UNORM, 0, kDefaultImageUsageFlags,
                                           {kWidth, kHeight, 1}, &vkImage, &vkDeviceMemory, &deviceSize,
                                           &imageCreateInfo);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_EQ(imageCreateInfo.sType, VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO);

    uint64_t info    = reinterpret_cast<uint64_t>(&imageCreateInfo);
    EGLint attribs[] = {
        EGL_VULKAN_IMAGE_CREATE_INFO_HI_ANGLE,
        static_cast<EGLint>((info >> 32) & 0xffffffff),
        EGL_VULKAN_IMAGE_CREATE_INFO_LO_ANGLE,
        static_cast<EGLint>(info & 0xffffffff),
        EGL_NONE,
    };
    EGLImageKHR eglImage = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_VULKAN_IMAGE_ANGLE,
                                             reinterpret_cast<EGLClientBuffer>(&vkImage), attribs);
    EXPECT_NE(eglImage, EGL_NO_IMAGE_KHR);

    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage);

    GLuint textures[1] = {texture};
    GLenum layouts[1]  = {GL_NONE};
    glAcquireTexturesANGLE(1, textures, layouts);

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

    glViewport(0, 0, kWidth, kHeight);
    // clear framebuffer with white color.
    glClearColor(1.f, 1.f, 1.f, 1.f);
    glClear(GL_COLOR_BUFFER_BIT);

    textures[0] = texture;
    layouts[0]  = GL_NONE;
    glReleaseTexturesANGLE(1, textures, layouts);
    EXPECT_EQ(layouts[0], expectedGLLayoutAfterTransfer(helper));

    std::vector<GLuint> pixels(kWidth * kHeight);
    helper.readPixels(vkImage, expectedVKLayoutAfterTransfer(helper), imageCreateInfo.format, {},
                      imageCreateInfo.extent, pixels.data(), pixels.size() * sizeof(GLuint));
    EXPECT_EQ(pixels, std::vector<GLuint>(kWidth * kHeight, kWhite));

    layouts[0] = GL_LAYOUT_TRANSFER_SRC_EXT;
    glAcquireTexturesANGLE(1, textures, layouts);

    // clear framebuffer with red color.
    glClearColor(1.f, 0.f, 0.f, 1.f);
    glClear(GL_COLOR_BUFFER_BIT);

    glReleaseTexturesANGLE(1, textures, layouts);
    EXPECT_EQ(layouts[0], expectedGLLayoutAfterTransfer(helper));

    helper.readPixels(vkImage, expectedVKLayoutAfterTransfer(helper), imageCreateInfo.format, {},
                      imageCreateInfo.extent, pixels.data(), pixels.size() * sizeof(GLuint));
    EXPECT_EQ(pixels, std::vector<GLuint>(kWidth * kHeight, kRed));

    EXPECT_GL_NO_ERROR();
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    framebuffer.reset();
    texture.reset();

    glFinish();

    EXPECT_EGL_TRUE(eglDestroyImageKHR(display, eglImage));
    vkDestroyImage(helper.getDevice(), vkImage, nullptr);
    vkFreeMemory(helper.getDevice(), vkDeviceMemory, nullptr);
}

// Test importing VkImage with eglCreateImageKHR and drawing to make sure no errors occur in setting
// up the framebuffer, including an imageless framebuffer.
TEST_P(VulkanImageTest, ClientBufferWithDraw)
{
    EGLWindow *window  = getEGLWindow();
    EGLDisplay display = window->getDisplay();

    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_vulkan_image"));

    VulkanHelper helper;
    helper.initializeFromANGLE();

    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;

    VkImage vkImage                   = VK_NULL_HANDLE;
    VkDeviceMemory vkDeviceMemory     = VK_NULL_HANDLE;
    VkDeviceSize deviceSize           = 0u;
    VkImageCreateInfo imageCreateInfo = {};

    VkResult result = VK_SUCCESS;
    result          = helper.createImage2D(VK_FORMAT_R8G8B8A8_UNORM, 0, kDefaultImageUsageFlags,
                                           {kWidth, kHeight, 1}, &vkImage, &vkDeviceMemory, &deviceSize,
                                           &imageCreateInfo);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_EQ(imageCreateInfo.sType, VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO);

    uint64_t info    = reinterpret_cast<uint64_t>(&imageCreateInfo);
    EGLint attribs[] = {
        EGL_VULKAN_IMAGE_CREATE_INFO_HI_ANGLE,
        static_cast<EGLint>((info >> 32) & 0xffffffff),
        EGL_VULKAN_IMAGE_CREATE_INFO_LO_ANGLE,
        static_cast<EGLint>(info & 0xffffffff),
        EGL_NONE,
    };
    EGLImageKHR eglImage = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_VULKAN_IMAGE_ANGLE,
                                             reinterpret_cast<EGLClientBuffer>(&vkImage), attribs);
    EXPECT_NE(eglImage, EGL_NO_IMAGE_KHR);

    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage);

    GLuint textures[1] = {texture};
    GLenum layouts[1]  = {GL_NONE};
    glAcquireTexturesANGLE(1, textures, layouts);

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

    ANGLE_GL_PROGRAM(drawGreen, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    drawQuad(drawGreen, essl1_shaders::PositionAttrib(), 0.5f);

    EXPECT_GL_NO_ERROR();
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    framebuffer.reset();
    texture.reset();

    glFinish();

    EXPECT_EGL_TRUE(eglDestroyImageKHR(display, eglImage));
    vkDestroyImage(helper.getDevice(), vkImage, nullptr);
    vkFreeMemory(helper.getDevice(), vkDeviceMemory, nullptr);
}

// Test importing VkImage with eglCreateImageKHR with RGB565, drawing and updating the texture.
TEST_P(VulkanRGB565Test, ClientBufferWithDraw)
{
    EGLWindow *window  = getEGLWindow();
    EGLDisplay display = window->getDisplay();

    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_vulkan_image"));

    VulkanHelper helper;
    helper.initializeFromANGLE();

    constexpr VkImageUsageFlags kDefaultImageUsageFlags =
        VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
        VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
        VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;

    VkImage vkImage                   = VK_NULL_HANDLE;
    VkDeviceMemory vkDeviceMemory     = VK_NULL_HANDLE;
    VkDeviceSize deviceSize           = 0u;
    VkImageCreateInfo imageCreateInfo = {};

    VkResult result = VK_SUCCESS;
    result = helper.createImage2D(VK_FORMAT_R5G6B5_UNORM_PACK16, 0, kDefaultImageUsageFlags,
                                  {kWidth, kHeight, 1}, &vkImage, &vkDeviceMemory, &deviceSize,
                                  &imageCreateInfo);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_EQ(imageCreateInfo.sType, VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO);

    uint64_t info    = reinterpret_cast<uint64_t>(&imageCreateInfo);
    EGLint attribs[] = {
        EGL_VULKAN_IMAGE_CREATE_INFO_HI_ANGLE,
        static_cast<EGLint>((info >> 32) & 0xffffffff),
        EGL_VULKAN_IMAGE_CREATE_INFO_LO_ANGLE,
        static_cast<EGLint>(info & 0xffffffff),
        EGL_NONE,
    };
    EGLImageKHR eglImage = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_VULKAN_IMAGE_ANGLE,
                                             reinterpret_cast<EGLClientBuffer>(&vkImage), attribs);
    EXPECT_NE(eglImage, EGL_NO_IMAGE_KHR);

    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage);

    GLuint textures[1] = {texture};
    GLenum layouts[1]  = {GL_NONE};
    glAcquireTexturesANGLE(1, textures, layouts);

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

    glViewport(0, 0, kWidth, kHeight);
    // Clear framebuffer with white.
    glClearColor(1.f, 1.f, 1.f, 1.f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw red to the texture.
    ANGLE_GL_PROGRAM(drawRed, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::red);

    // Copy cyan to one half of the image.
    std::vector<uint16_t> cyanBlock(kWidth * kHeight, 0x7FF);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight / 2, GL_RGB, GL_UNSIGNED_SHORT_5_6_5,
                    cyanBlock.data());
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight / 2, GLColor::cyan);
    EXPECT_PIXEL_RECT_EQ(0, kHeight / 2, kWidth, kHeight / 2, GLColor::red);

    // Copy yellow to one half of the image.
    std::vector<GLColorRGB> yellowBlock(kWidth * kHeight, GLColorRGB::yellow);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth / 2, kHeight, GL_RGB, GL_UNSIGNED_BYTE,
                    yellowBlock.data());
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth / 2, kHeight, GLColor::yellow);
    EXPECT_PIXEL_RECT_EQ(kWidth / 2, 0, kWidth / 2, kHeight / 2, GLColor::cyan);
    EXPECT_PIXEL_RECT_EQ(kWidth / 2, kHeight / 2, kWidth / 2, kHeight / 2, GLColor::red);

    EXPECT_GL_NO_ERROR();
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    framebuffer.reset();
    texture.reset();

    glFinish();

    EXPECT_EGL_TRUE(eglDestroyImageKHR(display, eglImage));
    vkDestroyImage(helper.getDevice(), vkImage, nullptr);
    vkFreeMemory(helper.getDevice(), vkDeviceMemory, nullptr);
}

// Test that when VMA image suballocation is used, image memory can be allocated from the system in
// case the device memory runs out.
TEST_P(VulkanMemoryTest, AllocateVMAImageWhenDeviceOOM)
{
    ANGLE_SKIP_TEST_IF(!getEGLWindow()->isFeatureEnabled(Feature::UseVmaForImageSuballocation));

    GLPerfMonitor monitor;
    glBeginPerfMonitorAMD(monitor);

    VulkanHelper helper;
    helper.initializeFromANGLE();
    uint64_t expectedAllocationFallbacks =
        getPerfCounters().deviceMemoryImageAllocationFallbacks + 1;
    uint64_t expectedAllocationFallbacksAfterLastTexture =
        getPerfCounters().deviceMemoryImageAllocationFallbacks + 2;

    VkDeviceSize totalDeviceLocalMemoryHeapSize = 0;
    ANGLE_SKIP_TEST_IF(!compatibleMemorySizesForDeviceOOMTest(helper.getPhysicalDevice(),
                                                              &totalDeviceLocalMemoryHeapSize));

    // Device memory is the first choice for image memory allocation. However, in case it runs out,
    // memory should be allocated from the system if available. Therefore, we want to make sure that
    // we can still allocate image memory even if the device memory is full.
    constexpr VkDeviceSize kTextureWidth  = 2048;
    constexpr VkDeviceSize kTextureHeight = 2048;
    constexpr VkDeviceSize kTextureSize   = kTextureWidth * kTextureHeight * 4;
    VkDeviceSize textureCount             = (totalDeviceLocalMemoryHeapSize / kTextureSize) + 1;

    std::vector<GLTexture> textures;
    textures.resize(textureCount);
    for (uint32_t i = 0; i < textureCount; i++)
    {
        glBindTexture(GL_TEXTURE_2D, textures[i]);
        glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, kTextureWidth, kTextureHeight);
        glDrawArrays(GL_POINTS, 0, 1);
        EXPECT_GL_NO_ERROR();

        // This process only needs to continue until the allocation is no longer on the device.
        if (getPerfCounters().deviceMemoryImageAllocationFallbacks >= expectedAllocationFallbacks)
        {
            break;
        }
    }
    EXPECT_EQ(getPerfCounters().deviceMemoryImageAllocationFallbacks, expectedAllocationFallbacks);

    // Verify that the texture allocated on the system memory can attach to a framebuffer correctly.
    GLTexture texture;
    std::vector<GLColor> textureColor(kTextureWidth * kTextureHeight, GLColor::magenta);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, kTextureWidth, kTextureHeight);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTextureWidth, kTextureHeight, GL_RGBA,
                    GL_UNSIGNED_BYTE, textureColor.data());
    EXPECT_EQ(getPerfCounters().deviceMemoryImageAllocationFallbacks,
              expectedAllocationFallbacksAfterLastTexture);

    glEndPerfMonitorAMD(monitor);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::magenta);
}

// Test that when VMA image suballocation is used, it is possible to free space for a new image on
// the device by freeing garbage memory from a 2D texture array.
TEST_P(VulkanMemoryTest, AllocateVMAImageAfterFreeing2DArrayGarbageWhenDeviceOOM)
{
    ANGLE_SKIP_TEST_IF(!getEGLWindow()->isFeatureEnabled(Feature::UseVmaForImageSuballocation));

    GLPerfMonitor monitor;
    glBeginPerfMonitorAMD(monitor);

    VulkanHelper helper;
    helper.initializeFromANGLE();
    uint64_t expectedAllocationFallbacks =
        getPerfCounters().deviceMemoryImageAllocationFallbacks + 1;

    VkPhysicalDeviceMemoryProperties memoryProperties;
    vkGetPhysicalDeviceMemoryProperties(helper.getPhysicalDevice(), &memoryProperties);

    VkDeviceSize totalDeviceLocalMemoryHeapSize = 0;
    ANGLE_SKIP_TEST_IF(!compatibleMemorySizesForDeviceOOMTest(helper.getPhysicalDevice(),
                                                              &totalDeviceLocalMemoryHeapSize));

    // Use a 2D texture array to allocate some of the available device memory and draw with it.
    GLuint texture2DArray;
    constexpr VkDeviceSize kTextureWidth  = 512;
    constexpr VkDeviceSize kTextureHeight = 512;
    VkDeviceSize texture2DArrayLayerCount = 10;
    glGenTextures(1, &texture2DArray);

    glBindTexture(GL_TEXTURE_2D_ARRAY, texture2DArray);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, kTextureWidth, kTextureHeight,
                 static_cast<GLsizei>(texture2DArrayLayerCount), 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 nullptr);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    for (size_t i = 0; i < texture2DArrayLayerCount; i++)
    {
        std::vector<GLColor> textureColor(kTextureWidth * kTextureHeight, GLColor::green);
        glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, static_cast<GLint>(i), kTextureWidth,
                        kTextureHeight, 1, GL_RGBA, GL_UNSIGNED_BYTE, textureColor.data());
    }

    ANGLE_GL_PROGRAM(drawTex2DArray, essl3_shaders::vs::Texture2DArray(),
                     essl3_shaders::fs::Texture2DArray());
    drawQuad(drawTex2DArray, essl1_shaders::PositionAttrib(), 0.5f);

    // Fill up the device memory until we start allocating on the system memory.
    // Device memory is the first choice for image memory allocation. However, in case it runs out,
    // memory should be allocated from the system if available.
    std::vector<GLTexture> textures2D;
    constexpr VkDeviceSize kTextureSize = kTextureWidth * kTextureHeight * 4;
    VkDeviceSize texture2DCount         = (totalDeviceLocalMemoryHeapSize / kTextureSize) + 1;
    textures2D.resize(texture2DCount);

    for (uint32_t i = 0; i < texture2DCount; i++)
    {
        glBindTexture(GL_TEXTURE_2D, textures2D[i]);
        glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, kTextureWidth, kTextureHeight);
        EXPECT_GL_NO_ERROR();

        // This process only needs to continue until the allocation is no longer on the device.
        if (getPerfCounters().deviceMemoryImageAllocationFallbacks >= expectedAllocationFallbacks)
        {
            break;
        }
    }
    EXPECT_EQ(getPerfCounters().deviceMemoryImageAllocationFallbacks, expectedAllocationFallbacks);

    // Wait until GPU finishes execution.
    GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
    glWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
    EXPECT_GL_NO_ERROR();

    // Delete the 2D array texture. This frees the memory due to context flushing from the memory
    // allocation fallbacks.
    glDeleteTextures(1, &texture2DArray);

    // The next texture should be allocated on the device, which will only be possible after freeing
    // the garbage.
    GLTexture lastTexture;
    std::vector<GLColor> lastTextureColor(kTextureWidth * kTextureHeight, GLColor::blue);
    glBindTexture(GL_TEXTURE_2D, lastTexture);
    glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, kTextureWidth, kTextureHeight);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTextureWidth, kTextureHeight, GL_RGBA,
                    GL_UNSIGNED_BYTE, lastTextureColor.data());
    EXPECT_EQ(getPerfCounters().deviceMemoryImageAllocationFallbacks, expectedAllocationFallbacks);

    glEndPerfMonitorAMD(monitor);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, lastTexture, 0);
    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::blue);
}

// Test that when VMA image suballocation is used, it is possible to free space for a new image on
// the device by freeing finished garbage memory from a 2D texture.
TEST_P(VulkanMemoryTest, AllocateVMAImageAfterFreeingFinished2DGarbageWhenDeviceOOM)
{
    ANGLE_SKIP_TEST_IF(!getEGLWindow()->isFeatureEnabled(Feature::UseVmaForImageSuballocation));

    GLPerfMonitor monitor;
    glBeginPerfMonitorAMD(monitor);

    VulkanHelper helper;
    helper.initializeFromANGLE();
    uint64_t expectedAllocationFallbacks =
        getPerfCounters().deviceMemoryImageAllocationFallbacks + 1;

    VkDeviceSize totalDeviceLocalMemoryHeapSize = 0;
    ANGLE_SKIP_TEST_IF(!compatibleMemorySizesForDeviceOOMTest(helper.getPhysicalDevice(),
                                                              &totalDeviceLocalMemoryHeapSize));

    // Use a large 2D texture to allocate some of the available device memory and draw with it.
    GLuint largeTexture;
    constexpr VkDeviceSize kLargeTextureWidth  = 2048;
    constexpr VkDeviceSize kLargeTextureHeight = 2048;
    std::vector<GLColor> firstTextureColor(kLargeTextureWidth * kLargeTextureHeight,
                                           GLColor::green);
    glGenTextures(1, &largeTexture);
    glBindTexture(GL_TEXTURE_2D, largeTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kLargeTextureWidth, kLargeTextureHeight, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kLargeTextureWidth, kLargeTextureHeight, GL_RGBA,
                    GL_UNSIGNED_BYTE, firstTextureColor.data());

    ANGLE_GL_PROGRAM(drawTex2D, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
    drawQuad(drawTex2D, essl1_shaders::PositionAttrib(), 0.5f);

    // Fill up the device memory until we start allocating on the system memory.
    // Device memory is the first choice for image memory allocation. However, in case it runs out,
    // memory should be allocated from the system if available.
    std::vector<GLTexture> textures2D;
    constexpr VkDeviceSize kTextureWidth  = 512;
    constexpr VkDeviceSize kTextureHeight = 512;
    constexpr VkDeviceSize kTextureSize   = kTextureWidth * kTextureHeight * 4;
    VkDeviceSize texture2DCount           = (totalDeviceLocalMemoryHeapSize / kTextureSize) + 1;
    textures2D.resize(texture2DCount);

    for (uint32_t i = 0; i < texture2DCount; i++)
    {
        glBindTexture(GL_TEXTURE_2D, textures2D[i]);
        glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, kTextureWidth, kTextureHeight);
        EXPECT_GL_NO_ERROR();

        // This process only needs to continue until the allocation is no longer on the device.
        if (getPerfCounters().deviceMemoryImageAllocationFallbacks >= expectedAllocationFallbacks)
        {
            break;
        }
    }
    EXPECT_EQ(getPerfCounters().deviceMemoryImageAllocationFallbacks, expectedAllocationFallbacks);

    // Wait until GPU finishes execution.
    GLsync syncOne = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
    glWaitSync(syncOne, 0, GL_TIMEOUT_IGNORED);
    EXPECT_GL_NO_ERROR();

    // Delete the large 2D texture. It should free the memory due to context flushing performed
    // during memory allocation fallbacks. Then we allocate and draw with this texture again.
    glDeleteTextures(1, &largeTexture);

    glBindTexture(GL_TEXTURE_2D, largeTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kLargeTextureWidth, kLargeTextureHeight, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kLargeTextureWidth, kLargeTextureHeight, GL_RGBA,
                    GL_UNSIGNED_BYTE, firstTextureColor.data());

    drawQuad(drawTex2D, essl1_shaders::PositionAttrib(), 0.5f);

    // Wait until GPU finishes execution one more time.
    GLsync syncTwo = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
    glWaitSync(syncTwo, 0, GL_TIMEOUT_IGNORED);
    EXPECT_GL_NO_ERROR();

    // Delete the large 2D texture. Even though it is marked as deallocated, the device memory is
    // not freed from the garbage yet.
    glDeleteTextures(1, &largeTexture);

    // The next texture should be allocated on the device, which will only be possible after freeing
    // the garbage from the finished commands. There should be no context flushing.
    uint64_t expectedSubmitCalls = getPerfCounters().commandQueueSubmitCallsTotal;
    GLTexture lastTexture;
    std::vector<GLColor> textureColor(kLargeTextureWidth * kLargeTextureWidth, GLColor::red);
    glBindTexture(GL_TEXTURE_2D, lastTexture);
    glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, kLargeTextureWidth, kLargeTextureWidth);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kLargeTextureWidth, kLargeTextureWidth, GL_RGBA,
                    GL_UNSIGNED_BYTE, textureColor.data());
    EXPECT_EQ(getPerfCounters().deviceMemoryImageAllocationFallbacks, expectedAllocationFallbacks);
    EXPECT_EQ(getPerfCounters().commandQueueSubmitCallsTotal, expectedSubmitCalls);

    glEndPerfMonitorAMD(monitor);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, lastTexture, 0);
    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::red);
}

// Test that when VMA image suballocation is used, it is possible to free space for a new buffer on
// the device by freeing garbage memory from a 2D texture.
TEST_P(VulkanMemoryTest, AllocateBufferAfterFreeing2DGarbageWhenDeviceOOM)
{
    ANGLE_SKIP_TEST_IF(!getEGLWindow()->isFeatureEnabled(Feature::UseVmaForImageSuballocation));

    GLPerfMonitor monitor;
    glBeginPerfMonitorAMD(monitor);

    VulkanHelper helper;
    helper.initializeFromANGLE();
    uint64_t expectedAllocationFallbacks =
        getPerfCounters().deviceMemoryImageAllocationFallbacks + 1;

    VkDeviceSize totalDeviceLocalMemoryHeapSize = 0;
    ANGLE_SKIP_TEST_IF(!compatibleMemorySizesForDeviceOOMTest(helper.getPhysicalDevice(),
                                                              &totalDeviceLocalMemoryHeapSize));

    // Use a large 2D texture to allocate some of the available device memory and draw with it.
    GLuint firstTexture;
    constexpr VkDeviceSize kFirstTextureWidth  = 2048;
    constexpr VkDeviceSize kFirstTextureHeight = 2048;
    glGenTextures(1, &firstTexture);

    glBindTexture(GL_TEXTURE_2D, firstTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kFirstTextureWidth, kFirstTextureHeight, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    {
        std::vector<GLColor> firstTextureColor(kFirstTextureWidth * kFirstTextureHeight,
                                               GLColor::green);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kFirstTextureWidth, kFirstTextureHeight, GL_RGBA,
                        GL_UNSIGNED_BYTE, firstTextureColor.data());
    }

    ANGLE_GL_PROGRAM(drawTex2D, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
    drawQuad(drawTex2D, essl1_shaders::PositionAttrib(), 0.5f);

    // Fill up the device memory until we start allocating on the system memory.
    // Device memory is the first choice for image memory allocation. However, in case it runs out,
    // memory should be allocated from the system if available.
    std::vector<GLTexture> textures2D;
    constexpr VkDeviceSize kTextureWidth  = 512;
    constexpr VkDeviceSize kTextureHeight = 512;
    constexpr VkDeviceSize kTextureSize   = kTextureWidth * kTextureHeight * 4;
    VkDeviceSize texture2DCount           = (totalDeviceLocalMemoryHeapSize / kTextureSize) + 1;
    textures2D.resize(texture2DCount);

    for (uint32_t i = 0; i < texture2DCount; i++)
    {
        glBindTexture(GL_TEXTURE_2D, textures2D[i]);
        glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, kTextureWidth, kTextureHeight);
        EXPECT_GL_NO_ERROR();

        // This process only needs to continue until the allocation is no longer on the device.
        if (getPerfCounters().deviceMemoryImageAllocationFallbacks >= expectedAllocationFallbacks)
        {
            break;
        }
    }
    EXPECT_EQ(getPerfCounters().deviceMemoryImageAllocationFallbacks, expectedAllocationFallbacks);

    glEndPerfMonitorAMD(monitor);

    // Wait until GPU finishes execution.
    GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
    glWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
    EXPECT_GL_NO_ERROR();

    // Delete the 2D array texture. This frees the memory due to context flushing from the memory
    // allocation fallbacks.
    glDeleteTextures(1, &firstTexture);

    // The buffer should be allocated on the device, which will only be possible after freeing the
    // garbage.
    GLBuffer lastBuffer;
    constexpr VkDeviceSize kBufferSize = kTextureWidth * kTextureHeight * 4;
    std::vector<uint8_t> bufferData(kBufferSize, 255);
    glBindBuffer(GL_ARRAY_BUFFER, lastBuffer);
    glBufferData(GL_ARRAY_BUFFER, kBufferSize, bufferData.data(), GL_STATIC_DRAW);
    EXPECT_GL_NO_ERROR();
}

// Test importing VkImage with VK_FORMAT_R5G6B5_UNORM_PACK16 when ForceFallbackFormat is enabled.
// This forces ANGLE to use a fallback format (RGBA8) for internal GLES RGB565 textures,
// but the imported sibling must still use the actual RGB565 format.
TEST_P(VulkanRGB565Test, ImportRGB565)
{
    EGLWindow *window  = getEGLWindow();
    EGLDisplay display = window->getDisplay();

    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_vulkan_image"));

    VulkanHelper helper;
    helper.initializeFromANGLE();

    // Check if VK_FORMAT_R5G6B5_UNORM_PACK16 is supported by the physical device.
    VkFormatProperties properties;
    vkGetPhysicalDeviceFormatProperties(helper.getPhysicalDevice(), VK_FORMAT_R5G6B5_UNORM_PACK16,
                                        &properties);
    ANGLE_SKIP_TEST_IF(!(properties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT));

    constexpr VkImageUsageFlags kDefaultImageUsageFlags =
        VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
        VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;

    VkImage vkImage                   = VK_NULL_HANDLE;
    VkDeviceMemory vkDeviceMemory     = VK_NULL_HANDLE;
    VkDeviceSize deviceSize           = 0u;
    VkImageCreateInfo imageCreateInfo = {};

    VkResult result = VK_SUCCESS;
    result = helper.createImage2D(VK_FORMAT_R5G6B5_UNORM_PACK16, 0, kDefaultImageUsageFlags,
                                  {kWidth, kHeight, 1}, &vkImage, &vkDeviceMemory, &deviceSize,
                                  &imageCreateInfo);
    EXPECT_EQ(result, VK_SUCCESS);
    EXPECT_EQ(imageCreateInfo.sType, VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO);

    uint64_t info    = reinterpret_cast<uint64_t>(&imageCreateInfo);
    EGLint attribs[] = {
        EGL_VULKAN_IMAGE_CREATE_INFO_HI_ANGLE,
        static_cast<EGLint>((info >> 32) & 0xffffffff),
        EGL_VULKAN_IMAGE_CREATE_INFO_LO_ANGLE,
        static_cast<EGLint>(info & 0xffffffff),
        EGL_NONE,
    };
    EGLImageKHR eglImage = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_VULKAN_IMAGE_ANGLE,
                                             reinterpret_cast<EGLClientBuffer>(&vkImage), attribs);
    EXPECT_NE(eglImage, EGL_NO_IMAGE_KHR);

    // Create a custom FBO with RGBA8 attachment for rendering to avoid 16x16 window limits.
    GLTexture colorTex;
    glBindTexture(GL_TEXTURE_2D, colorTex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 nullptr);

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

    // Set up the imported RGB565 texture.
    GLTexture texture;
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);
    glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    GLuint textures[1] = {texture};
    GLenum layouts[1]  = {GL_NONE};
    glAcquireTexturesANGLE(1, textures, layouts);

    // Fill the texture with red color (0xF800 in RGB565).
    std::vector<GLushort> pixels(kWidth * kHeight, 0xF800);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RGB, GL_UNSIGNED_SHORT_5_6_5,
                    pixels.data());
    EXPECT_GL_NO_ERROR();

    // Draw a quad to our FBO sampling from the imported texture.
    ANGLE_GL_PROGRAM(drawTex2D, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
    glUseProgram(drawTex2D);

    glViewport(0, 0, kWidth, kHeight);
    glClearColor(0.f, 0.f, 1.f, 1.f);
    glClear(GL_COLOR_BUFFER_BIT);

    drawQuad(drawTex2D, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

    // Verify the FBO is now red.
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::red);

    // Release the texture before destroying the image.
    glReleaseTexturesANGLE(1, textures, layouts);

    EXPECT_GL_NO_ERROR();
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    framebuffer.reset();
    colorTex.reset();
    texture.reset();

    glFinish();

    EXPECT_EGL_TRUE(eglDestroyImageKHR(display, eglImage));
    vkDestroyImage(helper.getDevice(), vkImage, nullptr);
    vkFreeMemory(helper.getDevice(), vkDeviceMemory, nullptr);
}

// Use this to select which configurations (e.g. which renderer, which GLES major version) these
// tests should be run against.
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND(VulkanImageTest,
                                       ES3_VULKAN().enable(Feature::ForceFallbackFormat));
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND(
    VulkanRGB565Test,
    ES3_VULKAN_SWIFTSHADER().enable(Feature::PreferBGR565ToRGB565),
    ES3_VULKAN().enable(Feature::ForceFallbackFormat),
    ES3_VULKAN().enable(Feature::PreferBGR565ToRGB565).enable(Feature::ForceFallbackFormat));
ANGLE_INSTANTIATE_TEST_ES3(VulkanMemoryTest);

}  // namespace angle
